Java中final關鍵字


許多編程語言都有某種方法,來向編譯器告知一塊數據是恆定不變的。有時候數據的恆定不變是很有用的,例如:

  • 一個編譯時恆定不變的常量
  • 一個在運行時初始化,而你不希望它被改變的變量

對於編譯期常量的這種情況,編譯器可以將該常量值代入任何可能用到它的計算式中,也就是說,可以在編譯期就執行計算式,這減輕了一些運行時的負擔。在Java中,這類常量必須是基本類型的,並且以final表示,在對這個常量在定義時,或者構造函數中,必須進行賦值。

一個即是static又是final的域只占一段不能改變的存儲空間。

final數據

當final應用於對象引用時,而不是基本類型時,其含義讓人疑惑。對基本類型使用final不能改變的是他的數值。而對於對象引用,不能改變的是他的引用,而對象本身是可以修改的。一旦一個final引用被初始化指向一個對象,這個引用將不能再指向其他對象。java並未提供對任何對象恆定不變的支持。這一限制也通用適用於數組,它也是對象。

package com.bupt.java.test;

import java.util.Random;

class Value{
    int i;
    final int j;
    public Value( int i , int j){
        this.i = i;
        this.j = j;
    }
}

/*
 * 對基本類型使用final不能改變的是它的數值
 * 而對於對象引用,不能改變的是它的引用,而對象本身是可以修改的
 * 一旦一個final引用被初始化只想一個對象,這個引用不能再指向其他對象
 * */
public class FinalData {
    private static Random rand = new Random(47);
    private String id;
    public FinalData( String id){
        this.id = id;
    }
    
    //編譯時常量,根據慣例,既是static又是final的域將大寫表示
    private final int valueOne = 9;
    private static final int VALUE_TWO = 99;
    
    //典型的公共常量
    public static final int VALUE_THREE = 39;
    
    //運行時常量
    private final int i4 = rand.nextInt(20);
    static final int INT_5 = rand.nextInt(20);
    private Value v1 = new Value(11,22);
    private final Value v2 = new Value(22,33);
    private static final Value VAL_3 = new Value(33,44);
    
    //數組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++; //不能對終態字段 FinalData.valueOne 賦值
        fd1.v2.i++;
//        System.out.println(fd1.v2.i);
        //fd1.v2.j++; //不能對終態字段 Value.j 賦值
        fd1.v1 = new Value(12,23); // ok -- not final
        
        for ( int i = 0 ; i < fd1.a.length ; i++ ) {
            fd1.a[i]++; //Object isn't constant!
        }
        //fd1.v2 = new Value(23,34);//不能對終態字段 FinalData.v2 賦值
        //fd1.VAL_3 = new Value(34,45); //不能對終態字段 FinalData.VAL_3 賦值
        //fd1.a = new int[3];//不能對終態字段 FinalData.a 賦值
        System.out.println(fd1);
        System.out.println("Creating new FinalData");
        FinalData fd2 = new FinalData("fd2");
        System.out.println(fd1);
        System.out.println(fd2);
    }
}

運行結果:

fd1: i4 = 15, INT_5 = 18
Creating new FinalData
fd1: i4 = 15, INT_5 = 18
fd2: i4 = 13, INT_5 = 18

由於valueOne和VALUE_TOW都是帶有編譯時數字的final基本類型,所以它們二者均可以用作編譯期常量,並且沒有重大區別。

VALUE_THREE是一種更加典型的對常量進行定義的方式:定義為public,可以被任何人訪問;定義為static,則強調只有一份;定義為final,說明它是個常量。請注意被static和final修飾的變量全用大寫字母命名,並且之間用下划線隔開。

我們不能因為某些數據是final的就認為在編譯時可以知道它的值。在運行時使用隨機數來初始化i4和INT5時說明這一點。實例中fd1和fd2中i4分別被初始化為15和13,INT_5的值是不可以通過創建第二個FinalData實例加以改變的,這是因為INT_5是被static修飾的。

空白final

java也許生成“空白final”,所謂空白final是指被聲明為final但又未給初值的域。無論什么情況下編譯器都會保證final域在使用前初始化。但空白final在final的使用上提供了很大的靈活性,為此,一個final域可以根據某些對象有所不同,卻又保持恆定不變的特性。

package com.bupt.java.test;

class Poppet{
    private int i;
    public Poppet(int i){
        this.i = i;
    }
    public int getI() {
        return i;
    }
    public void setI(int i) {
        this.i = i;
    }
    
    
}
public class BlankFinal {
    private final int i = 0; //Initialized final
    private  final int j; //blank final
    private final Poppet p; // blank final reference
    
    public BlankFinal(){
        j = 1;
        p = new Poppet(1);
    }
    
    public BlankFinal( int x) {
        j = x;
        p = new Poppet(x);
    }
    
    public static void main(String[] args){
        BlankFinal b1 = new BlankFinal();
        BlankFinal b2 = new BlankFinal(47);
        
        System.out.println("b1.j="+b1.j+"\t\t b1.p.i="+b1.p.getI());
        System.out.println("b2.j="+b2.j+"\t\t b2.p.i="+b2.p.getI());
    }
}

運行結果:

b1.j=1         b1.p.i=1
b2.j=47         b2.p.i=47

final參數

java中也會將參數列表中的參數以聲明的方式指明為final。這意味着你無法改變參數所指向的對象。例如:

package com.bupt.java.test;

class Gizmo{
    public void spin(String temp){
        System.out.println(temp+" Method call Gizmo.spin()");
    }
}
public class FinalArguments {
    void with(final Gizmo g){
        //g = new Gizmo(); //不能對終態局部變量 g 賦值。它必須為空白,並且不使用復合賦值
    }
    
    void without(Gizmo g) {
        g = new Gizmo();
        g.spin("without");
    }
    
//    void f(final int i){
//        i++;
//    }不能對終態局部變量 i 賦值。它必須為空白,並且不使用復合賦值
    
    int g(final int i){
        return i + 1;
    }
    public static void main(String[] args){
        FinalArguments bf = new FinalArguments();
        bf.without(null);
        bf.with(null);
        System.out.println("bf.g(10)="+bf.g(10));
    }
}

運行結果:

without Method call Gizmo.spin()
bf.g(10)=11

使用final方法有兩個原因。第一個原因是把方法鎖定,以防止任何繼承它的類修改它的含義。這是出於設計的考慮:想要確保在繼承中使用的方法保持不變,並且不會被覆蓋。過去建議使用final方法的第二個原因是效率。在java的早期實現中,如果將一個方法指明為final,就是同意編譯器將針對該方法的所有調用都轉為內嵌調用。當編譯器發現一個final方法調用命令時,它會根據自己的謹慎判斷,跳過插入程序代碼這種正常的調用方式而執行方法調用機制(將參數壓入棧,跳至方法代碼處執行,然后跳回並清理棧中的參數,處理返回值),並且以方法體中的實際代碼的副本來代替方法調用。這將消除方法調用的開銷。當然,如果一個方法很大,你的程序代碼會膨脹,因而可能看不到內嵌所帶來的性能上的提高,因為所帶來的性能會花費於方法內的時間量而被縮減。在最近的java版本中,虛擬機(特別是hotspot技術)可以探測到這些情況,並優化去掉這些效率反而降低的額外的內嵌調用,因此不再需要使用final方法來進行優化了。事實上,這種做法正逐漸受到勸阻。在使用java se5/6時,應該讓編譯器和JVM去處理效率問題,只有在想明確禁止覆蓋式,才將方法設置為final的。

final和private關鍵字

類中的所有private方法都是隱式的制定為final的。由於你無法訪問private方法,你也就無法覆蓋它。可以對private方法添加final修飾詞,但這是毫無意義的。例如:

package com.bupt.java.test;

class WithFinals{
    private final void f(){
        System.out.println("WithFinals.f()");
    }
    
    private final void g(){
        System.out.println("WithFinals.g()");
    }
}

class OverridingPrivate extends WithFinals {
      private final void f() {
        System.out.println("OverridingPrivate.f()");
      }
      private void g() {
        System.out.println("OverridingPrivate.g()");
      }
    }
    class OverridingPrivate2 extends OverridingPrivate {
      public final void f() {
        System.out.println("OverridingPrivate2.f()");
      }
      public void g() {
        System.out.println("OverridingPrivate2.g()");
      }
    }
    
public class OverrideFinal {
    public static void main(String[] args){
        WithFinals w1 = new WithFinals();
//        w1.f(); //類型 WithFinals 中的方法 f()不可視,無法訪問私有方法
//        w1.g();//類型 WithFinals 中的方法 g()不可視,無法訪問私有方法
        OverridingPrivate w2 = new OverridingPrivate();
//        w2.f();
//        w2.g();
        OverridingPrivate2 w3 = new OverridingPrivate2();
        w3.f();
        w3.g();
    }
}

運行結果:

OverridingPrivate2.f()
OverridingPrivate2.g()

final類

當將類定義為final時,就表明你不打算繼承該類,而且也不允許別人這么做。換句話說,出於某種考慮,你對該類的設計不需要做任何改動,或者出於安全的考慮,你不希望他有子類:

package com.bupt.java.test;

class SmallBrain{
    
}
final class Dinosaur{
    int i = 7;
    int j = 1;
    SmallBrain x = new SmallBrain();
    void f(){
        System.out.println("Dinosaur.f()");
    }
}
//class a extends Dinosaur{
//    
//}類型 a 不能成為終態類 Dinosaur 的子類
public class Jurassic {
    public static void main(String[] args){
        Dinosaur n = new Dinosaur();
        n.f();
        n.i = 40;
        n.j++;
        System.out.println("n.i="+n.i);
        System.out.println("n.j="+n.j);
    }
}

運行結果:

Dinosaur.f()
n.i=40
n.j=2

由於final是無法繼承的,所以被final修飾的類中的發你過分都隱式的制定為final,以為你無法覆蓋他們。在final類中可以給方法添加final,但這不會產生任何意義。

總結

  1. final類不能被繼承,沒有子類,final類中的方法默認是final的,但是final類中的成員變量默認不是final的。
  2. final方法不能被子類覆蓋,但可以被繼承。
  3. final成員變量表示常量,只能被賦值一次,賦值后值不再改變。
  4. final不能用於修飾構造方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM