Java 基礎(chǔ)常見知識點&面試題總結(jié)(下),2022 最新版!
《Java 面試指北》來啦!這是一份教你如何更高效地準備面試的小冊,涵蓋常見八股文(系統(tǒng)設(shè)計、常見框架、分布式、高并發(fā) ......)、優(yōu)質(zhì)面經(jīng)等內(nèi)容。
JavaGuide 在線閱讀網(wǎng)站:https://javaguide.cn/
你好,我是 Guide。秋招即將到來,我對 JavaGuide 的內(nèi)容進行了重構(gòu)完善,公眾號同步一下最新更新,希望能夠幫助你。
你也可以在網(wǎng)站(javaguide.cn)上在線閱讀,閱讀體驗會更好!

前兩篇:
異常
Java 異常類層次結(jié)構(gòu)圖概覽 :

Exception 和 Error 有什么區(qū)別?
在 Java 中,所有的異常都有一個共同的祖先 java.lang 包中的 Throwable 類。Throwable 類有兩個重要的子類:
Exception:程序本身可以處理的異常,可以通過catch來進行捕獲。Exception又可以分為 Checked Exception (受檢查異常,必須處理) 和 Unchecked Exception (不受檢查異常,可以不處理)。Error:Error屬于程序無法處理的錯誤 ,我們沒辦法通過catch來進行捕獲不建議通過catch捕獲 。例如 Java 虛擬機運行錯誤(Virtual MachineError)、虛擬機內(nèi)存不夠錯誤(OutOfMemoryError)、類定義錯誤(NoClassDefFoundError)等 。這些異常發(fā)生時,Java 虛擬機(JVM)一般會選擇線程終止。
Checked Exception 和 Unchecked Exception 有什么區(qū)別?
Checked Exception 即 受檢查異常 ,Java 代碼在編譯過程中,如果受檢查異常沒有被 catch或者throws 關(guān)鍵字處理的話,就沒辦法通過編譯。
比如下面這段 IO 操作的代碼:

除了RuntimeException及其子類以外,其他的Exception類及其子類都屬于受檢查異常 。常見的受檢查異常有:IO 相關(guān)的異常、ClassNotFoundException 、SQLException...。
Unchecked Exception 即 不受檢查異常 ,Java 代碼在編譯過程中 ,我們即使不處理不受檢查異常也可以正常通過編譯。
RuntimeException 及其子類都統(tǒng)稱為非受檢查異常,常見的有(建議記下來,日常開發(fā)中會經(jīng)常用到):
NullPointerException(空指針錯誤)IllegalArgumentException(參數(shù)錯誤比如方法入?yún)㈩愋湾e誤)NumberFormatException(字符串轉(zhuǎn)換為數(shù)字格式錯誤,IllegalArgumentException的子類)ArrayIndexOutOfBoundsException(數(shù)組越界錯誤)ClassCastException(類型轉(zhuǎn)換錯誤)ArithmeticException(算術(shù)錯誤)SecurityException(安全錯誤比如權(quán)限不夠)UnsupportedOperationException(不支持的操作錯誤比如重復創(chuàng)建同一用戶)......

Throwable 類常用方法有哪些?
String getMessage(): 返回異常發(fā)生時的簡要描述String toString(): 返回異常發(fā)生時的詳細信息String getLocalizedMessage(): 返回異常對象的本地化信息。使用Throwable的子類覆蓋這個方法,可以生成本地化信息。如果子類沒有覆蓋該方法,則該方法返回的信息與getMessage()返回的結(jié)果相同void printStackTrace(): 在控制臺上打印Throwable對象封裝的異常信息
try-catch-finally 如何使用?
try塊 :用于捕獲異常。其后可接零個或多個catch塊,如果沒有catch塊,則必須跟一個finally塊。* catch塊 :用于處理 try 捕獲到的異常。finally塊 :無論是否捕獲或處理異常,finally塊里的語句都會被執(zhí)行。當在try塊或catch塊中遇到return語句時,finally語句塊將在方法返回之前被執(zhí)行。
代碼示例:
try {
System.out.println("Try to do something");
throw new RuntimeException("RuntimeException");
} catch (Exception e) {
System.out.println("Catch Exception -> " + e.getMessage());
} finally {
System.out.println("Finally");
}
輸出:
Try to do something
Catch Exception -> RuntimeException
Finally
注意:不要在 finally 語句塊中使用 return! 當 try 語句和 finally 語句中都有 return 語句時,try 語句塊中的 return 語句會被忽略。這是因為 try 語句中的 return 返回值會先被暫存在一個本地變量中,當執(zhí)行到 finally 語句中的 return 之后,這個本地變量的值就變?yōu)榱?finally 語句中的 return 返回值。
jvm 官方文檔中有明確提到:
If the
tryclause executes a return, the compiled code does the following:
Saves the return value (if any) in a local variable. Executes a jsr to the code for the finallyclause.Upon return from the finallyclause, returns the value saved in the local variable.
代碼示例:
public static void main(String[] args) {
System.out.println(f(2));
}
public static int f(int value) {
try {
return value * value;
} finally {
if (value == 2) {
return 0;
}
}
}
輸出:
0
finally 中的代碼一定會執(zhí)行嗎?
不一定的!在某些情況下,finally 中的代碼不會被執(zhí)行。
就比如說 finally 之前虛擬機被終止運行的話,finally 中的代碼就不會被執(zhí)行。
try {
System.out.println("Try to do something");
throw new RuntimeException("RuntimeException");
} catch (Exception e) {
System.out.println("Catch Exception -> " + e.getMessage());
// 終止當前正在運行的Java虛擬機
System.exit(1);
} finally {
System.out.println("Finally");
}
輸出:
Try to do something
Catch Exception -> RuntimeException
另外,在以下 2 種特殊情況下,finally 塊的代碼也不會被執(zhí)行:
程序所在的線程死亡。 關(guān)閉 CPU。
相關(guān) issue:https://github.com/Snailclimb/JavaGuide/issues/190。
???? 進階一下:從字節(jié)碼角度分析try catch finally這個語法糖背后的實現(xiàn)原理。
如何使用 try-with-resources 代替try-catch-finally?
適用范圍(資源的定義): 任何實現(xiàn) java.lang.AutoCloseable或者java.io.Closeable的對象關(guān)閉資源和 finally 塊的執(zhí)行順序: 在 try-with-resources語句中,任何 catch 或 finally 塊在聲明的資源關(guān)閉后運行
《Effective Java》中明確指出:
面對必須要關(guān)閉的資源,我們總是應(yīng)該優(yōu)先使用
try-with-resources而不是try-finally。隨之產(chǎn)生的代碼更簡短,更清晰,產(chǎn)生的異常對我們也更有用。try-with-resources語句讓我們更容易編寫必須要關(guān)閉的資源的代碼,若采用try-finally則幾乎做不到這點。
Java 中類似于InputStream、OutputStream 、Scanner 、PrintWriter等的資源都需要我們調(diào)用close()方法來手動關(guān)閉,一般情況下我們都是通過try-catch-finally語句來實現(xiàn)這個需求,如下:
//讀取文本文件的內(nèi)容
Scanner scanner = null;
try {
scanner = new Scanner(new File("D://read.txt"));
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (scanner != null) {
scanner.close();
}
}
使用 Java 7 之后的 try-with-resources 語句改造上面的代碼:
try (Scanner scanner = new Scanner(new File("test.txt"))) {
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
}
當然多個資源需要關(guān)閉的時候,使用 try-with-resources 實現(xiàn)起來也非常簡單,如果你還是用try-catch-finally可能會帶來很多問題。
通過使用分號分隔,可以在try-with-resources塊中聲明多個資源。
try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
int b;
while ((b = bin.read()) != -1) {
bout.write(b);
}
}
catch (IOException e) {
e.printStackTrace();
}
異常使用有哪些需要注意的地方?
不要把異常定義為靜態(tài)變量,因為這樣會導致異常棧信息錯亂。每次手動拋出異常,我們都需要手動 new 一個異常對象拋出。 拋出的異常信息一定要有意義。 建議拋出更加具體的異常比如字符串轉(zhuǎn)換為數(shù)字格式錯誤的時候應(yīng)該拋出 NumberFormatException而不是其父類IllegalArgumentException。使用日志打印異常之后就不要再拋出異常了(兩者不要同時存在一段代碼邏輯中)。 ......
泛型
什么是泛型?有什么作用?
Java 泛型(Generics) 是 JDK 5 中引入的一個新特性。使用泛型參數(shù),可以增強代碼的可讀性以及穩(wěn)定性。
編譯器可以對泛型參數(shù)進行檢測,并且通過泛型參數(shù)可以指定傳入的對象類型。比如 ArrayList<Persion> persons = new ArrayList<Persion>() 這行代碼就指明了該 ArrayList 對象只能傳入 Persion 對象,如果傳入其他類型的對象就會報錯。
ArrayList<E> extends AbstractList<E>
并且,原生 List 返回類型是 Object ,需要手動轉(zhuǎn)換類型才能使用,使用泛型后編譯器自動轉(zhuǎn)換。
泛型的使用方式有哪幾種?
泛型一般有三種使用方式:泛型類、泛型接口、泛型方法。
1.泛型類:
//此處T可以隨便寫為任意標識,常見的如T、E、K、V等形式的參數(shù)常用于表示泛型
//在實例化泛型類時,必須指定T的具體類型
public class Generic<T>{
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
如何實例化泛型類:
Generic<Integer> genericInteger = new Generic<Integer>(123456);
2.泛型接口 :
public interface Generator<T> {
public T method();
}
實現(xiàn)泛型接口,不指定類型:
class GeneratorImpl<T> implements Generator<T>{
@Override
public T method() {
return null;
}
}
實現(xiàn)泛型接口,指定類型:
class GeneratorImpl<T> implements Generator<String>{
@Override
public String method() {
return "hello";
}
}
3.泛型方法 :
public static < E > void printArray( E[] inputArray )
{
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
使用:
// 創(chuàng)建不同類型數(shù)組:Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray( intArray );
printArray( stringArray );
注意:
public static < E > void printArray( E[] inputArray )一般被稱為靜態(tài)泛型方法;在 java 中泛型只是一個占位符,必須在傳遞類型后才能使用。類在實例化時才能真正的傳遞類型參數(shù),由于靜態(tài)方法的加載先于類的實例化,也就是說類中的泛型還沒有傳遞真正的類型參數(shù),靜態(tài)的方法的加載就已經(jīng)完成了,所以靜態(tài)泛型方法是沒有辦法使用類上聲明的泛型的。只能使用自己聲明的<E>
項目中哪里用到了泛型?
自定義接口通用返回結(jié)果 CommonResult<T>通過參數(shù)T可根據(jù)具體的返回類型動態(tài)指定結(jié)果的數(shù)據(jù)類型定義 Excel處理類ExcelUtil<T>用于動態(tài)指定Excel導出的數(shù)據(jù)類型構(gòu)建集合工具類(參考 Collections中的sort,binarySearch方法)。......
反射
何為反射?
如果說大家研究過框架的底層原理或者咱們自己寫過框架的話,一定對反射這個概念不陌生。
反射之所以被稱為框架的靈魂,主要是因為它賦予了我們在運行時分析類以及執(zhí)行類中方法的能力。通過反射你可以獲取任意一個類的所有屬性和方法,你還可以調(diào)用這些方法和屬性。
反射機制優(yōu)缺點
優(yōu)點 :可以讓咱們的代碼更加靈活、為各種框架提供開箱即用的功能提供了便利 缺點 :讓我們在運行時有了分析操作類的能力,這同樣也增加了安全問題。比如可以無視泛型參數(shù)的安全檢查(泛型參數(shù)的安全檢查發(fā)生在編譯時)。另外,反射的性能也要稍差點,不過,對于框架來說實際是影響不大的。Java Reflection: Why is it so slow?
反射的應(yīng)用場景
像咱們平時大部分時候都是在寫業(yè)務(wù)代碼,很少會接觸到直接使用反射機制的場景。
但是,這并不代表反射沒有用。相反,正是因為反射,你才能這么輕松地使用各種框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射機制。
這些框架中也大量使用了動態(tài)代理,而動態(tài)代理的實現(xiàn)也依賴反射。
比如下面是通過 JDK 實現(xiàn)動態(tài)代理的示例代碼,其中就使用了反射類 Method 來調(diào)用指定的方法。
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理類中的真實對象
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method " + method.getName());
return result;
}
}
另外,像 Java 中的一大利器 注解 的實現(xiàn)也用到了反射。
為什么你使用 Spring 的時候 ,一個@Component注解就聲明了一個類為 Spring Bean 呢?為什么你通過一個 @Value注解就讀取到配置文件中的值呢?究竟是怎么起作用的呢?
這些都是因為你可以基于反射分析類,然后獲取到類/屬性/方法/方法的參數(shù)上的注解。你獲取到注解之后,就可以做進一步的處理。
注解
Annotation (注解) 是 Java5 開始引入的新特性,可以看作是一種特殊的注釋,主要用于修飾類、方法或者變量。
注解本質(zhì)是一個繼承了Annotation 的特殊接口:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
public interface Override extends Annotation{
}
注解只有被解析之后才會生效,常見的解析方法有兩種:
編譯期直接掃描 :編譯器在編譯 Java 代碼的時候掃描對應(yīng)的注解并處理,比如某個方法使用 @Override注解,編譯器在編譯的時候就會檢測當前的方法是否重寫了父類對應(yīng)的方法。運行期通過反射處理 :像框架中自帶的注解(比如 Spring 框架的 @Value、@Component)都是通過反射來進行處理的。
JDK 提供了很多內(nèi)置的注解(比如 @Override 、@Deprecated),同時,我們還可以自定義注解。
I/O
什么是序列化?什么是反序列化?
如果我們需要持久化 Java 對象比如將 Java 對象保存在文件中,或者在網(wǎng)絡(luò)傳輸 Java 對象,這些場景都需要用到序列化。
簡單來說:
序列化:將數(shù)據(jù)結(jié)構(gòu)或?qū)ο筠D(zhuǎn)換成二進制字節(jié)流的過程 反序列化:將在序列化過程中所生成的二進制字節(jié)流轉(zhuǎn)換成數(shù)據(jù)結(jié)構(gòu)或者對象的過程
對于 Java 這種面向?qū)ο缶幊陶Z言來說,我們序列化的都是對象(Object)也就是實例化后的類(Class),但是在 C++這種半面向?qū)ο蟮恼Z言中,struct(結(jié)構(gòu)體)定義的是數(shù)據(jù)結(jié)構(gòu)類型,而 class 對應(yīng)的是對象類型。
維基百科是如是介紹序列化的:
序列化(serialization)在計算機科學的數(shù)據(jù)處理中,是指將數(shù)據(jù)結(jié)構(gòu)或?qū)ο鬆顟B(tài)轉(zhuǎn)換成可取用格式(例如存成文件,存于緩沖,或經(jīng)由網(wǎng)絡(luò)中發(fā)送),以留待后續(xù)在相同或另一臺計算機環(huán)境中,能恢復原先狀態(tài)的過程。依照序列化格式重新獲取字節(jié)的結(jié)果時,可以利用它來產(chǎn)生與原始對象相同語義的副本。對于許多對象,像是使用大量引用的復雜對象,這種序列化重建的過程并不容易。面向?qū)ο笾械膶ο笮蛄谢⒉桓爬ㄖ霸紝ο笏P(guān)系的函數(shù)。這種過程也稱為對象編組(marshalling)。從一系列字節(jié)提取數(shù)據(jù)結(jié)構(gòu)的反向操作,是反序列化(也稱為解編組、deserialization、unmarshalling)。
綜上:序列化的主要目的是通過網(wǎng)絡(luò)傳輸對象或者說是將對象存儲到文件系統(tǒng)、數(shù)據(jù)庫、內(nèi)存中。

https://www.corejavaguru.com/java/serialization/interview-questions-1
Java 序列化中如果有些字段不想進行序列化,怎么辦?
對于不想進行序列化的變量,使用 transient 關(guān)鍵字修飾。
transient 關(guān)鍵字的作用是:阻止實例中那些用此關(guān)鍵字修飾的的變量序列化;當對象被反序列化時,被 transient 修飾的變量值不會被持久化和恢復。
關(guān)于 transient 還有幾點注意:
transient只能修飾變量,不能修飾類和方法。transient修飾的變量,在反序列化后變量值將會被置成類型的默認值。例如,如果是修飾int類型,那么反序列后結(jié)果就是0。static變量因為不屬于任何對象(Object),所以無論有沒有transient關(guān)鍵字修飾,均不會被序列化。
獲取用鍵盤輸入常用的兩種方法
方法 1:通過 Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
方法 2:通過 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
Java 中 IO 流分為幾種?
按照流的流向分,可以分為輸入流和輸出流; 按照操作單元劃分,可以劃分為字節(jié)流和字符流; 按照流的角色劃分為節(jié)點流和處理流。
Java IO 流共涉及 40 多個類,這些類看上去很雜亂,但實際上很有規(guī)則,而且彼此之間存在非常緊密的聯(lián)系, Java IO 流的 40 多個類都是從如下 4 個抽象類基類中派生出來的。
InputStream/Reader: 所有的輸入流的基類,前者是字節(jié)輸入流,后者是字符輸入流。 OutputStream/Writer: 所有輸出流的基類,前者是字節(jié)輸出流,后者是字符輸出流。
按操作方式分類結(jié)構(gòu)圖:

按操作對象分類結(jié)構(gòu)圖:

既然有了字節(jié)流,為什么還要有字符流?
問題本質(zhì)想問:不管是文件讀寫還是網(wǎng)絡(luò)發(fā)送接收,信息的最小存儲單元都是字節(jié),那為什么 I/O 流操作要分為字節(jié)流操作和字符流操作呢?
回答:字符流是由 Java 虛擬機將字節(jié)轉(zhuǎn)換得到的,問題就出在這個過程還算是非常耗時,并且,如果我們不知道編碼類型就很容易出現(xiàn)亂碼問題。所以, I/O 流就干脆提供了一個直接操作字符的接口,方便我們平時對字符進行流操作。如果音頻文件、圖片等媒體文件用字節(jié)流比較好,如果涉及到字符的話使用字符流比較好。
·········· END ··············
歡迎加入我的知識星球獲取更多面試干貨,《Java 面試指北》持續(xù)更新完善中!

近期文章精選 :
走近作者 :
如果本文對你有幫助的話,歡迎點贊&在看&分享,這對我繼續(xù)分享&創(chuàng)作優(yōu)質(zhì)文章非常重要。感謝????
