問題描述
- 當使用內部類時
- 內部類外面的局部變量不能被重複賦值否則會報錯 爲什麼?
- 在內部類裏面不能修改局部類外面定義的變量的值 爲什麼?
原因分析
想知道上面的原因需要知道編譯器是怎麼編譯內部類的
原理
-
內部類對象創建規則
- lambda內部類
根據javap -p反編譯可以發現會生成方法 private static void lambda$test$0(int);
運行時會生成字節碼創建對象 - 匿名內部類
編譯器掃描到一個內部類時會在該類同包下創建一個class字節碼文件
- lambda內部類
-
命名規則
- lambda內部類
外部類名稱Lambda$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
- lambda內部類
-
內部類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修飾 常規修改肯定會報錯
通過反射可以修改 但是完全沒必要 因爲修改了沒作用對局部變量沒任何影響