1. 程式人生 > >2w字長文!手擼一套 Java 基礎面試題

2w字長文!手擼一套 Java 基礎面試題

## Java 基礎篇 ### Java 有哪些特點 * `併發性的`: 你可以在其中執行許多語句,而不必一次執行它 * `面向物件的`:基於類和麵向物件的程式語言。 * `獨立性的`: 支援**一次編寫,到處執行**的獨立程式語言,即編譯後的程式碼可以在支援 Java 的所有平臺上執行。 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200612142224855-369348130.png) #### Java 的特性 Java 的特性有如下這幾點 * `簡單`,Java 會讓你的工作變得更加輕鬆,使你把關注點放在主要業務邏輯上,而不必關心指標、運算子過載、記憶體回收等與主要業務無關的功能。 * `便攜性`,Java 是平臺無關性的,這意味著在一個平臺上編寫的任何應用程式都可以輕鬆移植到另一個平臺上。 * `安全性`, 編譯後會將所有的程式碼轉換為位元組碼,人類無法讀取。它使開發無病毒,無篡改的系統/應用成為可能。 * `動態性`,它具有適應不斷變化的環境的能力,它能夠支援動態記憶體分配,從而減少了記憶體浪費,提高了應用程式的效能。 * `分散式`,Java 提供的功能有助於建立分散式應用。使用`遠端方法呼叫(RMI)`,程式可以通過網路呼叫另一個程式的方法並獲取輸出。您可以通過從網際網路上的任何計算機上呼叫方法來訪問檔案。這是革命性的一個特點,對於當今的網際網路來說太重要了。 * `健壯性`,Java 有強大的記憶體管理功能,在編譯和執行時檢查程式碼,它有助於消除錯誤。 * `高效能`,Java 最黑的科技就是位元組碼程式設計,Java 程式碼編譯成的位元組碼可以輕鬆轉換為本地機器程式碼。通過 JIT 即時編譯器來實現高效能。 * `解釋性`,Java 被編譯成位元組碼,由 Java 執行時環境解釋。 * `多執行緒性`,Java支援多個執行執行緒(也稱為輕量級程序),包括一組同步原語。這使得使用執行緒程式設計更加容易,Java 通過管程模型來實現執行緒安全性。 ### 描述一下值傳遞和引用傳遞的區別 要想真正理解的話,可以參考這篇文章 : https://www.zhihu.com/question/31203609 簡單理解的話就是 `值傳遞`是指在呼叫函式時將實際引數複製一份到函式中,這樣的話如果函式對其傳遞過來的形式引數進行修改,將不會影響到實際引數 `引用傳遞` 是指在呼叫函式時將物件的地址直接傳遞到函式中,如果在對形式引數進行修改,將影響到實際引數的值。 ### == 和 equals 區別是什麼 `==` 是 Java 中一種操作符,它有兩種比較方式 * 對於`基本資料型別`來說, == 判斷的是兩邊的`值`是否相等 ```java public class DoubleCompareAndEquals { Person person1 = new Person(24,"boy"); Person person2 = new Person(24,"girl"); int c = 10; private void doubleCompare(){ int a = 10; int b = 10; System.out.println(a == b); System.out.println(a == c); System.out.println(person1.getId() == person2.getId()); } } ``` * 對於`引用型別`來說, == 判斷的是兩邊的`引用`是否相等,也就是判斷兩個物件是否指向了同一塊記憶體區域。 ```java private void equals(){ System.out.println(person1.getName().equals(person2.getName())); } ``` `equals` 是 Java 中所有物件的父類,即 `Object` 類定義的一個方法。它只能比較物件,它表示的是引用雙方的值是否相等。所以記住,並不是說 == 比較的就是引用是否相等,equals 比較的就是值,這需要區分來說的。 equals 用作物件之間的比較具有如下特性 * `自反性`:對於任何非空引用 x 來說,x.equals(x) 應該返回 true。 * `對稱性`:對於任何非空引用 x 和 y 來說,若x.equals(y)為 true,則y.equals(x)也為 true。 * `傳遞性`:對於任何非空引用的值來說,有三個值,x、y 和 z,如果x.equals(y) 返回true,y.equals(z) 返回true,那麼x.equals(z) 也應該返回true。 * `一致性`:對於任何非空引用 x 和 y 來說,如果 x.equals(y) 相等的話,那麼它們必須始終相等。 * `非空性`:對於任何非空引用的值 x 來說,x.equals(null) 必須返回 false。 ### String 中的 equals 是如何重寫的 String 代表的是 Java 中的`字串`,String 類比較特殊,它整個類都是被 `final` 修飾的,也就是說,String 不能被任何類繼承,任何 `修改` String 字串的方法都是建立了一個新的字串。 equals 方法是 Object 類定義的方法,Object 是所有類的父類,當然也包括 String,String 重寫了 `equals` 方法,下面我們來看看是怎麼重寫的 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200612142249452-328540131.png) * 首先會判斷要比較的兩個字串它們的`引用`是否相等。如果引用相等的話,直接返回 true ,不相等的話繼續下面的判斷 * 然後再判斷被比較的物件是否是 String 的例項,如果不是的話直接返回 false,如果是的話,再比較兩個字串的長度是否相等,如果長度不想等的話也就沒有比較的必要了;長度如果相同,會比較字串中的每個 `字元` 是否相等,一旦有一個字元不相等,就會直接返回 false。 下面是它的流程圖 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200612142257414-1428177831.png) 這裡再提示一下,你可能有疑惑什麼時候是 ```java if (this == anObject) { return true; } ``` 這個判斷語句如何才能返回 true?因為都是字串啊,字串比較的不都是堆空間嗎,猛然一看發現好像永遠也不會走,但是你忘記了 `String.intern()` 方法,它表示的概念在不同的 JDK 版本有不同的區分 在 JDK1.7 及以後呼叫 intern 方法是判斷執行時常量池中是否有指定的字串,如果沒有的話,就把字串新增到常量池中,並返回常量池中的物件。 驗證過程如下 ```java private void StringOverrideEquals(){ String s1 = "aaa"; String s2 = "aa" + new String("a"); String s3 = new String("aaa"); System.out.println(s1.intern().equals(s1)); System.out.println(s1.intern().equals(s2)); System.out.println(s3.intern().equals(s1)); } ``` * 首先 s1.intern.equals(s1) 這個無論如何都返回 true,因為 s1 字串創建出來就已經在常量池中存在了。 * 然後第二條語句返回 false,因為 s1 返回的是常量池中的物件,而 s2 返回的是堆中的物件 * 第三條語句 s3.intern.equals(s1),返回 true ,因為 s3 物件雖然在堆中建立了一個物件,但是 s3 中的 "aaa" 返回的是常量池中的物件。 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200612142320585-1477597877.png) ### 為什麼重寫 equals 方法必須重寫 hashcode 方法 equals 方法和 hashCode 都是 Object 中定義的方法,它們經常被一起重寫。 equals 方法是用來比較物件大小是否相等的方法,hashcode 方法是用來判斷每個物件 hash 值的一種方法。如果只重寫 equals 方法而不重寫 hashcode 方法,很可能會造成兩個不同的物件,它們的 hashcode 也相等,造成衝突。比如 ```java String str1 = "通話"; String str2 = "重地"; ``` 它們兩個的 hashcode 相等,但是 equals 可不相等。 我們來看一下 hashCode 官方的定義 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200612142329301-1659708264.png) 總結起來就是 * 如果在 Java 執行期間對同一個物件呼叫 hashCode 方法後,無論呼叫多少次,都應該返回相同的 hashCode,但是在不同的 Java 程式中,執行 hashCode 方法返回的值可能不一致。 * 如果兩個物件的 equals 相等,那麼 hashCode 必須相同 * 如果兩個物件 equals 不相等,那麼 hashCode 也有可能相同,所以需要重寫 hashCode 方法,因為你不知道 hashCode 的底層構造(反正我是不知道,有大牛可以傳授傳授),所以你需要重寫 hashCode 方法,來為不同的物件生成不同的 hashCode 值,這樣能夠提高不同物件的訪問速度。 * hashCode 通常是將地址轉換為整數來實現的。 ### String s1 = new String("abc") 在記憶體中建立了幾個物件 一個或者兩個,String s1 是聲明瞭一個 String 型別的 s1 變數,它不是物件。使用 `new` 關鍵字會在堆中建立一個物件,另外一個物件是 `abc` ,它會在常量池中建立,所以一共建立了兩個物件;如果 abc 在常量池中已經存在的話,那麼就會建立一個物件。 詳細請翻閱筆者的另外一篇文章 [一篇與眾不同的 String、StringBuffer、StringBuilde 詳解](https://mp.weixin.qq.com/s?__biz=MzI0ODk2NDIyMQ==&mid=2247484794&idx=1&sn=22efd808fa5a9e68cacabd4b6e08fdc3&chksm=e999f068deee797eef9b46b160c06afa4d50e03b3626d1ae1aad05ddc37ec9001c4514264e0f&token=1065926980&lang=zh_CN#rd) ### String 為什麼是不可變的、jdk 原始碼中的 String 如何定義的、為什麼這麼設計。 首先了解一下什麼是`不可變物件`,不可變物件就是一經建立後,其物件的內部狀態不能被修改,啥意思呢?也就是說不可變物件需要遵守下面幾條原則 * 不可變物件的內部屬性都是 final 的 * 不可變物件的內部屬性都是 private 的 * 不可變物件不能提供任何可以修改內部狀態的方法、setter 方法也不行 * 不可變物件不能被繼承和擴充套件 與其說問 String 為什麼是不可變的,不如說如何把 String 設計成不可變的。 String 類是一種物件,它是獨立於 Java 基本資料型別而存在的,String 你可以把它理解為字串的集合,String 被設計為 final 的,表示 String 物件一經建立後,它的值就不能再被修改,任何對 String 值進行修改的方法就是重新建立一個字串。String 物件建立後會存在於執行時常量池中,執行時常量池是屬於方法區的一部分,JDK1.7 後把它移到了堆中。 不可變物件不是真的不可變,可以通過`反射`來對其內部的屬性和值進行修改,不過一般我們不這樣做。 ### static 關鍵字是幹什麼用的?談談你的理解 static 是 Java 中非常重要的關鍵字,static 表示的概念是 `靜態的`,在 Java 中,static 主要用來 * 修飾變數,static 修飾的變數稱為`靜態變數`、也稱為`類變數`,類變數屬於類所有,對於不同的類來說,static 變數只有一份,static 修飾的變數位於方法區中;static 修飾的變數能夠直接通過 **類名.變數名** 來進行訪問,不用通過例項化類再進行使用。 * 修飾方法,static 修飾的方法被稱為`靜態方法`,靜態方法能夠直接通過 **類名.方法名** 來使用,在靜態方法內部不能使用非靜態屬性和方法 * static 可以修飾程式碼塊,主要分為兩種,一種直接定義在類中,使用 `static{}`,這種被稱為`靜態程式碼塊`,一種是在類中定義`靜態內部類`,使用 `static class xxx` 來進行定義。 * static 可以用於靜態導包,通過使用 `import static xxx` 來實現,這種方式一般不推薦使用 * static 可以和單例模式一起使用,通過雙重檢查鎖來實現執行緒安全的單例模式。 詳情請參考這篇文章 [一篇 static 還能難得住我?](https://mp.weixin.qq.com/s?__biz=MzI0ODk2NDIyMQ==&mid=2247484455&idx=1&sn=582d5d2722dab28a36b6c7bc3f39d3fb&chksm=e999f135deee7823226d4da1e8367168a3d0ec6e66c9a589843233b7e801c416d2e535b383be&token=1154740235&lang=zh_CN#rd) ### final 關鍵字是幹什麼用的?談談你的理解 final 是 Java 中的關鍵字,它表示的意思是 `不可變的`,在 Java 中,final 主要用來 * 修飾類,final 修飾的類不能被繼承,不能被繼承的意思就是不能使用 `extends` 來繼承被 final 修飾的類。 * 修飾變數,final 修飾的變數不能被改寫,不能被改寫的意思有兩種,對於基本資料型別來說,final 修飾的變數,其值不能被改變,final 修飾的物件,物件的引用不能被改變,但是物件內部的屬性可以被修改。final 修飾的變數在某種程度上起到了`不可變`的效果,所以,可以用來保護只讀資料,尤其是在併發程式設計中,因為明確的不能再為 final 變數進行賦值,有利於減少額外的同步開銷。 * 修飾方法,final 修飾的方法不能被重寫。 * final 修飾符和 Java 程式效能優化沒有必然聯絡 ### 抽象類和介面的區別是什麼 抽象類和介面都是 Java 中的關鍵字,抽象類和介面中都允許進行方法的定義,而不用具體的方法實現。抽象類和介面都允許被繼承,它們廣泛的應用於 JDK 和框架的原始碼中,來實現多型和不同的設計模式。 不同點在於 * `抽象級別不同`:類、抽象類、介面其實是三種不同的抽象級別,抽象程度依次是 介面 > 抽象類 > 類。在介面中,只允許進行方法的定義,不允許有方法的實現,抽象類中可以進行方法的定義和實現;而類中只允許進行方法的實現,我說的方法的定義是不允許在方法後面出現 `{}` * `使用的關鍵字不同`:類使用 `class` 來表示;抽象類使用 `abstract class` 來表示;介面使用 `interface` 來表示 * `變數`:介面中定義的變數只能是公共的靜態常量,抽象類中的變數是普通變數。 ### 重寫和過載的區別 在 Java 中,重寫和過載都是對同一方法的不同表現形式,下面我們針對重寫和過載做一下簡單的區分 * `子父級關係不同`,重寫是針對子級和父級的不同表現形式,而過載是在同一類中的不同表現形式; * `概念不同`,子類重寫父類的方法一般使用 `@override` 來表示;重寫後的方法其方法的宣告和引數型別、順序必須要與父類完全一致;過載是針對同一類中概念,它要求過載的方法必須滿足下面任何一個要求:方法引數的順序,引數的個數,引數的型別任意一個保持不同即可。 ### byte的取值範圍是多少,怎麼計算出來的 byte 的取值範圍是 -128 -> 127 之間,一共是 256 位。一個 byte 型別在計算機中佔據一個位元組,那麼就是 8 bit,所以最大就是 2^8 = 1111 1111。 Java 中用`補碼`來表示二進位制數,補碼的最高位是符號位,最高位用 0 表示正數,最高位 1 表示負數,正數的補碼就是其`本身`,由於最高位是符號位,所以正數表示的就是 0111 1111 ,也就是 127。最大負數就是 1111 1111,這其中會涉及到兩個 0 ,一個 +0 ,一個 -0 ,+0 歸為正數,也就是 0 ,-0 歸為負數,也就是 -128,所以 byte 的範圍就是 -128 - 127。 ### HashMap 和 HashTable 的區別 **相同點** HashMap 和 HashTable 都是基於雜湊表實現的,其內部每個元素都是 `key-value` 鍵值對,HashMap 和 HashTable 都實現了 Map、Cloneable、Serializable 介面。 **不同點** * 父類不同:HashMap 繼承了 `AbstractMap` 類,而 HashTable 繼承了 `Dictionary` 類 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200612142351219-1000398055.png) * 空值不同:HashMap 允許空的 key 和 value 值,HashTable 不允許空的 key 和 value 值。HashMap 會把 Null key 當做普通的 key 對待。不允許 null key 重複。 ![](https://img2020.cnblogs.com/blog/1515111/202006/1515111-20200612142359164-1314465483.png) * 執行緒安全性:HashMap 不是執行緒安全的,如果多個外部操作同時修改 HashMap 的資料結構比如 add 或者是 delete,必須進行同步操作,僅僅對 key 或者 value 的修改不是改變資料結構的操作。可以選擇構造執行緒安全的 Map 比如 `Collections.synchronizedMap` 或者是 `ConcurrentHashMap`。而 HashTable 本身就是執行緒安全的容器。 * 效能方面:雖然 HashMap 和 HashTable 都是基於`單鏈表`的,但是 HashMap 進行 put 或者 get