1. 程式人生 > >Java中final修飾的資料

Java中final修飾的資料

目錄

  • Java中final修飾的資料
    • 有初始值的final域
      • final+基本資料型別
      • final+引用資料型別
      • final與static final
    • 空白final域
    • final修飾的引數
      • 基本資料型別的引數
      • 引用資料型別的引數

Java中final修飾的資料

final是Java中的一個重要關鍵字,它可以修飾資料、方法和類,本篇將從final修飾的資料角度對final做出總結。

final修飾的資料代表著:永遠不變。意思是,一旦你用final修飾一塊資料,你之後就只能看看它,你想修改它,沒門。
我們不希望改變的資料有下面兩種情況:

  • 永不改變的編譯時常量。
//編譯時知道其值
private final int valueOne = 9;
  • 在執行時(不是編譯時)被初始化的值,且之後不希望它改變。
//在編譯時不能知道其值
private final int i4 = rand.nextInt(20);

設定成常量有啥好處呢?
很簡單,讓編譯器覺得簡單,就是最大的好處。比如把PI設定成final,且給定值為3.14,編譯器自然會覺得這個東西不會再被修改了,是足夠權威的。那麼,編譯器就會在執行之前(編譯時)就把這3.14代入所有的PI中計算,這樣在真正執行的時候,速度方面當然會快一點。

有初始值的final域

即宣告為final且當場就給定初始值的域。

 private final int valueOne = 9;

final+基本資料型別

final修飾的基本資料型別變數儲存的數值永恆不變。

/*基本型別變數*/
//帶有編譯時數值的final基本型別
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
public static final int VALUE_THREE = 39;
//!false:fd1.valueOne++;
//!false:fd1.VALUE_TWO++;
//!false:fd1.VALUE_THREE++;

康康上面醒目的三句false語句,很好地印證了我們之前說的:數值不讓改!!!
需要注意的是,按照慣例,下面是定義常量的典型方式:

//典型常量的定義方式
public static final int VALUE_THREE = 39;
  • public修飾符使其可被用於包之外。
  • static使資料只有一份。
  • final表示其無法被更改
  • 名稱全為大寫英文字母,以下劃線隔開。

final+引用資料型別

我們之前說過,基本型別存數值,引用型別存地址值。那麼既然final+基本資料型別不讓改數值,聰明的我們稍微一聯想就明白,final+引用資料型別就是不讓你改變數儲存實際物件的地址值啦。(也就是不能再讓它指向新的物件,很專一)

private Value v1 = new Value(1);
private final Value v2 = new Value(22);
private static final Value V_3 = new Value(333);
//引用變數並不是常量,儲存地址可以改變
fd1.v1 = new Value(10);

//v2是引用變數,final修飾之後表示地址不能改變,但是實際物件的值是可以改變的
fd1.v2.i++;
//!false:fd1.v2 = new Value(3);

//V_3與v2類似,是靜態和非靜態的區別,下面會說明
fd1.V_3.i++;
//!false:fd1.V_3 = new Value(10);
}

通過例子,確實也證明上面所說,一個以final修飾的引用資料型別變數,無法再指向一個新的物件,因為它所儲存的地址值已經無法被更改,但是並不影響它指向的實際物件。就拿一個比較典型的引用型別來舉例,我們知道陣列就是一種典型的引用型別,陣列的引用變數儲存的是陣列再堆中的地址,堆中存放的就是陣列每個索引的數值。

/*引用變數之陣列*/
private final int[] a = {1,2,3,4,5,6};

引用變數a被指定為final,所以它裡面的地址值不能再改,也就無法再讓它指向一個新的陣列。

//!false:fd1.a = new int[]{2,3,4,5,6,7};
for (int i = 0; i < fd1.a.length; i++) {
    fd1.a[i]++;

但是,它指向的數組裡的每個元素卻可以改動,因為陣列中的元素並沒有任何的限定。

final與static final

private final int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
System.out.println(fd1);//fd1: i4 = 15,INT_518

FinalData fd2 = new FinalData("fd2");
System.out.println(fd2);//fd2: i4 = 13,INT_518
FinalData fd3 = new FinalData("fd3");
System.out.println(fd3);//fd3: i4 = 1,INT_5 = 18
  • 上面示例分別建立了三個不同的物件,對其final 和final static 進行測試。
  • 需要明確的是,兩者都以final修飾,都不能被改變。
  • 三個物件的i4值,沒有用static修飾,不相同且不能改變。
  • 而INT_5的值因為被static修飾,在類載入時已經被初始化,不隨物件改變而改變。

空白final域

即宣告為final卻沒有給定初始值的域。

private final String id;//空白final

如果只有上面的這句,編譯器會報錯,因為它沒有初始化。

Variable 'id' might not have been initialized

所以,若定義了空白final域,一定記得在構造器中給它賦值!(必須在域的定義處或者每個構造器中以表示式對final進行賦值,因為系統不會為final域預設初始化)

//在構造器中為空白final域賦初值
public FinalData(){
    id = "空白final預設id";
}
public FinalData(String id){
    this.id = id;
}

不要試圖在初始化之前訪問域,不然會報錯。

final讓域可以根據物件的不同而不同,增加靈活性的同時,又保留不被改變的特性。

final修飾的引數

基本資料型別的引數

類似地,就是傳入的引數不讓改,只讓讀,這一點很好理解。

public int finalParamTest(final int i){
    //!false:i++;
    //不讓改,只讓讀
    return i+1;
}

但是,我又新增了許多測試,分別定義四種不同的引數傳入該方法,發現傳入param0和param1編譯會報錯。(非常疑惑,這部分書上沒提,查了許多資料也沒有理解清楚,希望大牛可以評論區指點迷津)

/*檢測傳入引數*/
int param0 = 5;
final int param1 = 10;
static final int PARAM_2 = 15;
static int param3 = 20;
//!false:System.out.println(fd1.finalParamTest(param0));
//!false:System.out.println(fd1.finalParamTest(param1));
//non-static field'param1' cannot be referenced from a static context
System.out.println(fd1.finalParamTest(PARAM_2));
System.out.println(fd1.finalParamTest(param3));
/*為什麼形參列表裡的引數用final修飾,但是用final修飾的param1無法傳進去,
一定要static修飾?*/

引用資料型別的引數

public void finalReferenceTest(final FinalData fd){
    //!false:fd = new FinalData();
    //不能再指向新的物件,儲存地址不準變
    fd.param0++;
}

還是類似,不可以讓這個引用型別的引數再指向新的物件,但是可以改變其實際指向物件的值。

最後的最後,下面的程式碼是根據《Thinking in Java》中的示例,結合自己的思想,將各個板塊融合而成的超級無敵測試程式碼,沖沖衝!

package com.my.pac16;

import java.util.Arrays;
import java.util.Random;

/**
 * @auther Summerday
 */

class Value{
    int i;//package access
    public Value(int i){
        this.i =i;
    }

}
/*final域在使用前必須被初始化:定義時,構造器中*/
public class FinalData {
    /*檢測傳入引數*/
    int param0 = 5;
    final int param1 = 10;
    static final int PARAM_2 = 15;
    static int param3 = 20;
    private static Random rand = new Random(47);
    private final String id;//空白final
    public FinalData(){
        id = "空白final預設id";
    }
    public FinalData(String id){
        this.id = id;
    }
    //帶有編譯時數值的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(1);
    private final Value v2 = new Value(22);
    private static final Value V_3 = new Value(333);

    private final int[] a = {1,2,3,4,5,6};
    @Override
    public String toString(){
        return id+": "+"i4 = "+i4+",INT_5 = "+INT_5;
    }
    public int finalParamTest(final int i){
        //!false:i++;
        //不讓改,只讓讀
        return i+1;
    }
    public void finalReferenceTest(final FinalData fd){
        //!false:fd = new FinalData();
        //不能再指向新的物件,儲存地址不準變
        fd.param0++;

    }

    public static void main(String[] args) {
        FinalData fd1 = new FinalData("fd1");
        /*基本型別變數*/
        //!false:fd1.valueOne++;
        //!false:fd1.VALUE_TWO++;
        //!false:fd1.VALUE_THREE++;
        /*引用變數*/
        fd1.v1 = new Value(10);
        fd1.v2.i++
        //!false:fd1.v2 = new Value(3);
        System.out.println("fd1.v2.i = [" + fd1.v2.i + "]");

        //!false:fd1.V_3 = new Value(10);
        fd1.V_3.i++;
        System.out.println("fd1.V_3.i = [" + fd1.V_3.i + "]");
        /*引用變數之陣列*/
        System.out.println("before:fd1.a[] = " + Arrays.toString(fd1.a));

        /*陣列引用變數a是final修飾,
        但是不代表它指向的資料值是final,
        而是a儲存的地址值不能改變
         */
        //!false:fd1.a = new int[]{2,3,4,5,6,7};
        for (int i = 0; i < fd1.a.length; i++) {
            fd1.a[i]++;
        }
        System.out.println("after :fd1.a[] = " + Arrays.toString(fd1.a));
        /*final 與static final*/
        //下面示例分別建立了三個不同的物件,對其final 和final static 進行測試
        /*可以發現,三個物件的i4值是隨機生成且不能改變的,且不相同,
        而INT_5的值不隨物件改變而改變,因為被static修飾,在類載入時已經被初始化*/
        System.out.println(fd1);//fd1: i4 = 15,INT_518

        FinalData fd2 = new FinalData("fd2");
        System.out.println(fd2);//fd2: i4 = 13,INT_518
        FinalData fd3 = new FinalData("fd3");
        System.out.println(fd3);//fd3: i4 = 1,INT_5 = 18

        //!false:System.out.println(fd1.finalParamTest(param0));
        //!false:System.out.println(fd1.finalParamTest(param1));
        //non-static field'param1' cannot be referenced from a static context
        System.out.println(fd1.finalParamTest(PARAM_2));
        System.out.println(fd1.finalParamTest(param3));
        /*為什麼形參列表裡的引數用final修飾,但是用final修飾的param1無法傳進去,
        一定要static修飾?*/

        System.out.println("fd1.param0 = "+fd1.param0);
        fd1.finalReferenceTest(fd1);
        System.out.println("fd1.param0 = "+fd1.param0);
    }

}

文章如有理解錯誤或敘述不到位,歡迎大家在評論區加以指正。
參考書籍:《Thinking in Jav