【Java】6.4 final 修飾符

目錄

final 成員變量

final局部變量

 final修飾基本類型變量和引用類型變量的區別

可執行“宏替換”的final變量 

final方法

final類

不可變類

緩存實例的不可變類


【final】關鍵字可以用來修飾【類】、【變量】(包括成員變量和局部變量)、【方法】

final 成員變量

Java成員(Field)默認是可以由系統執行初始化,程序員可以不指定初始化。而final修飾過的成員變量【必須由程序員執行初始化】,final修飾變量 —— 該變量值只能賦值一次,不可改變。(原因見註釋1)

【注意】若final修飾實例變量,可以再如下三個地方爲final實例變量的初始值 —— 最多指定一次,不能多也不能少

  1. 定義時指定初始值
  2. 初始化塊
  3. 構造器
  4. 【添加】普通方法不能爲final修飾的成員變量賦值
  5. 【添加】final不會對成員變量進行隱式初始化

【注意】普通方法不能對final值修飾的實例變量賦值。若final修飾類變量,可以在如下2個地方爲final類變量指定初始值

  1. 定義時指定初始值
  2. 類初始化

實例變量不能在靜態初始化塊中指定初始值(原因見註釋2),同樣的類變量也不能在普通初始化塊中指定初始值

public class FinalVariableTest {
	// 定義成員變量時的初始值,合法
	final int a = 6;
	// 下面變量將在初始化塊或構造器中分配初始值
	final String str;
	final int c;
	final static double d;
	// 以上既沒有指定默認值,有沒有在初始化塊或構造器中指定初始化值
	// 下面定義的ch實例變量是不合法的
	// final char ch;
	// 初始化塊,可對沒有指定默認值的實例變量指定初始化值
	{
		// 在初始化塊中爲實例變量指定初始值
		str = "hello";
	}
	static {
		// 靜態初始化塊爲靜態變量指定初始值
		d = 55.56;
	}

	// 構造器中,可對既有沒有默認初始值,又沒有在初始化塊中指定初始值的實例變量指定初始值
	public FinalVariableTest() {
		c = 5;
	}

	public void changeFinal() {
		// 普通方法不能爲final修飾的變量賦值
		// 不能在普通方法中爲final成員變量指定初始值
		// d=1.2;
		// ch='a';
	}

	public static void main(String[] args) {
		FinalVariableTest ft = new FinalVariableTest();
		System.out.println(ft.a);    //輸出6
		System.out.println(ft.c);     //輸出5
		System.out.println(ft.str);    //輸出hello
		System.out.println(FinalVariableTest.d);     //輸出55.56
	}
}

final局部變量

系統不會對局部變量初始化,所以需要有程序員顯式初始化。在final修飾的局部變量中

  1. 可以在定義時就賦值
  2. 也可以在後面代碼中對final修飾的局部變量賦初始值
  3. 且賦值之後不可改變

 final修飾基本類型變量和引用類型變量的區別

當使用final修飾基本類型變量的時候,基本類型變量只能被賦值一次。但是當final修飾引用類型變量的時候,他保存的僅僅只是一個引用,final只能保證這個引用變量的地址不會被改變,即一直引用同一個對象。使用final修飾的引用類型變量不能被重新賦值,但是可以改變引用類新變量所引用對象的內容

import java.util.Arrays;

class Persons{
	private int age;
	public Persons(){
		
	}
	public Persons(int age) {
		this.age=age;
	}
	
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}
public class FinalReferenceTest {
	public static void main(String[] args) {
		//final修飾數組變量,iArr是一個引用變量
		final int[] iArr = {5,6,8,2};
		System.out.println(Arrays.toString(iArr));
		//對數組元素進行排序,合法
		Arrays.sort(iArr);
		System.out.println(Arrays.toString(iArr));
		//對於元素賦值,合法
		iArr[2]=-8;
		System.out.println(Arrays.toString(iArr));
		//下面語句對iArr重新賦值,非法
		//iArr = null;
		//final修飾Person變量,p是一個引用變量
		final Persons p = new Persons(45);
		//改變Person對象的age值,合法
		p.setAge(23);
		System.out.println(p.getAge());
		//下面語句對p重新賦值,非法
		//p = null;
	}
}

運行結果

可執行“宏替換”的final變量 

final是在編譯的時候就確定下來了。只要有變量,編譯的時候就確定不下來。對於一個final變量來說,無論它是什麼類型的變量,只要滿足以下三個條件,這個final就不再是一個變量,而是相當於一個直接量:

  1. 使用final修飾符修飾
  2. 在定義該final變量時指定了初始值
  3. 該初始值可以在編譯時就被確定下來

final方法

final修飾的方法不可被重寫,常用於不希望父類中的方法被子類重寫重寫。

Java提供Object類中就有一個final方法:getClass()

此外,在Java方法中,final和private一起使用時沒有意義(註釋3

public class FinalMethonTest{
	private void test() {
		
	}
}
class Sub extends FinalVariableTest{
    //會出現提示性錯誤
	@Override
	private void test() {
		//
	}
}

final類

final修飾的類不允許有子類。用於保護父類的內部數據和禁止重寫父類的方法。。

不可變類

不可變類是指創建該類的實例之後,該實例不可被改變。比如8個包裝類。 如果需要創建自定義的不可變類,需要遵守如下準則:

  1. 使用private和final修飾符來修飾類的成員變量
  2. 提供攜帶參數構造器,用於根據傳入參數來初始化類裏的成員變量
  3. 僅爲該類的成員變量提供getter()方法,不要爲成員變量提供setter方法,因爲普通方法不能修改final修飾的成員變量的值
  4. 若有必要,重寫Object類的hashCode()和equals()兩個方法。equals方法根據關鍵成員變量來作爲兩個對象是否相等的標準,除此之外,還應該保證用兩個equals方法判斷爲相等的hashCode方法也相等
class Name {
	private String firstName;
	private String lastName;

	public Name() {

	}

	public Name(String firstName, String lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

}

public class Person3 {
	private final Name name;

	public Person3(Name name) {
		// 設置name實例變量爲臨時創建的Name對象,該對象爲firstname和lastname
		// 與傳入的name參數的firstname和lastname相同
		 this.name = new Name(name.getFirstName(),name.getLastName());
		 //若換下面的語句,則可以對firstname做修改
		//this.name = name;
	}

	public Name getName() {
		 return new Name(name.getFirstName(),name.getLastName());
		//return name;
	}

	public static void main(String[] args) {
		Name n = new Name("悟空", "孫");
		Person3 p = new Person3(n);
		// Person對象的name的firstname爲:悟空
		System.out.println(p.getName().getFirstName());
		// 以下爲了改變Person對象的firstname值
		//無法改變值了,輸出還是悟空
		n.setFirstName("八戒");
		System.out.println(p.getName().getFirstName());
	}
}

緩存實例的不可變類

若經常使用不可變類,可以將不可變類緩存下來。

class CacheImmutale{
	private static int MAX_SIZE=10;
	//用數組緩存已有實例
	private static CacheImmutale[] cache = new CacheImmutale[MAX_SIZE];
	
	//記錄緩存實例在緩存中的位置,cache[pos-1]是最新的緩存實例
	private static int pos = 0;
	private final String name;
	private CacheImmutale(String name) {
		this.name=name;
	}
	
	public String getName() {
		return name;
	}
	
	public static CacheImmutale valueOf(String name) {
		//遍歷已緩存對象
		for (int i = 0; i < MAX_SIZE; i++) {
			//若存在兩個相同的實例,則直接返回該緩存實例
			if(cache[i]!=null&&cache[i].getName().equals(name)) {
				return cache[i];
			}
		}
		//若緩存池已滿
		if(pos==MAX_SIZE) {
			//把緩存的第一個對象覆蓋,即把剛剛生成的對象放在緩存池最開始的位置
			cache[0]=new CacheImmutale(name);
			//把pos設爲1
			pos=1;
		}else{
			cache[pos++]=new CacheImmutale(name);
		}
		return cache[pos-1];
	}
	
	public boolean equals(Object obj) {
		if(this==obj) {
			return true;
		}
		if(obj!=null&&obj.getClass()==CacheImmutale.class) {
			CacheImmutale ci= (CacheImmutale)obj;
			return name.equals(ci.getName());
		}
		return false;
	}
	
	public int hashCode() {
		return name.hashCode();
	}
	
}
public class CacheImmutaleTest {
	public static void main(String[] args) {
		CacheImmutale c1 = CacheImmutale.valueOf("hello");
		CacheImmutale c2 = CacheImmutale.valueOf("hello");
		System.out.println(c1==c2);	//輸出true
	}
}

 

註釋1若讓系統初始化,變量則會被自動賦予0/0.0/\u0000/false/null等值。final修飾的這也變量值不允許被改變,name這些就失去價值了

【註釋2】因爲靜態初始化塊是靜態成員,不可以訪問實例變量 —— 非靜態成員。

【註釋3】因爲private方法不能被子類中的實例訪問到,所以子類中即使有相同的名字、相同的形參列表、相同的返回值,那也只不過是定義了一個新的方法,不是重寫。同時final也是不讓子類重寫方法,所以兩者放在一起沒有意義

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