杭州做网站小程序公司,沈阳快速排名优化,宁波网站推广公司有哪些,wordpress默认站点问题描述 Effectively final
Java 1.8 新特性#xff0c;对于一个局部变量或方法参数#xff0c;如果他的值在初始化后就从未更改#xff0c;那么该变量就是 effectively final#xff08;事实 final#xff09;。 这种情况下#xff0c;可以不用加 final 关键字修饰。 …问题描述 Effectively final
Java 1.8 新特性对于一个局部变量或方法参数如果他的值在初始化后就从未更改那么该变量就是 effectively final事实 final。 这种情况下可以不用加 final 关键字修饰。 内部类会持有外部类对象的引用
非静态内部类持有外部类的引用 以编译器自动生成的成员变量的形式持有通过编译器自动生成的构造方法传入。 静态内部类不持有外部类的引用。
内部类
非静态内部类会通过自动生成的构造函数持有一个外部类对象的引用 即便给内部类增加一个非默认的构造函数编译器依然会自动为构造函数添加一个外部类对象的参数 静态内部类
静态内部类自动生成的构造方法不会持有外部类的引用 匿名内部类
匿名内部类也会通过构造函数持有一个外部类对象的引用 匿名内部类会将捕获的局部变量在其构造函数中传入 匿名内部类捕获外部变量添加 final 的作用是保证匿名内部类捕获的副本引用和外部的局部变量始终都指向同一个对象也就是没有人可以修改它们的指向。 假如外部方法的局部变量不加final有可能外部的局部变量的指向改变了但是内部类却不知道。这样就可能导致内部类操作的对象和外部的局部变量指向的不是同一个对象会出现 bug。
注意匿名内部类捕获的局部变量加 final 是指的处于同一方法内的局部变量如果捕获的是所处的外部类中的变量则不需要加 final这是因为在这种情况下可以直接通过外部类对象的引用来获取到其成员变量。 总结
匿名内部类持有外部类的引用 以编译器自动生成的成员变量的形式持有通过编译器自动生成的构造方法传入。匿名内部类通过这个引用访问外部类的成员变量和方法。 匿名内部类访问外部局部变量时其实是访问自身的一个成员变量 这个成员变量是编译器自动生成的这个成员变量由编译器自动生成的构造方法初始化为了保证这个成员变量和外部局部变量时刻保持一致性二者必须都是final的。
根本原因就是为了保证内部和外部对这个局部变量的对象的操作保持一致性。因此要求两个副本均不可变。
Kotlin 的匿名内部类
kotlin 中的匿名内部类不会外部变量时即便不使用类似final的关键字修饰也能保证内部和外部访问的同一局部对象的一致性。 通过以上字节码分析可以得出结论
Kotlin 的匿名内部类不会在构造函数中传入整个外部类对象的引用但是对于捕获的局部变量会自动生成一个不可变val的保证类对象ObjectRef传入构造函数中。
对应的伪代码结构2如下 ObjectRef 是什么 我们看到它就是一个泛型类内部持有一个 element 的泛型成员变量可以认为是 Kotlin 为了解决捕获局部变量问题生成的一个装箱类。
除了 ObjectRefKotlin 中还有 ByteRefShortRef、IntRef、LongRef、FloatRef、 DoubleRef、CharRef、BooleanRef。
所以虽然与 Java 的解决方式不同但本质上看思想是一致的都要保持内部和外部对捕获变量的操作一致性即保证这两个副本的不可变性。 虽然包装类对象的指向不可变但是包装类对象里面包的东西是可以改变指向的这一点比 Java 要优秀 这一切都是编译器为我们自动实现的但对于开发者而言体验上就会跟 Java 有明显的不同“Java 需要 final 捕获但 Kotlin 不需要 val 捕获”但这是一个错觉实际上 Kotlin 也需要只不过你看不到而已。
内存泄漏问题
内存泄漏的根本原因就是一个长生命周期的对象被一个短生命周期的对象所引用。 一旦内部类对象被长生命周期对象引用或自身生命周期过长就会导致外部类无法被 GC 回收因为它们在同一条引用链上根据 GC Root 可达性分析算法判断为可达。比如在 Activity 中通过匿名内部类的方式创建的 Handler、AsyncTask、ResultReceiver等。
解决方式
不使用(匿名)内部类将类的定义放在外部显示的构造传参并在外部类销毁时如Activity.onDestroy()主动断开对外部类的引用例如很多框架会提供解绑、反注册的API以便用户在页面销毁时进行调用。必须要使用内部类的场景采用静态内部类 弱引用指向外部类对象的方式。由于弱引用一旦被GC扫描发现就会回收所以不存在内存泄漏问题