Java内部类从字节码层面理解

1. 非静态内部类

  • 非静态内部类常用语法
package inner;
public class OuterClass {
    private String string;
    private int anInt;
    public OuterClass(String string, int anInt) {
        this.string = string;
        this.anInt = anInt;
    }
    public OuterClass(){
    }
    private void outInt(){
        System.out.println("int:"+anInt);
    }
    //非静态方法
    public void output1(){
        //外部类的非静态方法里面直接使用new就可以创建内部类实例
        InnerClass innerClass = new InnerClass();
        innerClass.innerPut();
    }
    //静态方法
    public static void output2(){
        //外部类的静态方法里面必须先创建外部类实例,再用外部类实例创建内部类实例
        OuterClass outerClass = new OuterClass("str", 10);
        InnerClass innerClass = outerClass.new InnerClass();  //语法为:外部类实例.new 内部类
        innerClass.innerPut();
    }
    //创建内部类
    class InnerClass{
        private void innerPut(){
            //内部类可以调用外部类的私有成员变量和私有成员方法
            System.out.println("string:"+string);
            outInt();
        }
    }

    public static void main(String[] args){
        //静态方法中使用内部类,必须先创建外部类实例,在用外部类实例创建内部类实例
        OuterClass outerClass = new OuterClass("str", 10);
        //声明外部类时可以添加内部类的类名,当然也可以不添加
        OuterClass.InnerClass innerClass = outerClass.new InnerClass();
        innerClass.innerPut();
    }
}

运行结果:

string:str
int:10
  • 非静态内部类可以调用外部类的私有成员变量和成员方法,但是不能声明静态变量和静态方法、静态类,原因:JVM在进行类加载的时候会先加载静态属性和静态代码块,因此非静态类里面的静态属性会优先于静态类进行加载,此时就会出现矛盾
  • 非静态内部类的声明:
    • 在外部类的方法中既可以使用InnerClass innerClass;,也可以使用OuterClass.InnerClass innerClass;,推荐使用第二种
    • 在不是外部类中的类声明,则只能使用第二种,OuterClass.InnerClass innerClass = new OuterClass().new InnerClass();
  • 非静态内部类的初始化:
    • 在外部类非静态方法中:和普通类的声明一样,InnerClass innerClass = new InnerClass();
    • 在外部类静态方法中:需要先创建外部类的实例,然后再用外部类的实例来创建内部类实例,InnerClass innerClass = new OuterClass().new InnerClass()
  • 在内部类中引用外部类对象OuterClass2.this
package inner;
public class OuterClass2 {
    public void output(){
        System.out.println("OuterClass2.output");
    }
    class InnerClass{
        public OuterClass2 getOuterClass2(){
            return OuterClass2.this; //引用外部类对象
        }
    }
    public static void main(String[] args){
        InnerClass innerClass = new OuterClass2().new InnerClass();
        OuterClass2 outerClass2 = innerClass.getOuterClass2();
        outerClass2.output();
    }
}

运行结果:

OuterClass2.output

2. 静态内部类

  • 静态内部类又称嵌套类
package inner;
public class OuterClass3 {
     //创建静态内部类
     static class StaticInnerClass{
         //可以声明静态和非静态的变量和方法,但是不能调用外部类的非静态成员和非静态方法
        private String str = "str";
        private static String string = "string";
        public void InnerPut(){
            System.out.println(str);
        }
        public static void staticInnerPut(){
            System.out.println(string);
        }
        static class StaticInnerClass2{
            public void staticInnerPut2(){
                System.out.println(string);
            }
        }
    }
    public static void main(String[] args){
         //第一种初始化方法
        OuterClass3.StaticInnerClass staticInnerClass = new OuterClass3.StaticInnerClass();
        //第二种初始化方法
        OuterClass3.StaticInnerClass staticInnerClass1 = new StaticInnerClass();
        staticInnerClass.InnerPut();
        OuterClass3.StaticInnerClass.StaticInnerClass2 staticInnerClass2 = new StaticInnerClass.StaticInnerClass2();
        staticInnerClass2.staticInnerPut2();
    }
}

运行结果:

str
string
  • 静态内部类中可以声明静态和非静态的成员变量、方法、类,但是在不能在嵌套类的对象中访问非静态的外围类对象
  • 静态内部类初始化:
    • 第一种方法OuterClass3.StaticInnerClass staticInnerClass = new OuterClass3.StaticInnerClass();
    • 第二种方法:OuterClass3.StaticInnerClass staticInnerClass1 = new StaticInnerClass();
  • 静态内部类中没有this引用,类似于一个static方法

3. 匿名内部类

  • 1.本质上是:继承父类形式的匿名内部类
package inner;
public class OuterClass4 {
    public FatherClass getFatherClass(){
        //匿名内部类
        return new FatherClass(){
            public void output(){
                System.out.println("Son");
            }
        };
    }
    public static void main(String[] args){
        OuterClass4 outerClass4 = new OuterClass4();
        FatherClass fatherClass = outerClass4.getFatherClass();
        fatherClass.output();
    }
}
class FatherClass{
    public void output(){
        System.out.println("Father");
    }
}

上述匿名内部类是下述的简化形式:

package inner;
public class OuterClass4_2 {
    class SonClass extends FatherClass{
        public void output(){
            System.out.println("son");
        }
    }
    public FatherClass getFatherClass(){
        return new SonClass();
    }
    public static void main(String[] args){
        OuterClass4_2 outerClass4_2 = new OuterClass4_2();
        FatherClass fatherClass = outerClass4_2.getFatherClass();
        fatherClass.output();
    }
}
class FatherClass{
    public void output(){
        System.out.println("Father");
    }
}

两个的输出结果一样:

Son
  • 2.本质上是:实现接口的匿名内部类形式
package inner;
public class OuterClass5 {
    public FatherInterface getFatherInterface(){
        return new FatherInterface() {
            @Override
            public void output() {
                System.out.println("Son");
            }
        };
    }
    public static void main(String[] args){
        OuterClass5 outerClass5 = new OuterClass5();
        FatherInterface fatherInterface = outerClass5.getFatherInterface();
        fatherInterface.output();
    }
}
interface FatherInterface{
    void output();
}

上述匿名内部类是下述的简化形式

package inner;
public class OuterClass5_2 {
    class SonClass implements FatherInterface{
        @Override
        public void output() {
            System.out.println("son");
        }
    }
    public FatherInterface getFatherInterface(){
        return new SonClass();
    }
    public static void main(String[] args){
        OuterClass5_2 outerClass5_2 = new OuterClass5_2();
        FatherInterface fatherInterface = outerClass5_2.getFatherInterface();
        fatherInterface.output();
    }
}
interface FatherInterface{
    void output();
}

两个的输出结果是一样的:

Son
  • 结论:匿名内部类实际上是继承了某个父类,或者是实现某个接口,通过new表达式返回引用被自动向上转型
  • 3.匿名内部类中向父类构造器传递参数:
package inner;
public class OuterClass4 {
    public FatherClass getFatherClass(String string){
        //匿名内部类
        return new FatherClass(string){
            public void output(){
                System.out.println("Son");
            }
        };
    }
    public static void main(String[] args){
        OuterClass4 outerClass4 = new OuterClass4();
        FatherClass fatherClass = outerClass4.getFatherClass("string");
        fatherClass.output();
    }
}
class FatherClass{
    private String string;
    public FatherClass(String string){
        this.string = string;
    }
    public void output(){
        System.out.println(string);
    }
}

输出结果:

Son
  • 4.匿名内部类变量注意点
package inner;
public class OuterClass4 {
    public FatherClass getFatherClass(String string){
        String str = "str";
        str = "null"; //可以重新赋值这可以看出此时的str并不是final类型
        //匿名内部类,如果你在匿名类中使用了参数变量或使用了局部变量,则它必须为final类型;如果你没有在匿名类中使用过它,那它就没有被默认添加final类型
        return new FatherClass(string){
            public void output(){
                System.out.println(string);//此时的string为final类型,如果再用string重新赋值则会报错
                string = "string";//报错
            }
        };
    }
    public static void main(String[] args){
        OuterClass4 outerClass4 = new OuterClass4();
        FatherClass fatherClass = outerClass4.getFatherClass("string");
        fatherClass.output();
    }
}
class FatherClass{
    private String string;
    public FatherClass(String string){
        this.string = string;
    }
    public void output(){
        System.out.println(string);
    }
}
  • 注意:匿名内部类中如果使用了方法传递进来的参数或者使用了局部变量,那么该参数一定是final类型。在JDK8之前不写final会报错,JDK8之后不写的话底层会默认帮你添加final。但是如果你没有在匿名内部类中使用过参数和局部变量,那么它们不会被默认添加上final类型
    原因:内部类和外部类各会产生一个class文件,实际上编译后的内部类的构造方法的里面,传了对应的外部类的引用和所有局部变量的形参。在外部类方法被调用后,局部变量会被销毁,因此内部类构造函数中的局部变量实际上是一份"复制"。如果不设为final,那么内部类构造完毕后,外部类的局部变量改变了,内部类中的局部变量却没有改变,这就产生了不一致的情况

4. 从字节码再看匿名内部类

  • 实例代码:
package inner;
public class InnerClass {
    public FatherClass getFatherClass(String string){
        String str = "str";
        return new FatherClass(string){
            public void output(){
                System.out.println(string);
            }
        };
    }
    public static void main(String[] args){
        InnerClass innerClass = new InnerClass();
        FatherClass fatherClass = innerClass.getFatherClass("string");
        fatherClass.output();
    }
}
class FatherClass{
    private String string;
    public FatherClass(String string){
        this.string = string;
    }
    public void output(){
        System.out.println(string);
    }
}

生成的字节码文件:
在这里插入图片描述

Compiled from "InnerClass.java"
class inner.InnerClass$1 extends inner.FatherClass {
  final java.lang.String val$string;
  final inner.InnerClass this$0;
  inner.InnerClass$1(inner.InnerClass, java.lang.String, java.lang.String);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:Linner/InnerClass;
       5: aload_0
       6: aload_3
       7: putfield      #2                  // Field val$string:Ljava/lang/String;
      10: aload_0
      11: aload_2
      12: invokespecial #3                  // Method inner/FatherClass."<init>":(Ljava/lang/String;)V
      15: return

  public void output();
    Code:
       0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #2                  // Field val$string:Ljava/lang/String;
       7: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      10: return
}

从这个字节码中我们可以得到很多信息:

  1. final java.lang.String val$string;因为我们使用了传进来的参数string,因此它是final类型。
  2. 因为我们没有在方法中使用局部变量str,因此它并不是final类型。
  3. final inner.InnerClass this$0;这个就是我们内部类可以访问外部类中的变量和方法的原因,内部类对象会秘密捕获外部类的一个引用,我们实际上就是通过这个外部类的引用来访问外部类的变量和方法的。
  4. inner.InnerClass$1(inner.InnerClass, java.lang.String, java.lang.String);参数、局部变量、外部类的引用在底层都转化成了匿名类内部的成员变量,并且是通过匿名类的构造函数传递进来的。

5. 局部内部类

  • 局部内部类与匿名内部类的简单比较
package inner;
public class InnerClass {
    InterfaceFather getInterfaceFather(){
        //局部内部类
        class LocalClass implements InterfaceFather{

            @Override
            public void output() {
                
            }
        }
        return new LocalClass();
    }
    InterfaceFather getInterfaceFather2(){
        //匿名内部类
        return new InterfaceFather() {
            @Override
            public void output() {
                
            }
        };
    }
}
interface InterfaceFather{
    void output();
}
  • 局部内部类的名字在方法外是不可见的,当我们需要自己命名一个构造器或者重载构造器或者需要多个内部类对象时,可以使用局部内部类。而匿名内部类只能用于实例初始化

6. 内部类标识符

  • 1.普通的内部类的标识符格式:外部类名$内部类名
public class OuterClass{
    public class InnerClass{}
    public static class InnerClass2{}
}

这个文件会生成的class文件:OuterClass$InnerClass.class, OuterClass$InnerClass2.class

  • 2.局部内部类的表示符格式:外部类名$数字+局部内部名,局部内部类加上数字编号,防止同一个类中,局部类重复
interface Contents{}
public class LocalClass{
    public void method(){
        class ContentesImp1 implements Contentes{
        }
    }

    public void method2(){
        class ContentesImp2 implements Contentes{
        }
    }
}

这个文件会生成的class文件:Contents.class LocalClass$1ContentesImp1.class, LocalClass$2ContentesImp2.class, LocalClass.class

  • 3.匿名类的标识符:外围类名$数字 编译器会简单地产生一个数字作为其标识符
interface Contents{}
public class AClass{
    public void method(){
        new Contents(){
        };
    }
    public void method2(){
        new Contents(){
        };
    }
}

这个文件会生成的class文件:AClass$1.class,AClass$2.class,AClass.class,Contents.class

  • 4.嵌套内部类:外部类名$内部类名 嵌套了多少内部类就在后面加多少个内部类
public class A{
    public class B{
        public class c{}
    }
}

这个文件会生成的class文件:A.clss,A$B.class,A$B$C.class

总结(为什么需要内部类)

  • 一般来说,内部类继承自某个类或实现某个接口。内部类的代码操作创建它的外部类对象,所以可以认为内部类提供了某种进入外部类的窗口
  • 内部类可以有效的实现"多重继承",这使得多重继承的方案变得完整
  • 可以封闭我们所不想被别人访问的类

参考资料

《Java编程思想》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值