夯实Java基础(十一)——内部类

1、内部类的概念

内部类顾名思义:将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。对于很多Java初学者来说,内部类学起来真的是一头雾水,根本理解不清楚是个什么东西,包括我自己(我太菜了!哈哈),所以接下来我要好好地来研究一下。

我们来看下内部类的定义格式;

public class OuterClass {
        //code
    class InnerClass{
        //code
    }
}

这里的InnerClass就是一个内部类。无论在我们的学习中还是工作中,内部类用到的地方真的不是很多,一般都出现在源码中,但是我们还是要搞懂内部类,因为后面对我们阅读源码非常有帮助。而且随着后面我们编程能力的提高,自然而然会领悟到它的魅力所在,它能够让我们设计出更加优雅的程序结构。在使用内部类之前我们需要明白为什么要使用内部类,内部类能够为我们带来什么样的好处。

在《Think in java》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

也就是说内部类拥有类的基本特征(可以继承父类,实现接口)。在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。(注:内部类可以嵌套内部类,但是这极大的破换了代码的结构,这里不推荐使用)

那我们来看一下使用内部类如何进行多继承,接口多继承就不举例了,因为接口本身就可以实现多继承。

 class Father{
     public String handsome(){
         return "爸爸很帅气";
     }
 }

 class Mother{
     public String beautiful(){
         return "妈妈很漂亮";
     }
 }

 class Son{
     //内部类继承了Father类
     class MyFather extends Father{
         //重写父类方法
         public String handsome(){
             return "我遗传了爸爸的帅气";
         }
     }
     //内部类继承了Mother类
     class MyMother extends Mother{
         //重写父类方法
         public String beautiful(){
             return "我遗传了妈妈的漂亮";
         }
     }
 }

 public class Test {
     public static void main(String[] args) {
         Son son=new Son();
         Son.MyFather myFather=son.new MyFather();
         System.out.println(myFather.handsome());
         Son.MyMother myMother=son.new MyMother();
         System.out.println(myMother.beautiful());
     }
 }

运行结果:

夯实Java基础(十一)——内部类

从上面的举例代码可以看出,两个内部类分别继承了Father、Mother类,并且重写了父类的方法,这是内部类最重要的特性:内部类可以继承一个与外部类无关的类,保证了内部类的独立性,正是基于这一点,多重继承才会成为可能。

可以发现在创建内部类实例的时候,使用了 .new 这个特征,与以往我们创建实例不太相同。.new可以这样理解:根据外部类来创建内部类的对象实例。

Java中内部类可分为四种:成员内部类、局部内部类、匿名内部类、静态内部类。下面我们逐一介绍这四种内部类:

2、成员内部类

成员内部类是定义在类中的类。我们可以把成员内部类看成是外部类的一个成员,所以成员内部类可以无条件访问外部类的所有成员属性和成员方法,包括private成员和静态成员。但是外部类要访问内部类的成员属性和方法则需要通过内部类实例来访问。当成员内部类拥有和外部类同名的成员变量或者方法时,会优先访问的是成员内部类的成员,但是我们可以使用 .this(如果有继承可以使用super)来访问外部类的变量和方法。

在成员内部类中要注意两点:

  1. 成员内部类中不能存在任何static的变量和方法;
  2. 成员内部类是依附于外部类的,所以只有先创建了外围类才能够创建内部类(静态内部类除外)。
 class OuterClass{
     private String outerName="tang_hao_outer";
     private int outerAge=22;

     public OuterClass() {
     }

     //成员方法
     public void outerMethod() {
         System.out.println("我是外部类的outerMethod方法");
     }

     //外部类静态方法
     public static void outerStaticMethod() {
         System.out.println("我是外部类的outerStaticMethod静态方法");
     }
     //定义返回内部类实例的方法,推荐使用该方法来换取内部类实例
     public InnerClass getInnerClassInstance(){
         return new InnerClass();
     }

     //内部类
     class InnerClass{
         private String innerName="tang_hao_Inner";
         private int innerAge=21;

         public InnerClass() {
         }

         public void show(){
             //当名字和外部类一样时,默认调用内部类的成员属性
             System.out.println("内部类变量:"+innerName);
             System.out.println("内部类变量:"+innerAge);
             //当名字和外部类一样时,可以使用 。this来调用外部类属性
             System.out.println("外部类变量:"+OuterClass.this.outerName);
             System.out.println("外部类变量:"+OuterClass.this.outerAge);
             //访问外部类的方法
             outerMethod();
             outerStaticMethod();
         }
     }
 }
 public class Test {
     public static void main(String[] args) {
         //普通方法创建实例
         OuterClass outerClass=new OuterClass();
         OuterClass.InnerClass innerClass=outerClass.new InnerClass();
         innerClass.show();
         System.out.println("-------------------");
         //调用外部类的getInnerClassInstance来创建内部类实例
         OuterClass.InnerClass innerClassInstance = outerClass.getInnerClassInstance();
         innerClassInstance.show();
     }
 }

运行结果:

夯实Java基础(十一)——内部类

从上面示例中,当内部类和外部类的变量和方法一样时,我们用了 .this来调用外部类的属性(静态除外,因为静态随类加载而加载,优于对象的创建),它可以理解为:产生一个指向外部类的引用。还有如果该内部类的构造函数无参数,强烈推荐使用类似getInnerClassInstance()这样的方法来获取成员内部类的实例对象。

3、局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。注意:局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。

局部内部类一般都用于返回一个类或实现接口的实例。我们用Comparable接口为例:

 class OuterClass{
     //创建返回一Comparable接口实例的方法
     public Comparable getComparable(){
         //创建一个实现Comparable接口的内部类:局部内部类
         class MyComparable implements Comparable{
             @Override
             public int compareTo(Object o) {
                 return 0;
             }
         }
         //返回实现Comparable接口的实例
         return new MyComparable();
     }
 }

当我们创建外部类的实例调用getComparable()方法时,就可以轻松获取实现Comparable接口的实例了。

注意:局部内部类如果想用方法传入形参,该形参必须使用final声明(JDK8形参变为隐式final声明)。上面的例子如果是getComparable(Object o),那么这个形参前面就隐式加了final关键字。

4、匿名内部类

匿名内部类就是没有名字的内部类。它与局部内部类很相似,不同的是它没有类名,如果某个局部类你只需要用一次,那么你就可以使用匿名内部类。匿名内部类可以使你的代码更加简洁,你可以在定义一个类的同时对其进行实例化。

 //创建一个接口
 interface IPerson{
     public void eat();
     public void sleep();
 }

 public class OuterClass {
     //这里注意,局部内部类如果需要通过方法传入参数,该形参必须使用final声明(JDK8形参变为隐式final声明)
     //我用的JDK8,所以这里没有显式的加final,但是JVM会自动加
     public static IPerson getInnerClassInstance(String eat,String sleep){
         return new IPerson() {
             @Override
             public void eat() {
                 System.out.println(eat);
             }

             @Override
             public void sleep() {
                 System.out.println(sleep);
             }
         };//这个分好要注意
     }

     public static void main(String[] args) {
         IPerson person = OuterClass.getInnerClassInstance("吃饭", "睡觉");
         person.eat();
         person.sleep();
     }
 }

运行结果:吃饭、睡觉

我们知道在抽象类和接口中是不能被实例化的,但是在匿名内部类中我们却看见new了一个IPerson接口,这是怎么回事。这是因为匿名内部类是直接使用new来生成一个对象的引用,而在new对象时,系统会自动给抽象类或接口添加一个它们的实现类,当然这个引用是隐式的,我们看不见。我们自己拆分出来理解一下,注意这里是自己想出来的,运行时并不会有这些类存在:

 class Farmer implements IPerson{

     @Override
     public void eat() {
         System.out.println("农民吃饭");
     }

     @Override
     public void sleep() {
         System.out.println("农民睡觉");
     }
 }

一般我们创建抽象类或接口的实例是这样的:IPerson iPerson = new Farmer();这个可以叫做是非匿名对象非匿名类,而我们创建的是匿名内部类,所以这个实现类不能有名字,所以只好叫父类的名字,所以就看到了前面直接new了一个接口,其实是隐式的创建了实现类的对象。(不知道这样讲对不对,鄙人菜鸟一个,如果有什么不对的或理解错误的地方,欢迎指出,虚心接受!)

匿名内部类最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口。

在使用匿名内部类的过程中,我们需要注意如下几点:

  1. 使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
  2. 匿名内部类中是不能定义构造函数的。
  3. 匿名内部类中不能存在任何的静态成员变量和静态方法。
  4. 匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
  5. 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

5、静态内部类

静态内部类是指用static修饰的内部类。在前面夯实Java基础(七)——Static关键字中提到了static关键字可以修饰内部类。我们知道普通类是不允许声明为静态的,只要内部类才可以,被static修饰的内部类它不依赖于外部类的实例。这是因为非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外部类。

static修饰内部类注意几点:

  • 静态内部类可以不依赖于外部类的实例,但是要注意它们创建对象的区别。
  • 静态内部类只能访问外部类的静态变量和静态方法,否则编译会报错。
  • 非静态内部类中可以调用外部类的然后成员,不管是静态的还是非静态的。
  • 如果需要调用内部类的非静态方法,必须先new一个OuterClass的对象outerClass,然后通过outer。new生成内部类的对象,而static内部类则不需要。

简单举例:

 class OuterClass{
     //静态变量
     private static int static_num=66;
     //非静态变量
     private int num=99;

     //静态内部类
     static class InnerStaticClass{
         public void print(){
             //静态内部类只能访问外部类的静态变量和静态方法
             System.out.println("静态内部类方法print()=="+static_num);
             staticShow();
         }
     }
     //非静态内部类
     class InnerClass{
         public void display(){
             //非静态内部类中可以调用外部类的任何成员,不管是静态的还是非静态的
             System.out.println("外部类静态变量=="+static_num);
             System.out.println("外部类普通变量=="+num);
             show();
             System.out.println("非静态内部类方法display()=="+num);

         }
     }
     public void show(){
         System.out.println("外部类非静态show()方法");
     }
     public static void staticShow(){
         System.out.println("外部类静态staticShow()方法");
     }
 }
 public class Test {
     public static void main(String[] args) {
         //static对象实例
         OuterClass.InnerStaticClass staticClass=new OuterClass.InnerStaticClass();
         staticClass.print();

         //非static对象实例
         OuterClass outerClass=new OuterClass();
         OuterClass.InnerClass innerClass=outerClass.new InnerClass();
         innerClass.display();
     }
 }

运行结果:

夯实Java基础(十一)——内部类

从上面的例子我们可以看到静态内部类和非静态内部类的区别。

上一篇:2月刷题记录


下一篇:再读:Linux系统命令及Shell脚本实践指南(一)