7.3 Java final 關鍵字詳解及應用
- 在不同環境中,Java 的關鍵字
final
含意存在細微的區別,通常指的是無法改變,可能出於兩種理由- 設計
- 效率
final data
- 許多語言都有某種方法告知編譯器某塊數據是恆定不變
- 永不改變的編譯時常數
- 運行時被初始化的值,而不希望被改變
- 永不改變的編譯時常數
- Java 的常數必須是 primitives,並且以關鍵字
final
表示,定義時必須對其賦值- 既是
static
又是final
的值表示佔據一段不能改變的儲存空間
- 既是
- 當
final
運用在物件引用而非 primitives 時,final
使其引用恆定不變,即被初始化指向一個物件後,就不能再指向另一個物件,但物件自身是可以被修改的- Java 並未提供使任何物件恆定不變的途徑 (除了自己實現)
- 這個限制同樣適用於陣列,因為它也是物件
class Value {
int i; // Package access
public Value(int i) { this.i = i; }
}
public class FinalData {
private static Random rand = new Random(47);
private String id;
public FinalData(String id) { this.id = id; }
// Can be compile-time constants:
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
// Typical public constant:
public static final int VALUE_THREE = 39;
// Cannot be compile-time constants:
private final int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
private Value v1 = new Value(11);
private final Value v2 = new Value(22);
private static final Value VAL_3 = new Value(33);
// Arrays:
private final int[] a = { 1, 2, 3, 4, 5, 6 };
public String toString() {
return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5;
}
public static void main(String[] args) {
FinalData fd1 = new FinalData("fd1");
//! fd1.valueOne++; // Error: can't change value
fd1.v2.i++; // Object isn't constant!
fd1.v1 = new Value(9); // OK -- not final
for(int i = 0; i < fd1.a.length; i++)
fd1.a[i]++; // Object isn't constant!
//! fd1.v2 = new Value(0); // Error: Can't
//! fd1.VAL_3 = new Value(1); // change reference
//! fd1.a = new int[3];
print(fd1);
print("Creating new FinalData");
FinalData fd2 = new FinalData("fd2");
print(fd1);
print(fd2);
}
} /* Output:
fd1: i4 = 15, INT_5 = 18
Creating new FinalData
fd1: i4 = 15, INT_5 = 18
fd2: i4 = 13, INT_5 = 18
*/
valueOne
及VALUE_TWO
都是帶有編譯時數值的基本類型,因此都可以用作編譯時常數VALUE_THREE
,定義為public
、static
、final
,是可以被外部使用的常數- 不能因為
final
定義就認為編譯期間就能知道其值,i4
及INT_5
都是執行期間才透過隨機數初始化 v2
由於是引用,final
意旨不能指向另一個物件,但仍可以改變內部的值
空白final
- Java 允許生成「空白
final
」,即聲明為final
但沒有給初始值的欄位 - 編譯器仍舊會確保空白
final
在使用前被初始化,空白final
讓使用上具有更大的靈活性
class Poppet {
private int i;
Poppet(int ii) { i = ii; }
}
public class BlankFinal {
private final int i = 0; // Initialized final
private final int j; // Blank final
private final Poppet p; // Blank final reference
// Blank finals MUST be initialized in the constructor:
public BlankFinal() {
j = 1; // Initialize blank final
p = new Poppet(1); // Initialize blank final reference
}
public BlankFinal(int x) {
j = x; // Initialize blank final
p = new Poppet(x); // Initialize blank final reference
}
public static void main(String[] args) {
new BlankFinal();
new BlankFinal(47);
}
}
final 參數 (final arguments)
- Java 允許在參數列表中將參數指定為
final
,意味著無法在方法內更改參數引用所指向的對象
class Gizmo {
public void spin() {}
}
public class FinalArguments {
void with(final Gizmo g) {
//! g = new Gizmo(); // Illegal -- g is final
}
void without(Gizmo g) {
g = new Gizmo(); // OK -- g not final
g.spin();
}
// void f(final int i) { i++; } // Can't change
// You can only read from a final primitive:
int g(final int i) { return i + 1; }
public static void main(String[] args) {
FinalArguments bf = new FinalArguments();
bf.without(null);
bf.with(null);
}
}
final 方法
- 使用
final
方法的原因有兩個:- 鎖定方法以防任何繼承類別修改它
- 效率考量,早期的 Java 如果將方法宣告為
final
則表示同意編譯器將所有針對該方法的調用轉為內聯(inline)調用- 最近版本的 JVM 已經可以探測並優化去掉因濫用內聯而導致效率下降的調用
- 在使用 Java SE5/6 時,應交由編譯器和 JVM 去處理效率問題,只在明確禁止覆蓋時才使用
final
- class 中的
private
方法都是隱式的final
,由於無法取用所以也無法覆蓋
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();
}
} /* Output:
OverridingPrivate2.f()
OverridingPrivate2.g()
*/
信息
“覆蓋” 只有在某方法是 base class 的接口的一部分才會出現,當某方法是 private
時並不會被視為接口的一部分,因此在衍生類別以相同名稱定義 public
、protected
或 package access 的方法並不算覆蓋該方法
final 類別
- 將整個 class 定義為
final
時,就表明不打算繼承該類別,也不允許別人這麼做,這可能是出於某種考慮(或許是安全性考量),該 class 的設計永不需要變動,也不希望有子類 final
class 的 field 可以自由選擇是或不是final
,然而,由於final
類別禁止繼承,所以 class 內所有方法都是隱式地指定為final
class SmallBrain {}
final class Dinosaur {
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f() {}
}
//! class Further extends Dinosaur {}
// error: Cannot extend final class 'Dinosaur'
public class Jurassic {
public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;
n.j++;
}
}