再探内部类

开篇

内部类,一个熟悉又陌生的老朋友。到目前为止也没怎么在开发中写过内部类。但是最近在看 JDK 源码的时候,发现内部类使用到的频率非常高,这给我阅读源码带来了不少挑战。所以为了扫清这个障碍,专门复习一下内部类的相关知识。


内部类探索

Java 内部类总共由四种:成员内部类,局部内部类,匿名内部类,静态内部类。

成员内部类

成员内部类是最普通的内部类,定义在一个类的内部。以下就是一个简单内部类演示。

/**
 * @author lin
 * @date 2018/6/6 下午8:40
 * @description: 成员内部类演示
 */
public class OuterClass {
    private int a = 1;
    public int b = 2;
    private int e = 5;

    class InnerClass {
        private int c = 3;
        public int d = 4;
        private int e = 6;

        public void t2() {
            System.out.println("内部类: " + a);
            System.out.println("内部类: " + b);
            System.out.println("内部类: " + c);
            System.out.println("内部类: " + d);
            System.out.println("内部类: " + e);//输出 6
            System.out.println("内部类: " + OuterClass.this.e);//输出 5
        }
    }

    public void t1() {
        System.out.println("外部类: " + a);
        System.out.println("外部类: " + b);
//      System.out.println(c);compile error
//      System.out.println(d);compile error
        System.out.println("外部类: " + new InnerClass().c);
        System.out.println("外部类: " + new InnerClass().d);
        System.out.println("外部类: " + e);//输出 5
    }

    public static void main(String[] args) {
        new OuterClass().t1();
        new OuterClass().new InnerClass().t2();
    }
}
/*输出:
外部类: 1
外部类: 2
外部类: 3
外部类: 4
外部类: 5
内部类: 1
内部类: 2
内部类: 3
内部类: 4
内部类: 6
内部类: 5
*/
成员内部类小结
  1. InnerClass 就是 OuterClass 的内部类,OuterClass 就是普通的类,也可名曰外部类。这里为了区分,就称呼其为外部类。内部类说白了就是外部类的一个类成员,

所以普通成员变量可以设置的访问权限(public,protected,包权限,private),内部类都可以设置。

  1. 内部类里可以访问外部类的所有成员变量和方法(包括 private)。但是外部类访问内部类则需要先初始化一个外部类对象,再用这个对象的引用来访问。(为什么内部类可以直接访问?) 执行一下 javac OuterClass.java, 生成了 OuterClass.class 和 OuterClass$InnerClass.class 文件,我们看下 OuterClass$InnerClass.class 就知道为什么了。
/** OuterClass$InnerClass.class **/
class OuterClass$InnerClass {
    private int c;
    public int d;
    private int e;

    OuterClass$InnerClass(OuterClass var1) {
        this.this$0 = var1;//这里将外部类对象引用赋值给了 this.this$0
        this.c = 3;
        this.d = 4;
        this.e = 6;
    }

    public void t2() {
        System.out.println("内部类: " + OuterClass.access$000(this.this$0));//通过 this.this$0 获取外部类的私有成员变量
        System.out.println("内部类: " + this.this$0.b);//通过 this.this$0 获取外部类的公共成员变量
        System.out.println("内部类: " + this.c);
        System.out.println("内部类: " + this.d);
        System.out.println("内部类: " + this.e);
        System.out.println("内部类: " + OuterClass.access$100(this.this$0));
    }
}
  1. 外部类和内部类的成员变量或者方法重名时,如果想要在内部类里面访问外部类的重名变量或者方法,则需要使用如下方式访问:
外部类.this.xxx
  1. 如果要初始化内部类,则需要使用如下方式:
OuterClass outer = new OuterClass();
outer.new InnerClass(); //初始化内部类

局部内部类

局部内部类和成员内部类唯一的区别是局部内部类是定义在一个方法或者作用域里,局部内部类不能有 public,protected, private,以及 static 修饰的。

/**
 * @author lin
 * @date 2018/6/6 下午8:40
 * @description: 局部内部类演示
 */
public class OuterClass {

    int x = 0;
    public DrawAble t3() {
        int a = 1;

        class Innclass implements DrawAble {
            public void tt() {
                System.out.println(a); //内部类访问方法中的变量
//              a++//报错
                System.out.println(x); //内部类里访问外部类的变量     
                //局部内部类初始化的时候会传入外部类的对象引用以及,使用到的方法中的变量。
                x++;//正常编译
            }
        }
        return new Innclass();
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        outer.t3().tt();
    }
}

interface DrawAble {
    void tt();
}
/** 输出:
1
0
*/

编译成 class 的内部类长这幅德行:

class OuterClass$1Innclass implements DrawAble {
    OuterClass$1Innclass(OuterClass var1, int var2) {//传入一个外部类的对象引用,和使用到的一个方法中的变量。
        this.this$0 = var1;
        this.val$a = var2;
    }

    public void tt() {
        System.out.println(this.val$a);
        System.out.println(this.this$0.x);
    }
}
局部内部类小结

局部内部类访问的方法中的变量,需要用 final 修饰。Java 8 中虽然可以不用加,但是编译器在编译的时候会自动加上。这就可以理解上面代码为什么 a++ 会报错了,但是 Java 为什么要这么干就不清楚了,感觉拷贝一个值指到内部类中进行操作,而不改动方法中的那个变量这种场景也是可以有的,比如内部类中我只需要这个值来进行一些操作,但是方法中这个变量在后续步骤中可能还有其它用处。

匿名内部类

匿名内部类和局部内部类在成员变量使用上基本一致。

/**
 * @author lin
 * @date 2018/6/6 下午8:40
 * @description: 匿名内部类演示
 */
public class OuterClass {
    public DrawAble getDrawMethod() {
        return new DrawAble(){
            @Override
            public void tt() {
                System.out.println("draw");
            }
        };
    }

    public static void main(String[] args) {
        new OuterClass().getDrawMethod().tt();
    }
}

interface DrawAble {
    void tt();
}
匿名内部类小结
  1. 使用匿名内部类实现父类或者接口中的方法并生成一个对象的前提是这个父类或者接口先存在。
  2. 匿名内部类和局部内部类一样没有访问修饰符及 static 等修饰符。
  3. 匿名内部类没有构造方法。
  4. Java 8 出已经有 lambda 表达式了,简化了匿名内部类的写法。

静态内部类

静态内部类或者称之为 嵌套类 更为恰当,因为静态内部类是不需要依赖外部类的,虽然定义在一个类里面,但是要创建一个静态内部类并不需要先创建一个外部类对象,然后再用这个对象创建内部类对象。

/**
 * @author lin
 * @date 2018/6/6 下午8:40
 * @description: 静态内部类演示
 */
public class OuterClass {

    private int a = 1;
    private static int b = 2;

    static class InnerClass {
        private int c = 3;
        public void t() {
//            System.out.println(a);//报错
            System.out.println(b);
            System.out.println(c);
        }
    }

    public static void main(String[] args) {
        new InnerClass().t();;
//      new OuterClass.InnerClass().t();//在类外部使用的时候,需要先指定外部类。
    }
}
/**输出:
2
3
*/
静态内部类小结
  1. 静态内部类不能使用任何外部类的非静态变量或者方法
  2. 静态内部类创建是不需要先创建一个外部类对象的

总结

内部类共性
  • 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号
  • 内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否是private的
  • 内部类声明成静态的,就不能随便的访问外部类的成员变量了,此时内部类只能访问外部类的静态成员变量
为什么要使用内部类?
  1. 实现对类进行隐藏,一般外部类是public属性,而内部类可以是private、protected。(比如一个类就是只能被特有的一个类对象引用创建,不允许其它对象修改和创建,这个时候私有内部类就符合我们的要求了)
  2. 闭包
  3. 通过内部类可以实现多重继承。一个类只能继承一个类,通过定义多个内部类,可以实现继承不止一个类

参考资料:
《Java 核心技术卷1》

https://www.cnblogs.com/dolphin0520/p/3811445.html

https://www.cnblogs.com/chenssy/p/3388487.html

https://www.zhihu.com/question/21373020

https://baike.baidu.com/item/java%E5%86%85%E9%83%A8%E7%B1%BB/2292692?fr=aladdin

Comments
Write a Comment