lambda内部类局部变量值为什么不能被修改如和强制修改

内部类值无法修改原因

    • 问题描述
    • 原因分析
        • 原理
        • 分析
            • 内部类外面的局部变量不能被重复赋值否则会报错
            • 在内部类里面不能修改局部类外面定义的变量的值

问题描述

当使用内部类时
内部类外面的局部变量不能被重复赋值否则会报错 为什么?
在内部类里面不能修改局部类外面定义的变量的值 为什么?
lambda内部类局部变量值为什么不能被修改如和强制修改_第1张图片

原因分析

想知道上面的原因需要知道编译器是怎么编译内部类的

原理

  • 内部类对象创建规则

    • lambda内部类
      根据javap -p反编译可以发现会生成方法 private static void lambda$test$0(int);
      运行时会生成字节码创建对象
    • 匿名内部类
      编译器扫描到一个内部类时会在该类同包下创建一个class字节码文件
  • 命名规则

    • lambda内部类
      外部类名称 l a m b d a 内 部 类 自 增 序 号 / 随 机 值   如 c o m . n a i l s o u l . c o n f i g . R e d i s C o n f i g lambda内部类自增序号/随机值 如com.nailsoul.config.RedisConfig lambda/ com.nailsoul.config.RedisConfigLambda$1/1535128843
    • 普通内部类
      外部类名称$普通内部类自增序号/随机值 如com.nailsoul.config.RedisConfig$1

      通过下面代码来检查上面理论

      public void test(){
      int count = 1;
      Runnable l1 = ()->{ int t = count; };
      Runnable l2 = ()->{};
      Runnable r1 = new Runnable() {@Override public void run() { }};
         System.out.println(l1.getClass().getName()+"\n"+l2.getClass().getName()+"\n"+r1.getClass().getName());
      }
      

      结果

      com.nailsoul.config.RedisConfig$$Lambda$1/1535128843
      com.nailsoul.config.RedisConfig$$Lambda$2/1586270964
      com.nailsoul.config.RedisConfig$1
      
  • 内部类final字段创建

    • 会为普通内部类创建一个类型为外部类的final成员变量并为它赋值

      • lambda内部类只有访问了外部类的成员属性或成员方法时才会创建 并且为私有的
      • 普通内部类不管有没有访问直接创建 为包访问
    • 会为这个内部类访问的所有外部局部变量创建一个同类型的私有final成员变量并为它们赋值

      • 成员变量与成员方法通过外部类对象来访问 只会创建外部类对象final字段

    可以通过反射来检查上面的理论

    static int k1 = 2;
    int v1 = 3;
    public void test(){
        int tt = 1; String xx="few";
        Runnable r = new Runnable() {@Override public void run() {}};
        Runnable lr = ()->{ String s = tt + xx + k1; }; //访问了类变量
        Runnable lr2 = ()->{ String s = tt + xx + v1; }; //访问类成员变量
        printFields(r,"//r"); printFields(lr,"//lr");printFields(lr2,"//lr2");
    }
    private void printFields(Runnable r, String tag) {
        Field[] fields = r.getClass().getDeclaredFields();
        System.out.println(tag);
        for (Field field : fields) {
            System.out.println(field);
        }
    }
    

    结果

    //r
    final com.nailsoul.config.RedisConfig$1.this$0
    //lr
    private final int com.nailsoul.config.RedisConfig$$Lambda$1/1535128843.arg$1
    private final java.lang.String com.nailsoul.config.RedisConfig$$Lambda$1/1535128843.arg$2
    //lr2
    private final com.nailsoul.config.RedisConfig com.nailsoul.config.RedisConfig$$Lambda$2/1586270964.arg$1
    private final int com.nailsoul.config.RedisConfig$$Lambda$2/1586270964.arg$2
    private final java.lang.String com.nailsoul.config.RedisConfig$$Lambda$2/1586270964.arg$3
    
  • 总结

    局部变量和内部类成员final变量不是一个变量

    • 引用对象

      包含不可变对象 如String Integer 等
      局部变量和内部类成员final字段指定的引用地址一样
      即修改了任何一方的变量里面的属性 另一方也会被修改

    • 基本对象

      不包含包装对象 如Integer等
      内部类变量和局部变量都执行同一个字面常量值  不管怎么搞都不会互相影响

分析

内部类外面的局部变量不能被重复赋值否则会报错

通过上面的测试我们已经知道局部类中的变量和局部变量不是同一个变量

  • 如果为局部变量重新赋值会导致内部类和局部变量值不一致 容易造成误导

    java 可能不是应为该原因 只是我猜测 
    因为如果是这原因的话 在内部类被创建前局部变量被修改是安全的
    也有可能是为了方便 只要修改了值 就报错
    不一致推论如下

    • 如果先定义局部变量x为3在创建内部类task
    • 为task定义run方法为打印变量x
    • 这时候task内的x为3
    • 修改局部变量x为5
    • 调用task的run方法  打印出来的会是3
    • 反之 定义局部变量x为3 内部类修改成5 局部变量还是3

    验证

    public void test(){
        Integer x = 3;
        Runnable r = new Runnable() {
            @Override
            public void run() {
                int i = x;
                Class<? extends Runnable> aClass = getClass();
                Field field = ReflectionUtils.findField(aClass, "val$x");
                field.setAccessible(true);
                ReflectionUtils.setField(field,this,5);
            }
        };
        r.run();
        Field field = ReflectionUtils.findField(r.getClass(), "val$x");
        field.setAccessible(true);
        Object inner = ReflectionUtils.getField(field, r);
        System.out.println("inner:"+inner+",outer:"+x);
    }
    

    结果 因为内部类里的变量和局部变量不是同一个变量 
    所以修改内部类的变量的值 对局部变量没影响
    如果在内部类里修改x的value那就有影响了 
    因为它们指向的引用是同一个地址

    inner:5,outer:3
    
在内部类里面不能修改局部类外面定义的变量的值

经过前面发分析得知内部类里面的字段被final修饰 常规修改肯定会报错 
通过反射可以修改 但是完全没必要 因为修改了没作用对局部变量没任何影响

你可能感兴趣的:(JAVA)