天天看點

回望Java中的final關鍵字

final關鍵字可以了解為“這個東西不能改變”。之是以要禁止改變,可能是因為實際業務情況需要或者效率或者設計因素,比如說應用中的全局變量我們經常将其用static加final關鍵字聲明保證不能改變且該類的所有對象隻有一份。在聲明為final的地方有成員變量、方法、類。

final成員變量:

往往我們在用final聲明為是常數資料的時候,可以分為編譯期的常數資料和運作期的常數資料。

什麼叫編譯期的常數資料?是指在程式在編譯期間已經将資料通過過程的形式存在了class檔案中了,這部分資料在不需要在運作期間去執行,相當于節省了一部分開銷,這類資料必須是屬于基本資料類型(boolean、byte、char、short、int、float、long、double)且必須賦予一個初始值這樣一經初始化就在編譯期間設定了不能再改變。這類變量的聲明我們習慣性的用大寫來命名。

public class FinalTest {
	public static final int A = 5;

	public static void main(String[] args) {
		//FinalTest.A = 5; 編譯不能通過 因為final聲明的資料一旦指定不能改變
	}
}
           

什麼叫運作期初始化的常數?是指該變量的初始化資料必須在運作期間才能被指定,一旦指定後就不會改變,在這裡包括基本資料類型的指定和對象句柄的指定。基本資料類型的指定後是不可改變的,對象句柄的指定後也是不可改變的,但他的不同點是該句柄始終指向到一個具體的對象,而且該句柄始終指向這一對象,但是這一對象本身是可以改變的。

class Student {
	private int age;
	public Student(int age){
		this.age = age;
	}
	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

}

public class FinalTest {
	public static final int A = 5;
	final int b = (int) (Math.random() * 4);
	static final int staticb = (int) (Math.random() * 4);
	final Student p = new Student(20);

	public static void main(String[] args) {
		FinalTest test = new FinalTest();
		FinalTest test2 = new FinalTest();
		System.out.println(test.b);//b在運作期被初始化,一旦初始化後就不會改變
		System.out.println(test2.b);//b在運作期被初始化,一旦初始化後就不會改變

		System.out.println(test.staticb);//staticb在運作期被初始化,一旦初始化後就不會改變
		System.out.println(test2.staticb);//staticb在運作期被初始化,一旦初始化後就不會改變 與b不同的是加了static關鍵字是以test2.staticb值與test.b值一樣
		
		//test.p = new Student(15);//編譯不通過 ,對象句柄的指定後也是不可改變的,該句柄始終指向到一個具體的對象(就是之前初始化的那個對象)
		System.out.println(test.p.getAge());
		test.p.setAge(21);//但是值是指向的對象本身是可以改變的
		System.out.println(test.p.getAge());
	}
}
           

在《thinking in Java》中的中文翻譯中這句話個人覺得是有問題的:無論static還是final字段,都隻能存儲一個資料,而且不得改變。

英文中是:A field that is both static and final  has only one piece of storage that   cannot be changed .

我的了解是隻有final聲明的字段是隻能存儲一個資料而且不得改變,事實上static單獨聲明的字段是可以改變資料的。英文的正确翻譯應該是“字段同時由static和final聲明後都隻能存儲一個資料,而且不得改變”。

final方法:

之是以要使用final方法,可能是出于對兩方面理由的考慮。第一個是為方法“上鎖”,防止任何繼承類改變它的本來含義。設計程式時,若希望一個方法的行為在繼承期間保持不變,而且不可被覆寫或改寫,就可以采取這種做法。

class FinalFather  {
	public final void say(String str){
		System.out.println(str);
	}
}

public class FinalTest extends FinalFather{
	public void say(String str){//編譯不通過
		System.out.println(str);
	}
}
           

 采用final方法的第二個理由是程式執行的效率。隻要編譯器發現一個final方法調用,就會(根據它自己的判斷)忽略為執行方法調用機制而采取的正常代碼插入方法(将自變量壓入堆棧;跳至方法代碼并執行它;跳回來;清除堆棧自變量;最後對傳回值進行處理)。相反,它會用方法主體内實際代碼的一個副本來替換方法調用。這樣做可避免方法調用時的系統開銷。當然,若方法體積太大,那麼程式也會變得雍腫,可能受到到不到嵌入代碼所帶來的任何性能提升。因為任何提升都被花在方法内部的時間抵消了。Java編譯器能自動偵測這些情況,并頗為“明智”地決定是否嵌入一個final方法。然而,最好還是不要完全相信編譯器能正确地作出所有判斷。通常,隻有在方法的代碼量非常少,或者想明确禁止方法被覆寫的時候,才應考慮将一個方法設為final。

final類:

如果說整個類都是final(在它的定義前冠以final關鍵字),就表明自己不希望從這個類繼承,或者不允許其他任何人采取這種操作。換言之,出于這樣或那樣的原因,我們的類肯定不需要進行任何改變;或者出于安全方面的理由,我們不希望進行子類化(子類處理)。

将類定義成final後,結果隻是禁止進行繼承——沒有更多的限制。然而,由于它禁止了繼承,是以一個final類中的所有方法都預設為final,但是成員變量預設是沒加的。因為此時再也無法覆寫它們。是以與我們将一個方法明确聲明為final一樣,編譯器此時有相同的效率選擇。

final class FinalFather  {
	//final int a = 5;
	int a = 5;
	public  void say(String str){//預設也是帶有final的
		System.out.println(str);
	}
}

//class FinalChild extends FinalFather{}//編譯不通過

public class FinalTest{
	public static void main(String[] args) {
		FinalFather f = new FinalFather();
		f.a=10;//如果把FinalFather中的a聲明為final則編譯不通過說明final類的成員變量是沒有加自動加上fianl的隻有方法自動加上了final限制
		System.out.println(f.a);
	}
}
           

設計一個類時,往往需要考慮是否将一個方法設為final。可能會覺得使用自己的類時執行效率非常重要,沒有人能覆寫自己的方法。這種想法在某些時候是正确的。但要慎重作出自己的假定。通常,我們很難預測一個類以後會以什麼樣的形式再生或重複利用。正常用途的類尤其如此,若将一個方法定義成final,就杜絕了在其他程式員的項目中對自己的類進行繼承的途徑。