从编译过程看内部类和lambda表达式

什么是内部类

内部类按名称分为:匿名内部类,和非匿名内部类。
非匿名内部类又分为:静态内部类和非静态内部类。

有时候我们会发现,修改外部类的某个方法使得它访问了内部类的某个方法,编译之后就会发现字节码中会多出来一个额外的方法,所以为了了解原理,还是要看看编译期间它到底做了什么。第一点我们都知道,内部类在编译期间会被编译成一个和外部类一样的顶级类。

1 静态内部类/非静态内部类的区别

静态内部类/非静态内部类
静态内部类/非静态内部类的区别大家都熟悉吧,非静态内部类持有一个外部类的引用,静态内部类不持有外部类的引用。这点大家应该都有经历,非静态内部类是不能被外部其他类通过new来生成的,这样的话就可以把非静态内部类当做是外部类的一部分,外部类初始化才会初始化非静态内部类;而静态类可以被其他类(除了自己和外部类)通过new生成的。所以在android性能优化中建议handle的实现尽量使用静态内部类,而且使用虚引用,静态内部类不会引用外部类,防止外部类Activity类不能被回收导致的OOM。

实际上我们把编译后的字节码看一看就知道了,在创建非静态内部类的时候,编译器会自动合成this$0域表示的就是外部类的引用。
代码如下:

这里写图片描述
这里写图片描述

外部类和内部类相互访问
既然内部类实际上和外部类一样的顶级类了,既然都是顶级类,那就意味着对方的private的method/field是没有办法被访问到的,事实上外部类为了访问内部私有的域/方法,编译期间自动会为内部类生成access&XX方法。

public class BaseBug{
    public void test(Context context){
        InnerClass inner=new InnerClass("old apk");
        Toast.makeText(context, inner.s, Toast.LENGTH_SHORT).show();
    }
    class InnerClass{
        private String s;
        private InnerClass(String s){
            this.s=s;
        }
    }
}

我们可以看到BaseBug可以访问到内部类的InnerClass的私有域S,所以编译器为InnerClass自动生成了access&100这个方法,这个方法的实现简单返回私有域s的值。同样的如果此时匿名内部类想要访问外部类的私有域/方法,编译器同样会为外部类自动生成access&XX的相关方法以供内部类访问。

2 匿名内部类的编译

匿名内部类实际上也是一个内部类,但是匿名类是没有名字的,匿名内部类的名称格式一般是 外部类&numble ,后面的numble是根据匿名内部类在外部类中出现的先后关系,依次累加命名的。因此我们可以知道匿名内部类类似于非静态内部类。但是没有具体名称,外部其他类也不可引用。

什么是lambda

lambda是为了添加缺失的函数式编程的特点,而新增的功能。函数式借口具有两个特征:一个是接口,还有这个接口具有唯一的一个抽象方法,我们将满足这两个特征的接口称为函数式接口。比如:java.lang.Runnable和java.util.Comparator都是典型的函数式接口。跟匿名内部类的区别如下:

  • 关键字this匿名类的this关键字指向匿名类,而lambda表达式的this关键字 指向包围lambda表达式的类。

  • 编译方式:java编译器会将lambda表达式编译成类的私有方法,使用了java7的invokedynamic字节码指令动态绑定方法(也就是一个类可以拥有不仅仅是自己的方法还可以动态绑定其他类的方法)。编译器编译匿名内部类只是将其编译成外部类&numble的新类。

如下代码展示了lambda使用的情况:
这里写图片描述
这里写图片描述

上述代码编译后:
这里写图片描述
这里写图片描述

可以发现:

  • 编译期间自动生成了私有静态lambda main XX(X)的方法,这个方法的实现其实就是lambda表达式里面的逻辑
  • invokedynamic指令执行的lambda表达式
  • 相比较于匿名内部类,这个并没有动态生成新的类(就是不会生成对应的字节码文件,但是在内存中还是会有新class类创建)

invokedynamic指令执行时会去调用LambdaMetaFactory的metafactory的静态方法,这个方法实际上会在运行时生成一个实现函数式接口的具体类,然后具体类就会调用test私有静态方法Lambda main XX(X)方法。如果将.class打印出来就是这样的:
这里写图片描述

根据上面代码可以知道,lambda表达式在编译后它的唯一接口方法逻辑会被invokedynamic动态链接到它所处在的类test中,作为它所处在的类的私有静态方法(就是test.lambda main 0()方法和test.lambda main 1var1()方法),而LambdaMetaFactory也会创建一个lambda实现的接口实例(Test$$Lambda$1和),这个实例的方法只是调用了这个实例所在的类的对应的静态内部方法。

猜你喜欢

转载自blog.csdn.net/u012345683/article/details/74859624