目錄
- 1 final基本用法
- 1.1 final修飾變數
- 1.2 final修飾方法引數
- 1.3 final修飾方法
- 1.4 final修飾類
- 1.5 空白final
- 1.6 static final
- 2 jvm角度理解final不可變性
- 3 final多執行緒下可見性
- 4 final域重排序規則
- 5 面試常見問題
- 5.1 所有的final修飾的欄位都是編譯期常量嗎?
- 5.2 final型別的類如何拓展?
- 5.3 如何理解private所修飾的方法是隱式的final?
1 final基本用法
final:“這是無法改變的"
final可以修飾:變數、引數、方法、類
1.1 final修飾變數
修飾變數(變數、區域變數),當變數型別為:
- 基本型別,一旦被賦值,該值不能被改變,
- 參考型別,一旦參考被初始化指向一個物件,就不能指向別的物件,但物件內容可以被修改
- 資料型別:陣列也是參考型別
分析以下代碼:
import java.util.Random;
class Value {
int i; // Package access
public Value(int i) { this.i = i; }
}
public class FinalData {
private static Random rand = new Random(47);
private String id;
public FinalData(String id) { this.id = id; }
//編譯時常量
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(11);
private final Value v2 = new Value(22);
private static final Value VAL_3 = new Value(33);
// Arrays:
private final int[] a = { 1, 2, 3, 4, 5, 6 };
public String toString() {
return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5;
}
public static void main(String[] args) {
FinalData fd1 = new FinalData("fd1");
//! fd1.valueOne++; // Error: can’t change value
fd1.v2.i++; // OK:參考指向的物件內容可變
fd1.v1 = new Value(9); // OK :非final,參考可變
for(int i = 0; i < fd1.a.length; i++)
fd1.a[i]++; // Object isn’t constant!
//! fd1.v2 = new Value(0); // Error: final參考不可變
//! fd1.VAL_3 = new Value(1); //Error: final參考不可變
//! fd1.a = new int[3];
System.out.println(fd1);
System.out.println("Creating new FinalData");
FinalData fd2 = new FinalData("fd2");
System.out.println(fd1);
System.out.println(fd2);
}
}
/* 運行結果:
fd1: i4 = 15, INT_5 = 18
Creating new FinalData
fd1: i4 = 15, INT_5 = 18
fd2: i4 = 13, INT_5 = 18
*///:~
說明:
- valuOne和VALUE_TWO:都是編譯期常量,無重大區別,
- VAL_THREE:典型的對常量定義的方式:定義為public,則可以被用于包之外;定義為static,則強調只有一份;定義為final,則說明它是一個常量,注意這種型別常量的命名方式(大寫和下劃線)
- i4和INT_ 5:final變數不代表編譯時可知它的值,可以在運行時初始化值,例如在運行時使用隨機生成的數值來初始化
- v1、v2、VAL_3 說明final參考的特征
- 特別注意:INT_5:不可以通過創建第二個FinalData物件而加以改變,因為它是static的,在裝載時已被初始化,而不是每次創建新物件時都初始化,
1.2 final修飾方法引數
引數:遵循final修飾變數的約束條件,不能在方法中修改它的值或者指向別的物件,
private void finalParam(final Map param){
param = new HashMap();//報錯
param.put("","");//不報錯
}
1.3 final修飾方法
使用final方法的原因:確保在繼承中使方法行為保持不變,并且不會被覆寫(設計考慮),
- final修飾的方法不可以重寫(重寫發生在父類與之類)
- final修飾的方法可以多載(同一個類)
以下代碼可以正確運行:
public class FinalExampleParent {
public final void test() {
}
public final void test(String str) {
}
}
final和private:
類中所有的private方法都隱式地指定為final的,由于其它類無法取用private方法,因此無法覆寫它,可以對private方法添加final修飾,但沒意義,
1.4 final修飾類
當類定義為final時,表示該類不可繼承,
final類的所有方法都是隱式為final,因為無法覆寫它們
1.5 空白final
定義:被宣告為final但又未給定初值的域,
用途:提供了更大的靈活性:一個類中的final域就可以做到根據物件而有所不同,卻又保持其恒定不變的特性,
class Poppet {
private int i;
Poppet(int ii) { i = ii; }
}
public class BlankFinal {
private final int i = 0; // Initialized final
private final int j; // Blank final
private final Poppet p; // Blank final reference
//空final構造器中初始化
public BlankFinal() {
j = 1; // Initialize blank final
p = new Poppet(1); // Initialize blank final reference
}
public BlankFinal(int x) {
j = x; // Initialize blank final
p = new Poppet(x); // Initialize blank final reference
}
public static void main(String[] args) {
//空final域在不同情形下賦予不一樣的初值
new BlankFinal();
new BlankFinal(47);
}
}
說明:
- 必須在域的定義處或者每個構造器中對final賦值,這正是fnal域在使用前總是被初始化的原因所在,
- 一個類中的final域可以根據物件而有所不同,卻又保持其不變的特性,
1.6 static final
- 同時是static final 的欄位占據一段不能改變的存盤空間,它必須在定義的時候進行賦值,否則編譯器將不予通過【即使在建構式中初始化也不行】,
- static修飾的欄位并不屬于一個物件,而是屬于這個類的,【對一個類創建多個物件,其static final 修飾的變數其實是指向同一個值】
2 jvm角度理解final不可變性
一、Javac編譯器
final變數的不變性由Javac編譯時來保證:(只能在編譯期而不能在運行期中檢查)
javac編譯時,進入資料及控制流分析階段時,Flow.flow()會涉及以下檢查:檢查final變數是否有多次賦值,空白final變數是否在建構式中進行過初始化,
這里參考:javac final變數未賦值檢測講解
二、JVM類加載
final類的不可變性由jvm進行類加載的校驗階段來保證:
JVM類加載的校驗階段中,對元資料驗證時,包含final語意校驗:
1. 這個類的父類是否繼承了不允許被繼承的類(被final修飾的類)
2. 類中的欄位、方法是否與父類產生矛盾(例如覆寫了父類的final欄位,或者出現不符合規則的方法多載,例如方法引數都一致,但回傳值型別卻不同等)
3 final多執行緒下可見性
定義:被final修飾的欄位在構造器中一旦被初始化完成,并且構造器沒有把“this”的參考傳遞出去,那么在其他執行緒中就能看見final欄位的值,
如代碼所示,變數i與j都具備可見性,它們無須同步就能被其他執行緒正確訪問,
public static final int i;
public final int j;
static {
i = 0;
// 省略后續動作
}
{
// 選擇在建構式中初始化
j = 0;
// 省略后續動作
}
解讀:
final欄位如果宣告時賦值,因為只能賦值一次,因此即便存在并發,也能確保只有唯一值
如果在建構式中賦值,在無參考溢位下,建構式是執行緒安全的,因此final欄位也是執行緒安全
4 final域重排序規則
這方面內容待研究,或者參考:final域重排序規則
5 面試常見問題
5.1 所有的final修飾的欄位都是編譯期常量嗎?
不是
編譯期常量指的就是程式在編譯時就能確定這個常量的具體值
非編譯期常量就是程式在運行時才能確定常量的值 (運行時常量)
public class Test {
//編譯期常量
final int i = 1;
final static int J = 1;
//非編譯期常量
Random r = new Random();
final int k = r.nextInt();
}
k的值由亂數物件決定,所以不是所有的final修飾的欄位都是編譯期常量,只是k的值在被初始化后無法被更改,
5.2 final型別的類如何拓展?
設計模式中最重要的兩種關系,一種是繼承/實作,另外一種是組合關系,所以當遇到不能用繼承的,應該考慮用組合:
class MyString{
private String innerString;
// ...init & other methods
// 支持老的方法
public int length(){
return innerString.length(); // 通過innerString呼叫老的方法
}
// 添加新方法
public String toMyString(){
//...
}
}
5.3 如何理解private所修飾的方法是隱式的final?
類中所有的private方法都隱式地指定為final,因為其它類無法呼叫private方法,因此無法覆寫它,可以對private方法添加final修飾,但沒意義
參考書籍:《Thinking in Java》 《深入理解java虛擬機》
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/540277.html
標籤:其他
上一篇:SQL 中各種連接 JOIN
