java - final關鍵字
final是java的保留關鍵字,字面意思是”最終的、不可更改的”,對應到java的使用場景完全適用。
java的final關鍵字可以修飾類、方法、成員變量、局部變量、方法參數。接下來將會分別說明這些用法。
用法
修飾類
final修飾類,表示該類是不可被繼承的,該類已經足夠完整了。jdk源碼中的String
和Integer
等包裝類都是final的。如String
類的聲明
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
}
Integer
類的聲明
public final class Integer extends Number implements Comparable<Integer> {
}
聲明爲final的類如果被子類繼承,編譯期即會報錯。
修飾方法
final修飾方法,表示該方法不可被重寫。如果確認方法功能已經足夠完整,不需要子類重寫,可以在方法前加上該修飾符。如下例
public class A {
//該方法被聲明爲final
public final void aMethod() {
}
}
public class B extends A{
//該方法試圖重寫類A的方法,編譯期將會報錯,提示final方法不可被重寫
public final void Method() {
}
}
修飾成員變量
final修飾成員變量,表示該變量一旦被賦值就不能改變,final變量也稱爲java常量。
對於基本數據類型,表示該值本身不可被改變,對於指向對象的引用,表示該引用不可改變,但是引用指向的對象確是可以改變的。
final變量和其他普通的成員變量不太一樣,普通成員變量會初始化爲該類型的”零”值,例如:
public class Test {
//int型的"零值"是0
private int x;
private long y;
//對象引用的"零值"是null
private Long l;
private Object obj;
public void print() {
System.out.println(x);
System.out.println(y);
System.out.println(l);
System.out.println(obj);
}
public static void main(String[] args) {
Test test = new Test();
test.print();
}
}
將會打印出:
0
0
null
null
可以看出,int型默認爲0,long型默認也是0,對象引用類型的默認爲null。
如果成員變量用final來修飾,則必須對其進行初始化,否則編譯期即報錯,final變量有3種初始化方式:
直接賦值、構造函數、靜態初始化塊:
直接賦值
public class Test { //直接給final成員變量賦值 private final int x = 0; }
構建函數
public class Test { private final int x; public Test() { //構造函數初始化 x = 0; } }
靜態初始化塊
public class Test { private static final int x; static { //static final修飾的成員變量只能通過直接賦值或者靜態初始化塊賦值 x = 0; } }
final修飾的成員變量存儲在常量池中
修飾局部變量
final還可以修飾局部變量,final修飾的局部變量在使用前必須顯示初始化,如果該變量未被使用則不需要初始化。
同樣地,final修飾的局部變量一經賦值,就不能更改。
另外,方法中的局部內部類(包括匿名內部類)訪問方法的局部變量必須是final修飾的,例如:
public class Outer {
public Object method() {
//被局部內部類訪問的局部變量必須是final修飾的
final int k = 0;
class Inner {
void innerMethod() {
//此處引用了方法的局部變量
System.out.println(k);
}
}
return new Inner();
}
}
方法中的普通變量在線程退出該方法之後,局部變量k就消失了,但是方法method的返回值對該變量有引用,可能會訪問到不存在的變量。所以該變量在方法退出之後不能消失。那麼java是如何解決的呢?
我們知道內部類和外部類是如何通信的,內部類其實是持有外部類的引用Outer.this,通過這個引用可以訪問外部類的成員變量。方法中的局部內部類也是如此(方法中的內部類也有自己獨立的類文件)。
局部內部類如果想訪問方法的局部變量按理來說是沒法辦到的,但是爲了解決這個問題,java通過將方法中的局部變量傳遞給(通過局部類的構造函數)局部內部類並變成局部內部類的成員變量來實現的,以後局部內部類訪問方法的局部變量其實是在訪問自己的成員變量,也就是說局部內部類維護了方法局部變量的副本,既然是副本,那就有一致性的問題,如何保證局部變量的值和局部內部類中該變量副本的值是一樣的?利用final來保證,將變量用final修飾,禁止修改。
此時方法退出後,局部內部類仍然可以訪問到自己的成員變量(該變量是方法局部變量的副本),造成了局部內部類仍然可以訪問方法局部變量的假象。
修飾方法參數
方法參數其實也是局部變量,不再說明。
注意事項
java引入final關鍵字其中一個重要原因是效率,那麼爲何用final修飾後會提高效率呢?
編譯器遇到final方法時會採用內聯機制,節省了方法調用的開銷,但是如果方法體過大,編譯器可能不會採用內聯機制。
參考:
- Thinking in java
- http://blog.csdn.net/zhangjg_blog/article/details/19996629 (這篇分析內部類的文章非常好,分析的很透徹)