深入了解final


深入了解final

 

 

 

參考:

final和volatile:

https://www.cnblogs.com/jhxxb/p/10944691.html

如何理解String類型值的不可變?

https://www.zhihu.com/question/20618891

 

 

final?

final深入

final對象初始化方式

final類修飾的引用類型變量是引用不可變,還是對象不可變

final類的實例引用也是final的?

為什么String類型值不可變

內存模型 final 和 volatile的內存語義

 

 

 

final?

final的本意是不可改變的,絕對性的,最終的。

final是JAVA中的一個關鍵字,創造的意圖在於中如果想聲明一個類不讓別人繼承,或某個方法不讓別人重寫,或某個變量不可改變,可以使用final修飾符修飾。

final修飾的變量稱為常量。

final修飾符:

修飾類:

改類不能有子類

修飾變量:

引用類型:

變量指向的對象地址[stack里存的是個地址]不可改變。

基本數據類型:

變量的值不能改變

修飾的變量一般大寫

修飾方法:

該方法不能被重新

 

 

final深入

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;
//    }
}

 

final靜態成員變量可以直接賦值

import com.sun.glass.ui.Size;
// final static直接賦值
public class TestFinal {
    private final static int age = 20;
    private final static Size size = new Size();
}

 

final靜態成員變量,可以在靜態代碼塊中賦值

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加載到方法區時就依舊初始化了的,是線程共享的,屬於類。構造代碼塊和構造函數是創建類的實例時才初始化的。

 

 

final類修飾的引用類型變量是引用不可變,還是對象不可變

針對引用類型的變量,對其初始化只會不能再讓其指向另一個對象。當一個對象被創建時,會在堆中開辟一小塊空間存放對象實例,並將該對象的地址copy一份存放到stack中的變量中,這個變量也稱為對象實例的引用。

final變量指向的對象實例如果被移動(回收)了呢?

上面說了引用類型變量只是copy堆里對象實例的地址,如果對象實例被移動,那當前的這個地址已經沒有數據了,所有引用類型變量持有的地址指向了一個空的內存空間,自始至終這個虛擬機棧里的引用地址都沒有被改變,因為他是final修飾的。

 

 

 

 

final類的實例引用也是final的?

final修飾類只是說明這個類不能被繼承,並不代表着使用final修飾的類的實例引用地址不可變!這句話有點繞,我們知道final修飾的引用形變量,該變量引用的地址不可改變,這里說的不可改變指的是虛擬機棧里面對象的引用地址,這個地址也就是copy堆里那個對象的地址。final類的實例和普通對象實例並沒有太大區別,實例和變量只是存在一種強引用關系,實例本身是怎樣的和變量並沒有太大關西,所以final修飾的類的實例變量是不是final取決於這個變量自身有沒有在前面添加final修飾。

 

 

 

為什么String類型值不可變

深入閱讀String類源碼:

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 和 volatile的內存語義

在構造函數內對一個final域的寫入,與隨后把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。

初次讀一個包含final域的對象的引用,與隨后初次讀這個final域,這兩個操作之間不能重排序。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM