Chapter 7 複用類

final關鍵字

java中的final關鍵字通常是指它所修飾的元素“是無法改變的”。而根據它所修飾的元素的不同,所起的作用存在着細微的區別。下面就討論可能用到final的三種情況:數據、方法和類。

 

final數據

final修飾變量本身並不複雜,就是變量一經初始化就不能再改變(如果是基本數據類型,就是其數值不可以改變;如果是引用類型,就是其不可以再重新指向其他對象)。

關於final成員變量的初始化——總之一句話:就是在構造方法調用結束之前完成final成員變量的初始化。

很多文章都這麼說——其初始化可以在兩個地方,一是其定義處,二是在構造方法中,兩者只能選其一。這種說法是不正確的,final變量可以在任何可以被初始化的地方初始化,但只能被初始化一次。一旦被初始化後就不能再次賦值。作爲成員變量一定要顯式初始化,而作爲臨時變量則可以只定義不初始化(當然也不能引用)——既空白final。下面是final成員變量初始化的小例子:

package com.ygc;

import java.util.Random;

/**
 * final成員變量初始化的幾種情況, 可以在定義處初始化,可以在語句塊中初始化,可以在構造方法中初始化。
 * 可以採用隨機值進行初始化,甚至可以使用方法進行初始化,只要初始化在構造方法結束前完成,並且只初始化一次就可以。
 * */
public class FinalFieldInitialization {
	Random rand = new Random(47);
	/**
	 * 可以在定義時初始化, 可以使用隨機值初始化
	 * */
	final int finalIntegerValue = 1;
	final int randomFinalIntegerValue = rand.nextInt();
	final String finalReference = "final reference";

	/**
	 * 可以在語句塊中初始化
	 * */
	final int finalIntegerValue1;
	final int randomFinalIntegerValue1;
	final String finalReference1;
	{
		finalIntegerValue1 = 1;
		randomFinalIntegerValue1 = rand.nextInt();
		finalReference1 = "final reference";
	}

	/**
	 * 可以在構造方法中初始化, 有多少個構造方法就得初始化多少次
	 * */
	final int finalIntegerValue2;
	final String finalReference2;

	public FinalFieldInitialization() {
		finalIntegerValue2 = 1;
		finalReference2 = "final reference";
	}

	public FinalFieldInitialization(int i) {
		finalIntegerValue2 = 1;
		finalReference2 = "final reference";
	}

	/**
	 * 使用方法初始化
	 * */
	public FinalFieldInitialization(String s) {
		finalIntegerValue2 = 1;
		finalReference2 = getString();
	}

	private String getString() {
		return "final refrence";
	}

}


final修飾方法參數

final修飾方法參數的效果也是一樣的,如果修飾的是基本數據類型,則不可以修改它的值;而如果修飾的是引用類型,則不能重新指向其它對象,但對象本身是可以改變的。而final修飾方法參數最常用的目的就是供匿名內部類使用,而且如果想要在匿名內部類中使用局部變量或者方法參數,則必須使用final修飾,而使用外部類的成員變量則不需要使用final修飾。因爲當你在匿名內部類中使用局部變量或方法參數時,編譯器會把它傳遞給匿名內部類的構造方法,然後隱式地對它做一個拷貝,使它成爲匿名內部類的一個成員變量,而如果這個局部變量或方法參數不是final的,那麼他們在匿名內部類的內部或外部就可以被修改(如果是基本數據類型 就可以修改它們的數值;而如果是引用類型,可以修改它們所指向的對象),一旦對這個變量進行了修改,就會導致外部變量和內部變量的不一致,從而產生一些不可預測的錯誤。爲了避免這種情況的發生,匿名內部類在引用局部變量或方法參數時必須使用final修飾。而使用成員變量時,編譯器會隱式地給匿名內部類傳入一個外部類的this引用 ,而這個this引用是final的,所以匿名 內部類可以隨意地訪問外部類的成員。下面是一個小例子:

	private String enclosingClassField;
	private void updateStatus() {
		final StringBuffer stringBuffer = new StringBuffer();
		Runnable doUpdateStatus = new Runnable() {
			public void run() {
				// 你可以使用FinalFieldInitialization.this, 因爲它永遠都是final的
				FinalFieldInitialization.this.enclosingClassField = "";
				// 下面這種寫法是簡便的寫法,因爲每一個內部類在實例化的時候,編譯器都會隱式地幫它們添加一個外部類的this引用
				enclosingClassField = "";
				// 你不可以改變stringBuffer的值,但是你可以修改它指向的對象的內容
				stringBuffer.append("hello final");
			}
		};
	}

上述說法可以通過查看字節碼得到 驗證,有興趣的同學可以研究一下。

final方法

final修飾方法的第一個原因是把方法鎖定,以防止任何繼承類修改它的定義。既,用final修飾的方法不可以重寫。

final修飾方法的第二個原因是爲了提高效率,在早期的java實現中會起到一定的作用,現在不建議使用,因爲使用final修飾方法並不會得到性能的顯著提升。所以只有當你確定你的方法不想被重寫時,才應該考慮用final修飾。

類中所有private修飾的方法都隱式地被指定爲final的。因爲在繼承類中無法訪問private方法,所以也無法覆蓋。如果你試圖重寫一個父類中的private修飾的方法,實際上你只是定義了一個新的方法。

class WithFinals {
    // Identical to "private" alone:
    private final void f() {
        print("WithFinals.f()");
    }

    // Also automatically "final":
    private void g() {
        print("WithFinals.g()");
    }
}
class OverridingPrivate extends WithFinals {
    private final void f() {
        print("OverridingPrivate.f()");
    }

    private void g() {
        print("OverridingPrivate.g()");
    }
} 
class OverridingPrivate2 extends OverridingPrivate {
    public final void f() {
        print("OverridingPrivate2.f()");
    }

    public void g() {
        print("OverridingPrivate2.g()");
    }
}
public class FinalOverridingIllusion {
    public static void main(String[] args) {
        OverridingPrivate2 op2 = new OverridingPrivate2();
        op2.f();
        op2.g();
        // You can upcast:
        OverridingPrivate op = op2;
        // But you can't call the methods:
        // ! op.f();
        // ! op.g();
        // Same here:
        WithFinals wf = op2;
        // ! wf.f();
        // ! wf.g();
    }
}

 

“覆蓋”只有在某方法是基類的接口的一部分時纔會出現。即,必須能將一個對象向上轉型爲它的基本類型並調用相同的方法。如果某方法爲private,它就不是基類接口的一部分。它僅是一些隱藏於類中的程序代碼,只不過具有相同的名稱而已。但如果在導出類中以相同的名稱生成一個public、protected 或包訪問權限(package access)方法的話,此時你並沒有覆蓋該方法,你僅是生成了一個新的方法。

final 類

final修飾類的目的在於你不想有任何類從此類繼承。final類中的所有方法都隱式地是final的,因爲類無法被繼承,所以其中的方法也不會被重寫。但是final類中的域並不是final的,除非 顯示地設置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章