天天看點

關于final的一些事情

1.關于final的一些事情

一個Java關鍵字

1.當其一個類變量沒有加該修飾符的時候的一個效果:

private static int i = 847;

public static void main(String[] args) {
    System.out.println(i);
    i = 888;
    System.out.println(i);
}      

執行結果:

關于final的一些事情

這個結果應該都懂得,這裡就不解釋了。

當加上一個final之後,是一個什麼樣的效果呢?

private final static int i = 847;

public static void main(String[] args) {
    System.out.println(i);
    i = 888;
    System.out.println(i);
}      

顯而易見,連編譯都無法通過

關于final的一些事情
public static class Father {
    final static int i;

    static {
        i = 100;
    }
}      

但是此類方法卻不會報錯,因為雖然被修飾為了final,但并沒有被初始化,那麼是可以使用靜态方法對其進行初始化操作的。

那麼同理可得,構造方法,也是可以存在的。

public static class Father {
    final int i;
}      

但是上圖這種方式,會編譯異常,因為對于jvm來說,你沒有設計出構造方法來對于i進行初始化操作,那麼将會抛出異常;

并且,當你設計出的構造方法,沒有對類成員變量進行初始化操作的代碼塊的話,那麼同樣也會引起編譯報錯(總而言之,要想使用這個類,在類成員還沒進行初始化操作的前提下,都是不允許的,那麼類靜态成員也是一樣的)

public static class Father {
    final int i;

    public Father(int i) {
        this.i = i;
    }
}      

那麼對于靜态成員進行初始操作,會影響類的靜态代碼塊初始化嗎?順序是怎麼樣呢?咱寫個代碼分析一下:

第一步,不使用final,也就是不是常量;

public static void main(String[] args) {
    System.out.println(Father.i);
}

public static class Father {
    static int i;

    static {
        System.out.println("我是類靜态代碼塊");
    }

    public Father() {
        System.out.println("我是類無參構造代碼塊");
    }
}      
關于final的一些事情

先進行clean,清除所有編譯檔案,保持首次編譯,結果如下:

關于final的一些事情

可以看出,在通路類靜态成員變量的時候,類的靜态代碼塊也先進行了初始化操作,并且順序是在我們通路之前的,是以可以猜出類的加載機制,是先執行類的靜态成員變量,對于如果通路類中的靜态成員方法也是同理哈,也是先初始化類的靜态代碼塊,再執行類的靜态成員方法。

那麼重點來了,我們将i置為final,看看常量對類的加載機制有什麼影響;

public static void main(String[] args) {
    System.out.println(Father.i);
}

public static class Father {
    static final int i = 1;

    static {
        System.out.println("我是類靜态代碼塊");
    }

    public Father() {
        System.out.println("我是類無參構造代碼塊");
    }
}      

執行結果:

關于final的一些事情

奇了!!進行通路類的靜态常量成員的時候,沒有加載類的靜态方法代碼;

那麼就可以了解了,對于常量池的通路來說,并不會影響類的加載器,因為在通路i這個指向的内容的時候,并不會執行類加載器的操作。這也是常量池的優勢。

那麼如果類的靜态常量初始化的時候,并不是一個直接的常量值呢,而是new一個對象而進行的初始化呢?,需要執行類加載嗎?

public static void main(String[] args) {
    System.out.println(Father.i);
}

public static class Father {
    static final int i = new Integer(1);

    static {
        System.out.println("我是類靜态代碼塊");
    }

    public Father() {
        System.out.println("我是類無參構造代碼塊");
    }
}      

結果:

關于final的一些事情

結果很意外啊,這個時候,雖然加上了final關鍵字,類加載器又對類進行了加載操作。是以這種問題,可以這麼說,當你不去親身試驗一下,你根本不知道類的加載時機。

關于類的加載時機,還有很多隐式的操作,我們都可以去實踐一下,比如子類進行構造加載的時候,其實這個時候也是會對父類進行一個類加載的。(這裡就不說結果了,不然就偏離我們這個final的主題了)

public static void main(String[] args) {
    System.out.println(Son.i);
}

public static class Father {
    static final int i = new Integer(1);

    static {
        System.out.println("我是父類靜态代碼塊");
    }

    public Father() {
        System.out.println("我是父類無參構造代碼塊");
    }
}

public static class Son extends Father {
    static final int i = new Integer(1);

    void method1() {
    }

    static {
        System.out.println("我是子類靜态代碼塊");
    }

    public Son() {
        System.out.println("我是子類無參構造代碼塊");
    }

}      

2.那麼一個局部變量是什麼效果呢?

public static void main(String[] args) {
    final int i = 847;
    System.out.println(i);
    i = 888;
    System.out.println(i);
}      

結果:

關于final的一些事情

同樣的操作,也就是說,在編譯階段,這樣都是不允許的;

3.那麼作用在一個方法上是有什麼效果呢?

public class Father {
    final int i = 10;

    final void method1() {
    }
}

public class Son extends Father {
    void method1() {
    }
}      

結果:

關于final的一些事情

很明顯,編譯直接不通過,說明方法被修飾為final,不允許重寫

3.那麼再引申一下,如果作用在類上是一個什麼效果呢?

public final class Father {

}

public class Son extends Father {

}      

那麼這種操作是不合法的,直接會編譯不通過的。就不執行結果了。

那麼作用在類上是有什麼特殊含義嗎?在下面會總結一下;

4.那是不是加上了final就不可以改變這個對象的内容呢?

不一定,貼代碼

public static void main(String[] args) {
    final AtomicInteger i = new AtomicInteger(847);
    System.out.println(i);
    i.compareAndSet(847,1111);
    System.out.println(i);
}      

上述打出的結果

關于final的一些事情

很好了解哈,就是,上圖可以說,對象i指向了一個new AtomicInteger對象,那麼這個AtomicInteger對象又指向了一個 847 記憶體位址,那麼我們可以改變 這個AtomicInteger所指向的變量值,但是不能改變i對象所指向的對象,因為加了final修飾。

5.總結:

當類被一個final關鍵字所修飾的時候,辨別這個類不被繼承,那麼所帶來的作用是什麼用意呢?

可以了解為一種規範,當我們對一個類進行final進行修改的時候,那麼這個類就不允許被繼承,限制了程式上的操作,那麼類中的所有成員方法,也會隐式的設定為final,不允許重寫,但是成員變量不會,此處需注意;

那麼對于一個方法來說,其實加上了private也可以了解為,也是隐式的添加了final關鍵字,辨別該方法不别重寫;

那麼對于jvm層面有什麼積極意義呢?

對于類加載我們知道,在類加載的時候,需要維護類的上下級繼承關系,那麼對于如果加入了final的話,會有什麼影響呢?

對于final修飾的類是不能被繼承的,也就是不可派生。在jdk源碼中,有很多相關的例子,例如常用的String、System,整個類都是被修飾為final。這裡涉及到一個final的記憶體配置設定的機制;

方法内嵌:什麼是方法内嵌?抽象點了解,就是當我們确定了一個方法永遠不會發生改變的時候,那麼我們就可以了解為,他會直接把方法的代碼貼到方法調用方中,減少記憶體傳遞,減少使用者線程中的函數調用;因為我們知道該方法是怎麼實作的了,并且知道你不會改方法了。這樣有什麼好處呢?就是當我們進行循環之類的調用,就會不停的内嵌調用一個方法,那麼如果這個方法内容比較少,那麼這個時候,編譯器就會執行自動優化,發現該方法為final辨別,那麼就會将該方法的代碼塊直接内嵌到這個循環體中,那麼對于編譯成位元組碼的時候,直接編譯即可,就減少了方法間的調用情況。

但是可以這麼說,對于直覺上的執行效率的提升,并不明顯。

對于類來說,也是相似的原理,對于類的加載機制,對于jvm來說,他知道該類為一個不可繼承的類,那麼對于類之間的派生類,父類互相維持關系,互相初始化,那麼就沒有必要,jvm就知道進行對應的加載優化了。

這也是我對final自我認識,歡迎指正,批評