java 在匿名内部类和lambda表达式中 使用局部变量需要使用final 修饰

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表达式是一个语法糖,会被编译生成为当前类的一个私有方法,
引用局部变量本质是一种隐式传参,而引用的成员变量 是直接方法内引用

  1. 看下内部类 的存在方式 同样以上面代码案例显示
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 ,简化我们的开发难度
那么 ,通过以上内容 我们可以发现 :

  1. 对于内部类的实现 其实就是相当于 建了一新类 如果我们需要操作原类中的 成员变量 就会将原类。作为新类中的成员变量 引入进来, 从而达成可以操作原类中的成员变量 , 而局部变量 是通过拷贝而来的

  2. 对于lambada 表达式 会被编译生成为当前类的一个私有方法 引入的成员变量本身就是本类 可以直接通过this 调用 但是局部变量也是通过拷贝而来的

从上面来看 我我们理解了 为什么 内部类 和lambda为什么能过够对本类的成员变量进行操作
但是 为什么对局部变量不能操作呢

首先 我们可以发现 对于局部变量 都是以参数传递方式进行的 无论是调用自己类的 方法 还是其他类的方法 都是以参数传递形式传入 这就引身到 java 中 参数传递 是以什么样的形式调用了 这里简单概括下

按值调用(call by value) 对应基本数据类型
表示方法接收的是调用者提供的值。

按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。 对
一个方法可以修改传递 引用所对应的变量值,而不能修改传递值调用所对应的变量值。

也就是 如果传递的数据类型是个基本数据类型 那么后期对于这个值的改变 是不会影响到原来传递的值的

这里回到主题 为什么我们在 内部类 或者 lambda 表达式中 操作局部的基本数据变量 编译器会提示 这是 final 修饰的 不容许操作呢 或者说 为什么 编译器会默认加入 这个final 修饰符呢?

本人的理解是 , 通过以上的内容分析, 这一切都是java 编译器自己执行的效果, 它只是一个语法糖而已 ,原理还是操作额外的接口方法, 无论本类方法或者其他类方法, 但是, 在编写的时候 , 这种方式和展现会
误导开发人员, 让开发人员认为这中操作是可以改变传入的参数的 ,毕竟 非基本数据类型或者成员变量可以改变, 从而造成难度提升, 默认给局部变量 加上 final修饰 ,可以降低或者提醒开发人员 ,从而达到降低开发难度 这种强制提醒, 可以可以理解为 编译器无法保证所有开发人员都是大佬😄

参考:

  1. JAVA中方法的值传递
  2. java lambda 表达式的局部变量为什么必须是final修饰?
  3. fina 修饰 Java编译器设计思想,P7面试题
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值