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