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之後,是一個什麼樣的效果呢?
private final static int i = 847;
public static void main(String[] args) {
System.out.println(i);
i = 888;
System.out.println(i);
}
顯而易見,連編譯都無法通過
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("我是類無參構造代碼塊");
}
}
先進行clean,清除所有編譯檔案,保持首次編譯,結果如下:
可以看出,在通路類靜态成員變量的時候,類的靜态代碼塊也先進行了初始化操作,并且順序是在我們通路之前的,是以可以猜出類的加載機制,是先執行類的靜态成員變量,對于如果通路類中的靜态成員方法也是同理哈,也是先初始化類的靜态代碼塊,再執行類的靜态成員方法。
那麼重點來了,我們将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("我是類無參構造代碼塊");
}
}
執行結果:
奇了!!進行通路類的靜态常量成員的時候,沒有加載類的靜态方法代碼;
那麼就可以了解了,對于常量池的通路來說,并不會影響類的加載器,因為在通路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的主題了)
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);
}
結果:
同樣的操作,也就是說,在編譯階段,這樣都是不允許的;
3.那麼作用在一個方法上是有什麼效果呢?
public class Father {
final int i = 10;
final void method1() {
}
}
public class Son extends Father {
void method1() {
}
}
結果:
很明顯,編譯直接不通過,說明方法被修飾為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);
}
上述打出的結果
很好了解哈,就是,上圖可以說,對象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自我認識,歡迎指正,批評