1. 首先我们需要知道 java 中 内部类 和 lambda 表达式 是以什么样的形式存在
1 我们先看下lambada 表达式 的存在方式
public class LambdaTest {
//成员变量
Integer instance = 1;
private void test() {
// 用于直接引用
Integer local = 2;
// 用于传参
Consumer<Integer> consumer = (x) -> {
//传入的参数值 显示
System.out.println(x);
//引用 局部变量
System.out.println(local);
//引用 成员变量
System.out.println(instance);
//操作成员变量
instance++;
};
consumer.accept(local);
}
}
通过 javac LambdaTest.java 命令 获得 LambdaTest.class 文件
public class LambdaTest {
Integer instance = 1;
public LambdaTest() {
}
//因为 class 文件字段名称有变化 我将加以注释 方便对应 查看
private void test() {
Integer var1 = 2;
Consumer var2 = (var2x) -> {
// var2x -> 对应源代码 x 变量
System.out.println(var2x);
// var1 -> 对应源代码 local 变量
System.out.println(local);
//可以看到 操作成员变量是直接 通过this 引用的
// instance -> 对应源代码 instance 变量
System.out.println(this.instance);
Integer var3 = this.instance;
Integer var4 = this.instance = this.instance + 1;
};
var2.accept(var1);
}
}
通过 javap -p LambdaTest.class 反编译字节码
public class kma.LambdaTest {
java.lang.Integer instance;
public kma.LambdaTest();
private void test();
//此方法中 一个参数代表 上面的 x 一个参数代表代码中的 local 而 instance 变量是直接通过this 引用的
private void lambda$test$0(java.lang.Integer, java.lang.Integer);
//由此可看出,Lambda表达式是一个语法糖,会被编译生成为当前类的一个私有方法,
//Lambda表达式内直接引用局部变量本质是一种隐式传参,编译时会自动将引用的局部变量放到参数列表中(Lambda方法多了个参数)
//而引用的实例变量并不需要放到参数列表,因为方法内可以直接引用。
}
通过以上代码 参考class 文件 和反编译字节码 可以看出 Lambda表达式是一个语法糖,会被编译生成为当前类的一个私有方法,
引用局部变量本质是一种隐式传参,而引用的成员变量 是直接方法内引用
- 看下内部类 的存在方式 同样以上面代码案例显示
public class InnerTest {
Integer instance = 1;
private void test() {
// 用于直接引用
Integer local = 2;
// 用于传参
Consumer<Integer> consumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
//引用 局部变量
System.out.println(local);
//引用 成员变量
System.out.println(instance);
//操作成员变量
instance++;
}
};
}
}
通过 javac InnerTest.java 命令
可以看到 生成 两个 class 文件
InnerTest.class
InnerTest$1.class
InnerTest.class 文件内容
public class InnerTest {
Integer instance = 1;
public InnerTest() {
}
private void test() {
final Integer var1 = 2;
Consumer var10000 = new Consumer<Integer>() {
public void accept(Integer var1x) {
System.out.println(var1);
System.out.println(InnerTest.this.instance);
Integer var2 = InnerTest.this.instance;
Integer var3 = InnerTest.this.instance = InnerTest.this.instance + 1;
}
};
}
}
InnerTest$1.class 文件内容
//通过InnerTest$1.class 可以看到 源代码中 对于内部类 是生成一个 额外的 class 文件 也可以理解为帮你自动建立一个类
// 因为内部类中引用了 成员变量 所以 这个类中将 InnerTest 作为 自己的成员变量 这样就可以操作 InnerTest 的成员变量 instance
class InnerTest$1 implements Consumer<Integer> {
//该类的构造方法需要传入 InnerTest 和 方法中的局部变量
InnerTest$1(InnerTest var1, Integer var2) {
this.this$0 = var1;
this.val$local = var2;
}
public void accept(Integer var1) {
// 参与 var1 对应对应源代码 integer 参数
// this.val$local -> 对应源代码 local 变量
System.out.println(this.val$local);
// this.this$0.instance -> 对应源代码 instance 变量
System.out.println(this.this$0.instance);
Integer var2 = this.this$0.instance;
Integer var3 = this.this$0.instance = this.this$0.instance + 1;
}
}
总结:
关于 匿名内部类 和 lambda表达式 在java 中 我们都可以认为只是一个 简单Template ,简化我们的开发难度
那么 ,通过以上内容 我们可以发现 :
-
对于内部类的实现 其实就是相当于 建了一新类 如果我们需要操作原类中的 成员变量 就会将原类。作为新类中的成员变量 引入进来, 从而达成可以操作原类中的成员变量 , 而局部变量 是通过拷贝而来的
-
对于lambada 表达式 会被编译生成为当前类的一个私有方法 引入的成员变量本身就是本类 可以直接通过this 调用 但是局部变量也是通过拷贝而来的
从上面来看 我我们理解了 为什么 内部类 和lambda为什么能过够对本类的成员变量进行操作
但是 为什么对局部变量不能操作呢
首先 我们可以发现 对于局部变量 都是以参数传递方式进行的 无论是调用自己类的 方法 还是其他类的方法 都是以参数传递形式传入 这就引身到 java 中 参数传递 是以什么样的形式调用了 这里简单概括下
按值调用(call by value) 对应基本数据类型
表示方法接收的是调用者提供的值。
按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。 对
一个方法可以修改传递 引用所对应的变量值,而不能修改传递值调用所对应的变量值。
也就是 如果传递的数据类型是个基本数据类型 那么后期对于这个值的改变 是不会影响到原来传递的值的
这里回到主题 为什么我们在 内部类 或者 lambda 表达式中 操作局部的基本数据变量 编译器会提示 这是 final 修饰的 不容许操作呢 或者说 为什么 编译器会默认加入 这个final 修饰符呢?
本人的理解是 , 通过以上的内容分析, 这一切都是java 编译器自己执行的效果, 它只是一个语法糖而已 ,原理还是操作额外的接口方法, 无论本类方法或者其他类方法, 但是, 在编写的时候 , 这种方式和展现会
误导开发人员, 让开发人员认为这中操作是可以改变传入的参数的 ,毕竟 非基本数据类型或者成员变量可以改变, 从而造成难度提升, 默认给局部变量 加上 final修饰 ,可以降低或者提醒开发人员 ,从而达到降低开发难度 这种强制提醒, 可以可以理解为 编译器无法保证所有开发人员都是大佬😄
参考: