final修飾符--Java學習筆記


final關鍵字可表示它修飾的類、方法、變量不可改變

final修飾成員變量

       成員變量隨着類/對象的初始化而初始化,此時系統爲該變量分配內存以及默認值。可以在定義時就指定初始值,或者在初始化塊、構造器中指定初始值。

       但final修飾的成員變量必須顯式指定初始值。一旦有了初始值,就不能被重新賦值。具體來說:1. 若果是類變量,必須在靜態初始化塊中指定初始值或者聲明類變量時指定初始值;2. 如果是實例變量,在非靜態初始化塊聲明該實例變量構造器三者之一中指定初始值。

       普通方法不爲final修飾的成員變量賦值,也不在普通方法中爲final成員變量指定初始值。

       Java允許通過方法來訪問final 成員變量,但這可能違背了final成員變量設計初衷:對於final成員變量,我們總希望能訪問到固定的、顯式初始化的值。

       下面的程序顯示final修飾成員變量的效果。

public class FinalVariableTest{
	// 定義成員變量就指定初始值,或在構造器或初始化塊中分配初始值
	final int a = 6;
	final String str;
	final int c;
	final static double d;
	{
		str = "Hello";
		a = 5;// cannot assign a value to final variable 'a'
	}
	static{
		d = 1.1;
	}
	
	/*
	下面這個ch不合法
	既沒有指定默認值,將來也沒有在初始化塊、構造器中指定初始值
	*/
	final char ch;
	d = 1.2; // cannot assign a value to final variable 'd'
	ch = 'a'; // 錯誤

	
}

       下面代碼展示了通過方法訪問未初始化的final變量。(實際不會這樣做)

public class FinalErrorTest{
	final int age;
	// variable 'age' might not have been initialized
	System.out.print(age); 
	
	printAge(); // 這類方法是合法的,將輸出0
	
	public void printAge(){
		System.out.println(age);
	}
	public static void main(String[] args){
	new FinalErrorTest();
	}
}

final修飾局部變量

       系統不會對局部變量進行初始化,因此在定義使用了final修飾的局部變量時是否指定默認值是可選的。下面的程序示範了final修飾局部變量、形參的場景。

public class FinalLocalVariableTest{
	public void test(final int a){
        a = 5; // cannot assign a value to final variable 'a'
    }
	public static void main(String[] args) {
        final String str = "hello";
        str="World";//cannot assign a value to final variable 'str'
        final int b;
        b = 2;
        b = 4;// variable 'b' might already have been assigned to
    }
}

final在修飾基本類型與引用類型的區別

       根據final修飾的變量不可改變不難得知,當final修飾基本類型變量時不可改變,但修飾引用類型變量時,引用的對象可以改變,引用的地址不變。
       下面的代碼將展示這一情形:final分別修飾數組和Person對象的情形。

class Person{
    private int age;

    public Person() {
    }

    public Person(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

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

public class FinalTest{
    
    public static void main(String[] args) {
        final int[] arr = {1,2,3,4};
        System.out.println(Arrays.toString(arr));
        Arrays.sort(arr);
        arr[2] = 100;
        arr = null; 
        // cannot assign a value to final variable 'arr'
        
        final Person person = new Person(34);
        person.setAge(12);
        System.out.println(person.getAge());
        person = null;
        // cannot assign a value to final variable 'arr'
    }
    
}

final與“宏替換”

  • 只要一個final變量滿足以下三個條件,那麼該變量就是一個直接量(“宏變量”):
    • 使用final修飾
    • 在定義時指定了初始值
    • 該初始值在編譯時就被確定下來

如爲final變量賦值時使用基本的算術表達式或字符串連接運算,沒有訪問普通變量,調用方法,Java將將這種final變量當作"宏變量",編譯器會把程序中所有用到該變量的地方直接替換成相對應的值。

示例1:

public class FinalTest{

    public static void main(String[] args) {
       final int a = 1 + 1;
       final String str = "Hello" + "World";
       final String str2 = "HelloWorld" + 100;
 
       final String str3 = "Hello World" + String.valueOf(100);
       // 調用了valueOf方法
       System.out.println(str2 == "HelloWorld100");// true
       System.out.println(str3 == "HelloWorld100");// false
    }

}

示例2:

public class FinalTest{

    public static void main(String[] args) {
        String helloWorld = "HelloWorld";
        String s = "Hello" + "World";
        System.out.println(helloWorld==s);// true
        String s1 = helloWorld + s;
        System.out.println(s==s1);// false
    
    }

}
    輸出爲 false 是因爲編譯器無法在編譯時確定 s1 ,
    不會讓 s1 指向先前常量池中的 "HelloWorld" .
    若要第二個輸出爲 true 
    只需要將 helloWorld 和 s 變成 final "宏變量"即可

具體關於字符串的比較見博客:String深入理解

final方法

如果不希望子類方法重寫父類的某個方法,則可以使用final修飾該方法。
例如Java提供的Object類裏有個final方法:getClass()

	@HotSpotIntrinsicCandidate
	public final native Class<?> getClass();

而對於這個類提供的toString()equals()方法,就沒有使用final修飾,允許我們重寫:

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
	}
public boolean equals(Object obj) {
        return (this == obj);
	}

如果試圖重寫final方法,將會引發編譯錯誤:

public class FinalMethod{
        public final void test(){}
}
    class Sub extends FinalMethod{
        public void test(){}
        /* 
        'test()' cannot override 'test()' in FinalTest.FinalMethod';
        overridden method is final
        */
    }

但是可以通過private修飾該方法,那麼其字類無法訪問該方法,即便在字類擁有一個與父類private方法中同名同參的方法,也只是重新定義了一個新方法:

public class FinalMethod{
        private final void test(){}
    }
    class Sub extends FinalMethod{
        public void test(){}  
    }

然而可以重載父類同名方法,不會出現任何問題.

final類

同樣地,final修飾的類不可被繼承,如果出現以下用法將報錯:

public final class FinalClass{}
class Sub extends FinalClass{}
// cannot inherit from final 'FinalTest.FinalClass'

參考資料:瘋狂Java

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