【Java 溫故而知新系列】基礎(chǔ)知識-04 重點關(guān)鍵字(面試經(jīng)常遇到的)
1、final
在 Java 中,final 關(guān)鍵字可以用于變量、方法和類,分別賦予它們不同的語義和行為。以下是 final 關(guān)鍵字的主要作用
修飾變量
當(dāng) final 修飾一個變量時,表示該變量的值一旦初始化后就不能再被改變。這適用于基本類型和引用類型。
- 基本類型:對于基本類型(如
int,double等),final意味著變量的值不能改變。基本類型詳情見這篇【Java 溫故而知新系列】基礎(chǔ)知識-02 數(shù)據(jù)基本類型 - 引用類型:對于引用類型(如對象或數(shù)組),
final意味著引用本身不能指向另一個對象,但對象內(nèi)部的狀態(tài)是可以改變的(除非對象本身是不可變的)
1 final int x = 5; // 基本類型的 final 變量 2 // x = 10; // 錯誤:不能修改 final 變量的值 3 4 final StringBuilder sb = new StringBuilder("Hello"); 5 sb.append(" World"); // 正確:可以修改對象的狀態(tài) 6 // sb = new StringBuilder("Java"); // 錯誤:不能重新賦值給 final 引用
修飾方法
當(dāng) final 修飾一個方法時,表示該方法不能被子類重寫(override)。這有助于確保某些關(guān)鍵行為不會被意外改變,并且可以提高性能優(yōu)化的可能性,因為編譯器知道該方法的行為不會在運行時改變。
代碼為證:

修飾類
當(dāng) final 修飾一個類時,表示該類不能被繼承。這意味著沒有任何其他類可以從這個類派生出來。使用 final 類通常是為了保護(hù)類的設(shè)計不被更改,或者是因為該類提供了非常具體的實現(xiàn),不需要也不應(yīng)該有子類。
其實Java中我們平時用到的很多類都是被final關(guān)鍵字修飾的,比如常用的包裝類:Integer、Double 等(包裝類詳情可見這篇:【Java 溫故而知新系列】基礎(chǔ)知識-03 基本類型對應(yīng)之包裝類),再比如 String 等等。
修飾方法參數(shù)
你也可以將方法參數(shù)聲明為 final,這意味著在方法體內(nèi)不能修改這些參數(shù)的值。這對于編寫清晰、無副作用的方法很有幫助,尤其是在多線程環(huán)境中。其實這個跟第一種修飾變量是比較類似的。
代碼為證:

2、static
static可以用來修飾變量、方法、代碼塊和內(nèi)部類。使用 static 的主要目的是為了創(chuàng)建屬于類本身而不是類的實例(對象)的成員。這意味著靜態(tài)成員可以在沒有創(chuàng)建類的任何實例的情況下被訪問,并且所有實例共享同一個靜態(tài)成員。
靜態(tài)變量
- 靜態(tài)變量:又稱為類變量,也就是說這個變量屬于類的,類所有的實例都共享靜態(tài)變量,可以直接通過類名來訪問它。靜態(tài)變量在內(nèi)存中只存在一份。
- 實例變量:每創(chuàng)建一個實例就會產(chǎn)生一個實例變量,它與該實例同生共死。
1 class A { 2 public static Integer count = 10; //靜態(tài)變量,類變量 3 public Integer countB= 10;//實例變量 4 } 5 public class JavaKeyWorkDemo { 6 public static void main(String[] args) { 7 System.out.println(A.count); 8 A.count = 20; 9 System.out.println(A.count); 10 } 11 }
10 20
靜態(tài)方法
靜態(tài)方法在類加載的時候就存在了,它不依賴于任何實例。所以靜態(tài)方法必須有實現(xiàn),也就是說它不能是抽象方法。
1 public abstract class A { 2 public static void func1(){ 3 } 4 // public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static' 5 }
只能訪問所屬類的靜態(tài)字段和靜態(tài)方法,方法中不能有 this 和 super 關(guān)鍵字,因為這兩個關(guān)鍵字與具體對象關(guān)聯(lián)。
1 class A { 2 public static Integer countA = 10; //靜態(tài)變量,類變量 3 public Integer countB= 10;//實例變量 4 public static void func1(){ 5 Integer a = countA; 6 //Integer b = countB; // Non-static field 'countB' cannot be referenced from a static context 無法從 static 上下文引用非 static 字段 'countB' 7 //Integer c = this.countB; // 'A.this' cannot be referenced from a static context 無法從 static 上下文引用 'A.this' 8 } 9 }
靜態(tài)代碼塊
靜態(tài)語句塊在類初始化時運行一次。靜態(tài)代碼塊用于初始化類級別的資源,在類加載到 JVM 時執(zhí)行一次。如初始化靜態(tài)變量等。
1 class A { 2 static { 3 System.out.println("123"); 4 } 5 public static Integer countA = 10; //靜態(tài)變量,類變量 6 public Integer countB= 10;//實例變量 7 public static void func1(){ 8 Integer a = countA; 9 //Integer b = countB; // Non-static field 'countB' cannot be referenced from a static context 無法從 static 上下文引用非 static 字段 'countB' 10 //Integer c = this.countB; // 'A.this' cannot be referenced from a static context 無法從 static 上下文引用 'A.this' 11 } 12 } 13 public class JavaKeyWorkDemo { 14 public static void main(String[] args) { 15 A a1= new A(); 16 A a2= new A(); 17 } 18 }
123
靜態(tài)內(nèi)部類
非靜態(tài)內(nèi)部類依賴于外部類的實例,也就是說需要先創(chuàng)建外部類實例,才能用這個實例去創(chuàng)建非靜態(tài)內(nèi)部類。而靜態(tài)內(nèi)部類不需要。靜態(tài)內(nèi)部類不能訪問外部類的非靜態(tài)的變量和方法。
靜態(tài)內(nèi)部類實現(xiàn)單例:
1 public class Singleton { 2 3 // 私有構(gòu)造函數(shù),防止外部直接實例化 4 private Singleton() { 5 } 6 7 // 靜態(tài)內(nèi)部類,用于持有外部類的實例 8 private static class SingletonHolder { 9 private static final Singleton INSTANCE = new Singleton(); 10 } 11 12 // 提供公有的靜態(tài)方法,用于獲取外部類的實例 13 public static Singleton getInstance() { 14 return SingletonHolder.INSTANCE; 15 } 16 }
靜態(tài)導(dǎo)包
在使用靜態(tài)變量和方法時不用再指明 ClassName,從而簡化代碼,但可讀性大大降低。
import static 包名.類名.靜態(tài)成員;//導(dǎo)入一個類中指定靜態(tài)成員
import static com.xxx.ClassName.* //導(dǎo)入一個類中的所有靜態(tài)成員,可以使用通配符*
1 import static java.lang.Math.*; 2 3 public class StaticImportExample { 4 public static void main(String[] args) { 5 System.out.println(sqrt(16)); // 直接調(diào)用 sqrt 方法,無需前綴 Math. 6 System.out.println(PI); // 直接使用 PI 常量,無需前綴 Math. 7 } 8 }
初始化順序
靜態(tài)變量和靜態(tài)語句塊優(yōu)先于實例變量和普通語句塊,靜態(tài)變量和靜態(tài)語句塊的初始化順序取決于它們在代碼中的順序。
存在繼承的情況下,初始化順序為:
- 父類(靜態(tài)變量、靜態(tài)語句塊)
- 子類(靜態(tài)變量、靜態(tài)語句塊)
- 父類(實例變量、普通語句塊)
- 父類(構(gòu)造函數(shù))
- 子類(實例變量、普通語句塊)
- 子類(構(gòu)造函數(shù))
3、 public,private,protected,以及不寫(默認(rèn))時的區(qū)別
- private : 在同一類內(nèi)可見。使用對象:變量、方法。 注意:不能修飾類(外部 類)
- default (即缺省,什么也不寫,不使用任何關(guān)鍵字): 在同一包內(nèi)可見,不使用 任何修飾符。使用 對象:類、接口、變量、方法。
- protected : 對同一包內(nèi)的類和所有子類可見。使用對象:變量、方法。 注意: 不能修飾類(外部 類)。
- public : 對所有類可見。使用對象:類、接口、變量、方法
4、this關(guān)鍵字的用法
- 普通的直接引用,this相當(dāng)于是指向當(dāng)前對象本身。
- 形參與成員名字重名,用this來區(qū)分
1 public Person(String name, int age) { 2 this.name = name; 3 this.age = age; 4 }
- 引用本類的構(gòu)造函數(shù)
1 class Person{ 2 private String name; 3 private int age; 4 5 public Person() { 6 } 7 8 public Person(String name) { 9 this.name = name; 10 } 11 public Person(String name, int age) { 12 this(name); 13 this.age = age; 14 } 15 }
5、super關(guān)鍵字的用法
- 普通的直接引用 與this類似,super相當(dāng)于是指向當(dāng)前對象的父類的引用,這樣就可以用super.xxx來引用父類的成員。
- 子類中的成員變量或方法與父類中的成員變量或方法同名時,用super進(jìn)行區(qū)分
- 引用父類構(gòu)造函數(shù)。super(參數(shù)):調(diào)用父類中的某一個構(gòu)造函數(shù)(應(yīng)該為構(gòu)造函數(shù)中的第一條語句);this(參數(shù)):調(diào)用本類中另一種形式的構(gòu)造函數(shù)(應(yīng)該為構(gòu)造函數(shù)中的第一條語句)。
1 class Person{ 2 protected String name; 3 4 public Person(String name) { 5 this.name = name; 6 } 7 8 } 9 10 class Student extends Person{ 11 private String name; 12 13 public Student(String name, String name1) { 14 super(name); 15 this.name = name1; 16 } 17 18 public void getInfo(){ 19 System.out.println(this.name); //Child 20 System.out.println(super.name); //Father 21 } 22 23 } 24 25 public class Test { 26 public static void main(String[] args) { 27 Student s1 = new Student("Father","Child"); 28 s1.getInfo(); 29 30 } 31 }
6、volatile
volatile是一個變量修飾符,只能用來修飾變量,用法也比較簡單,只需要在聲明一個可能被多線程訪問的變量時,使用volatile修飾即可。在并發(fā)編程的三大特性——原子性、可見性、有序性中,volatile只能保證可見性和有序性(禁止指令重排),并不能保證原子性。
- 原子性:一個操作不可拆分、不被中斷,要么全部執(zhí)行,要么都不執(zhí)行。(數(shù)據(jù)庫中ACID的原子性指的是“要么都執(zhí)行要么都回滾”,這是不同的概念)
- 可見性:指多個線程之間共享數(shù)據(jù)的可見性。即當(dāng)一個線程修改了共享變量時,其它線程能立即看到這個修改
- 有序性:volatile除了可以保證數(shù)據(jù)的可見性之外,還有一個強(qiáng)大的功能,那就是它可以禁止指令重排序,所以能在一定程度上保證有序性
不能保證原子性(代碼為證):
1 class VolatileIncr { 2 volatile int num = 0; 3 public void add() { 4 num++; 5 } 6 public static void main(String[] args) { 7 VolatileIncr test = new VolatileIncr(); 8 //啟動10個線程 9 for (int i = 0; i < 10; i++) { 10 new Thread(() -> { 11 //每個線程執(zhí)行1000次+1操作 12 for (int j = 0; j < 1000; j++) { 13 test.add(); 14 } 15 }, String.valueOf(i)).start(); 16 } 17 while (Thread.activeCount() > 2) { 18 Thread.yield(); 19 } 20 System.out.println("最后num的值為:" + test.num); 21 } 22 }
其中一次的執(zhí)行結(jié)果:
最后num的值為:9120
進(jìn)程已結(jié)束,退出代碼0
以上代碼,我們的預(yù)期結(jié)果應(yīng)該是10000才對,但執(zhí)行起來發(fā)現(xiàn),并不是每次都是10000,這就是因為i++這個操作沒辦法保證原子性。它其實包含三個指令:
- 執(zhí)行GETFIELD拿到主內(nèi)存中的原始值num。
- 執(zhí)行IADD進(jìn)行+1操作。
- 執(zhí)行PUTFIELD把工作內(nèi)存中的值寫回主內(nèi)存中。
當(dāng)多個線程并發(fā)執(zhí)行PUTFILED指令的時候,會出現(xiàn)寫回主存覆蓋的問題,所以最終結(jié)果可能會比預(yù)期的結(jié)果要小,所以volatile不能保證原子性。
可見性(代碼為證):
1 class Visibility { 2 private boolean flag = false; 3 public void start() { 4 new Thread(() -> { 5 System.out.println("Thread1 start"); 6 while (!flag) { 7 // 不斷循環(huán),等待flag變?yōu)閠rue 8 } 9 System.out.println("Thread1 complet"); 10 }).start(); 11 // 確保線程1先啟動 12 try { 13 Thread.sleep(100); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 new Thread(() -> { 18 System.out.println("Thread2 start"); 19 // 修改flag的值為true 20 flag = true; 21 System.out.println("Thread2 complet"); 22 }).start(); 23 } 24 public static void main(String[] args) { 25 Visibility example = new Visibility(); 26 example.start(); 27 } 28 }
執(zhí)行結(jié)果:

從結(jié)果看,程序“卡”在了Thread1的while循環(huán)中,說明Thread1讀到的flag還是flase,但是Thread2已經(jīng)把它改為true了,這是為什么?這是因為Thread1在執(zhí)行的時候,就把flag的副本保存在這就的工作內(nèi)存中,之后就會一直讀取自己線程工作內(nèi)存中flag變量的值,而不會去主內(nèi)存中重新獲取新的值。
加了volatile修飾后的運行結(jié)果:

有序性(代碼為證):
下面是一個經(jīng)典的例子:雙重檢測實現(xiàn)單例的例子(面試經(jīng)常問這樣實現(xiàn)單例會不會有問題?有什么問題?如何解決?)
1 public class Singleton { 2 //私有化構(gòu)造函數(shù) 3 private Singleton(){} 4 //單例對象(無volatile修飾) 5 private static Singleton instance=null; 6 public static Singleton getInstance(){ 7 //第一次檢測 8 if (instance==null){ 9 //加鎖 10 synchronized (Singleton.class){ 11 //第二次檢測 12 if (instance==null){ 13 //初始化 14 instance=new Singleton(); 15 } 16 } 17 } 18 return instance; 19 } 20 }
雙重校驗鎖機(jī)制在多線程環(huán)境中,不一定線程安全,原因是有指令重排的存在,在極端情況下,上述的單例對象可能發(fā)生空指針異常
我們假設(shè)線程1和線程2同時請求getSingleton()方法的時候:
- 線程1執(zhí)行到instance=new Singleton();,開始初始化。
- 線程2執(zhí)行到“第一次檢測”的位置,判斷singleton == null。
- 線程2經(jīng)過判斷發(fā)現(xiàn)singleton !=null,于是就直接執(zhí)行return instance。
- 線程2拿到singleton對象后,開始執(zhí)行后續(xù)的操作。
以上過程看似沒有什么問題,但在第4步執(zhí)行后續(xù)操作的時候,是有可能拋空指針異常的,這是因為在第3步的時候,線程2拿到的singleton對象并不是一個完整的對象。
很明顯instance=new Singleton();,這段代碼出現(xiàn)了問題,那我們來分析一下,這個代碼的執(zhí)行過程可以簡化成3步:
- JVM為對象分配一塊內(nèi)存M。
- 在內(nèi)存上為對象進(jìn)行初始化。
- 將內(nèi)存M的地址賦值給singleton變量。
因為將內(nèi)存的地址賦值給singleton變量是最后一步,所以線程1在這一步驟執(zhí)行之前,線程2在對singleton == null判斷一直都是true,那么它會一直阻塞,直到線程1執(zhí)行完。
但是這個過程并不是一個原子操作,并且編譯器可能會進(jìn)行重排序,如果以上步驟被重排序為:
- JVM為對象分配一塊內(nèi)存M。
- 將內(nèi)存M的地址賦值給singleton變量。
- 在內(nèi)存上為對象進(jìn)行初始化。
這樣的話線程1會先內(nèi)存分配,再執(zhí)行變量賦值,最后執(zhí)行初始化。也就是說在線程1執(zhí)行初始化之前,線程2對singleton == null的判斷會提前得到一個false,于是便返回了一個不完整的對象,所以在執(zhí)行后續(xù)操作時,就發(fā)生了空指針異常。
要解決的話,直接禁止它指令重排就行了,所以volatile就派上用場了,只需要用volatile修飾一下instance即可。
7、其他
class (類):public class A(){}花括號里是已實現(xiàn)的方法體,類名需要與文件名相同 interface (接口):public interface B(){}花括號里有方法體,但沒有實現(xiàn),方法體句子后面是英文分號“;”結(jié)尾 abstract (聲明抽象):public abstract class C(){}介于類與接口中間,可以有,也可以沒有已經(jīng)實現(xiàn)的方法體 implements (實現(xiàn)):用于類或接口,實現(xiàn)接口public class A interface B(){} extends (繼承):用于類繼承類public class A extends D(){} new (創(chuàng)建新對象):A a=new A();A表示一個類 import (引入包的關(guān)鍵字):當(dāng)使用某個包的一些類時,僅需要類名,即可自動插入類所在的包 package (定義包的關(guān)鍵字):將所有相關(guān)的類放在一個包類以便查找修改等 assert(斷言):assert <bool expression>,以 Integer類的IntegerCache 方法 中代碼舉例 assert IntegerCache.high >= 127。斷言是為了方便調(diào)試程序,并不是發(fā)布程序的組成部分。理解這一點是很關(guān)鍵的。默認(rèn)情況下,JVM是關(guān)閉斷言的。因此如果想使用斷言調(diào)試程序,需要手動打開斷言功能。在命令行模式下運行Java程序時可增加參數(shù)-enableassertions或者-ea打開斷言。可通過-disableassertions或者-da關(guān)閉斷言(默認(rèn)情況,可有可無)。 const——常量,常數(shù):用于修改字段或局部變量的聲明。保留字(現(xiàn)在沒用以后可能用到作為關(guān)鍵字) goto——轉(zhuǎn)到:指定跳轉(zhuǎn)到標(biāo)簽,找到標(biāo)簽后,程序?qū)⑻幚韽南乱恍虚_始的命令。保留字(現(xiàn)在沒用以后可能用到作為關(guān)鍵字)
雙重校驗鎖機(jī)制在多線程環(huán)境中,不一定線程安全,原因是有指令重排的存在

浙公網(wǎng)安備 33010602011771號