天天看點

Java關鍵字final學習(變量、方法、類,final域重排序語義介紹)

前言引入

final用于聲明屬性、方法和類,分别表示屬性不可變、方法不可覆寫和類不可被繼承

一、final關鍵字的基本用法

二、深入了解final關鍵字

三、final 域的記憶體語義(線程中)擴充

一、final關鍵字的基本用法

final屬性

對于一個final變量,如果是基本資料類型的變量,則其數值一旦在初始化之後便不能更改;如果是引用類型的變量,則在對其初始化之後便不能再讓其指向另一個對象。

舉例代碼

Java關鍵字final學習(變量、方法、類,final域重排序語義介紹)

:被final修飾的引用變量不可變。這裡的不可變有兩種含義:

  • 引用不可變
  • 對象不可變

舉例說明

public class Test {
	public static void main(String[] args) {
		final StringBuffer sb = new StringBuffer("Hello");
		sb.append(" world");
		System.out.println(sb);
	}
}
           

運作結果:

Java關鍵字final學習(變量、方法、類,final域重排序語義介紹)

舉例代碼:

Java關鍵字final學習(變量、方法、類,final域重排序語義介紹)
  • 從上面的例子可以看出,引用變量被final修飾之後,雖然不能再指向其他對象,但是它指向的對象的内容是可變的。final關鍵字指的是引用不可變性,即它隻能指向初始化時指向的那個對象,而不關心指向指向對象内容的變化。是以,被final修飾的變量必須初始化。

final變量初始化的幾種方式

  • 在定義的時候初始化 ;
  • final變量可以在初始化塊中初始化,但不可以在靜态初始化塊中初始化 ;
  • 靜态final成員變量可以在靜态初始化塊中初始化,但不可在初始化塊中初始化;
  • 在類的構造器中初始化,但靜态final成員變量不可在構造函數中初始化

final 方法

下面這段話摘自《Java程式設計思想》第四版第143頁:

“使用final方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修改它的含義;第二個原因是效率。在早期的Java實作版本中,會将final方法轉為内嵌調用(inline)。但是如果方法過于龐大,可能看不到内嵌調用帶來的任何性能提升。在最近的Java版本中,不需要使用final方法進行這些優化了。“

是以,如果隻有在想明确禁止 該方法在子類中被覆寫的情況下才将方法設定為final的。

final類

當用final修飾一個類時,表明這個類不能被繼承。也就是說,如果一個類你永遠不會讓他被繼承,就可以用final進行修飾。final類中的成員變量可以根據需要設為final,但是要注意final類中的所有成員方法都會被隐式地指定為final方法。
Java關鍵字final學習(變量、方法、類,final域重排序語義介紹)
  • 在使用final修飾類的時候,要注意謹慎選擇,除非這個類真的在以後不會用來繼承或者出于安全的考慮,盡量不要将類設計為final類。一般基本的類型為防止擴充類無意間破壞原來方法的實作類型都應該是final的,在JDK中String、StringBuffer等都是基本類型,是以String、StringBuffer等類是不能繼承的。

二、深入了解final關鍵字

1.類的final變量和普通變量有什麼差別?

當用final作用于類的成員變量時,成員變量(注意是類的成員變量,局部變量隻需要保證在使用之前被初始化指派即可)必須在定義時或者構造器中進行初始化指派,而且final變量一旦被初始化指派之後,就不能再被指派了。

那麼final變量和普通變量到底有何差別呢?下面請看一個例子:

 public class Test {
	public static void main(String[] args) {
		        String a = "hello2"; 
		        final String b = "hello";
		        String d = "hello";
		        String c = b + 2; 
		        String e = d + 2;
		        System.out.println((a == c));  // true
		        System.out.println((a == e));  // false
	}
}
           
大家可以先想一下這道題的輸出結果。為什麼第一個比較結果為true,而第二個比較結果為fasle。這裡面就是final變量和普通變量的差別了,當final變量是基本資料類型以及String類型時,如果在編譯期間能知道它的确切值,則編譯器會把它當做編譯期常量使用。也就是說在用到該final變量的地方,相當于直接通路的這個常量,不需要在運作時确定。這種和C語言中的宏替換有點像。
  • 是以在上面的一段代碼中,由于變量b被final修飾,是以會被當做編譯器常量,是以在使用到b的地方會直接将變量b 替換為它的

    值。而對于變量d的通路卻需要在運作時通過連結來進行。想必其中的差別大家應該明白了,不過要注意,隻有在編譯期間能确切知道final變量值的情況下,編譯器才會進行這樣的優化,

比如下面的這段代碼就不會進行優化:

public class Test {
	public static void main(String[] args) {
		String a = "hello2";
		final String b = getHello();
		String c = b + 2;
		System.out.println((a == c));  // false
	}

	public static String getHello() {
		return "hello";
	}
}
           

三、final 域的記憶體語義(線程中)擴充

多線程的中的知識在這塊補充一下。

** final 域的重排序規則**

對于final域,編譯器和處理器要遵守兩個重排序規則。

  • 在構造函數内對一個final域的寫入,與随後把這個被構造函數對象的引入用指派給引用變量,這兩個操作之間不能重排序;
  • 初次讀一個包含final域的對象的引用,與随後初次讀這個final域,這兩個操作之間不能重排序。

補充問題

  1. 被final修飾的類,能不能繼承其它類呢?即public final class Son extends Father{}

    可以

  2. 被final修飾的方法,能不能被子類繼承?

    可以

  3. 被final修飾的方法,能不能在本類中重載?

    可以

問題:

final修飾的類,變量,方法有什麼特點?ABC

A:被final修飾的類,表明該類是最終類,不能被繼承

B:被final修飾的方法,表明該方法是最終方法,不能被重寫

C:被final修飾的變量,表明該變量是常量,不能再次被指派,可以初始值

D:預設

參考部落格:https://www.cnblogs.com/dolphin0520/p/3736238.html

書籍:<<Java并發程式設計的藝術>>

繼續閱讀