lambda內部類局部變量值爲什麼不能被修改如和強制修改

問題描述

當使用內部類時
內部類外面的局部變量不能被重複賦值否則會報錯 爲什麼?
在內部類裏面不能修改局部類外面定義的變量的值 爲什麼?
內部類使用局部變量報錯圖片

原因分析

想知道上面的原因需要知道編譯器是怎麼編譯內部類的

原理

  • 內部類對象創建規則

    • lambda內部類
      根據javap -p反編譯可以發現會生成方法 private static void lambda$test$0(int);
      運行時會生成字節碼創建對象
    • 匿名內部類
      編譯器掃描到一個內部類時會在該類同包下創建一個class字節碼文件
  • 命名規則

    • lambda內部類
      外部類名稱lambda/ com.nailsoul.config.RedisConfiglambda內部類自增序號/隨機值 如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修飾 常規修改肯定會報錯 
通過反射可以修改 但是完全沒必要 因爲修改了沒作用對局部變量沒任何影響

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章