https://www.cnblogs.com/jhxxb/p/10944691.html
https://www.zhihu.com/question/20618891
final是JAVA中的一個關鍵字,創造的意圖在於中如果想聲明一個類不讓別人繼承,或某個方法不讓別人重寫,或某個變量不可改變,可以使用final修飾符修飾。
private final int age = 20; // private final Size size = new Size();
// 如果使用這種方式需要在每一個構造函數里都對final成員變量賦值 import com.sun.glass.ui.Size; public class TestFinal { private final int age; private final Size size; public TestFinal() { age = 0; size = null; } // 如果解開注釋,定義的成員變量會編譯不通過 // public TestFinal(int age) { // // } public TestFinal(int age) { this(); } // public TestFinal(int age) { // this(); // // 不能重復賦值,編譯不通過 //// this.age = age; // } public TestFinal(int age, Size size) { this.age = age; this.size = size; } public void m() { final String ss = "asdadas"; } }
import com.sun.glass.ui.Size; // 構造代碼塊中的代碼會在構造函數之前執行 public class TestFinal { private final int age; private final Size size; { age = 20; size = new Size(); } // 在構造函數里重復賦值,編譯不通過 // public TestFinal(int age, Size size) { // this.age = 20; // } }
import com.sun.glass.ui.Size; // final static直接賦值 public class TestFinal { private final static int age = 20; private final static Size size = new Size(); }
import com.sun.glass.ui.Size; public class TestFinal { private final int sex; private final static int age; private final static Size size; { this.sex = 1; // static類型不能使用this // this.age = 20; } // 靜態代碼塊作用在於類的初始化,只在類被JVM加載時執行一次,是給類初始化的 // 構造代碼塊是給對象初始化的 // 在這里也可以直接對final static成員變量做修飾 static { age = 20; size = new Size(); } public void m() { System.out.println(age); } }
靜態變量 > 靜態代碼塊 > 構造代碼塊 > 構造函數
靜態變量和靜態代碼塊是在類被JVM加載到方法區時就依舊初始化了的,是線程共享的,屬於類。構造代碼塊和構造函數是創建類的實例時才初始化的。
針對引用類型的變量,對其初始化只會不能再讓其指向另一個對象。當一個對象被創建時,會在堆中開辟一小塊空間存放對象實例,並將該對象的地址copy一份存放到stack中的變量中,這個變量也稱為對象實例的引用。
上面說了引用類型變量只是copy堆里對象實例的地址,如果對象實例被移動,那當前的這個地址已經沒有數據了,所有引用類型變量持有的地址指向了一個空的內存空間,自始至終這個虛擬機棧里的引用地址都沒有被改變,因為他是final修飾的。
final修飾類只是說明這個類不能被繼承,並不代表着使用final修飾的類的實例引用地址不可變!這句話有點繞,我們知道final修飾的引用形變量,該變量引用的地址不可改變,這里說的不可改變指的是虛擬機棧里面對象的引用地址,這個地址也就是copy堆里那個對象的地址。final類的實例和普通對象實例並沒有太大區別,實例和變量只是存在一種強引用關系,實例本身是怎樣的和變量並沒有太大關西,所以final修飾的類的實例變量是不是final取決於這個變量自身有沒有在前面添加final修飾。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[];
String類是一個構造極其嚴謹的類,首先String類是被final修飾的,說明String類不可被繼承(使用繼承方式改變想法失敗)。
String類的成員變量value也就是我們在使用中賦值的變量value是個char數組,而且是final修飾的,final修飾的變量一旦賦值引用地址就不可在改變,也就是說value不能在指向其他字符數組。(final修飾不能改變引用地址)
雖然value不能指向其他數據,但是value是一個對象,final修飾的引用型變量地址不可變,但對象內部的屬性等可以改變,但是仔細看看,priavet final chat value[]; private的私有訪問權限的作用都比final大,value被設置成不允許外界訪問,而且在String類的方法里沒有去動Array里的元素,沒有暴露內部成員字段。(改變引用地址指向數組的元素想法失敗)
來個例子:System.arraycopy是淺復制,淺復制是指對對象引用的復制,深復制是指重新開辟空間復制值和對象內容。arraycopy是native靜態方法,對於一維數組來說,如果一維數組存放的不是對象,則直接復制值,如果是對象的話,復制結果是一維的引用變量傳遞給副本的一維數組,修改副本會影響原來的數組。對於二維數組數組賦值的是引用。
String[] s1 = new String[]{"Hi", "Hi", "Hi"}; String[] s2 = new String[s1.length]; System.arraycopy(s1, 0, s2, 0, s1.length); s1[1] = "Hier"; System.out.println(Arrays.toString(s1)); // output:[Hi,Hier,Hi] // 期待輸出:[Hi,Hier,Hi] System.out.println(Arrays.toString(s2)); // 實際輸出:[Hi,Hi,Hi]
修改后:由於String值是不可變,所以重新開辟的新的空間,s1[1]里存放的引用地址發生了改變,而s2里的s2[1]依舊存放的是之前的地址。
同樣的問題還會出現在自動裝箱類,用int型為Integer對象賦值時,實際是創建了新對象(這點自動裝箱和String有點不同,只有[-128,127]區間的數字,虛擬機才會緩存)。
Integer i = 1000; Integer j = 1000; System.out.println(i == j); // output:false Integer a = 10; Integer b = 10; System.out.println(a == b); // output:true
在構造函數內對一個final域的寫入,與隨后把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。
初次讀一個包含final域的對象的引用,與隨后初次讀這個final域,這兩個操作之間不能重排序。