Java 初級面試題及答案


1、Java中的重載與重寫有什么區別

重載(Overload)是讓類以統一的方式處理不同類型數據的一種手段,實質表現就是多個具有不同的參數個數或者類型的同名函數(返回值類型可隨意,不能以返回類型作為重載函數的區分標准)同時存在於同一個類中,是一個類中多態性的一種表現(調用方法時通過傳遞不同參數個數和參數類型來決定具體使用哪個方法的多態性)。

重寫(Override)是父類與子類之間的多態性,實質是對父類的函數進行重新定義,如果在子類中定義某方法與其父類有相同的名稱和參數則該方法被重寫,不過子類函數的訪問修飾權限不能小於父類的;若子類中的方法與父類中的某一方法具有相同的方法名、返回類型和參數表,則新方法將覆蓋原有的方法,如需父類中原有的方法則可使用 super 關鍵字。

重載:

必須具有不同的參數列表;

可以有不同的返回類型;

可以有不同的訪問修飾符;

可以拋出不同的異常。

重寫:

參數列表必須完全與被重寫的方法相同,否則不能稱其為重寫而是重載;

返回類型必須一直與被重寫的方法相同,否則不能稱其為重寫而是重載;

訪問修飾符的限制一定要大於等於被重寫方法的訪問修飾符;

重寫方法一定不能拋出新的檢查異常或者比被重寫方法申明更加寬泛的檢查型異常。

重載與重寫是 Java 多態性的不同表現,重寫是父類與子類之間多態性的表現,在運行時起作用(動態多態性,譬如實現動態綁定),而重載是一個類中多態性的表現,在編譯時起作用(靜態多態性,譬如實現靜態綁定)。

 

2、 Java 中 final、finally、finalize 的區別

final 是一個修飾符,如果一個類被聲明為 final 則其不能再派生出新的子類,所以一個類不能既被聲明為 abstract 又被聲明為 final 的;將變量或方法聲明為 final 可以保證它們在使用中不被改變(對於對象變量來說其引用不可變,即不能再指向其他的對象,但是對象的值可變),被聲明為 final 的變量必須在聲明時給定初值,而在以后的引用中只能讀取不可修改,被聲明為 final 的方法也同樣只能使用不能重載。

使用 final 關鍵字如果編譯器能夠在編譯階段確定某變量的值則編譯器就會把該變量當做編譯期常量來使用,如果需要在運行時確定(譬如方法調用)則編譯器就不會優化相關代碼;將類、方法、變量聲明為 final 能夠提高性能,這樣 JVM 就有機會進行估計並進行優化;接口中的變量都是 public static final 的。

finally 用來在異常處理時提供塊來執行任何清除操作,如果拋出一個異常,則相匹配的 catch 子句就會執行,然后控制就會進入 finally 塊。

finalize 是一個方法名,Java 允許使用 finalize() 方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作,這個方法是由垃圾收集器在確定這個對象沒有被引用時對這個對象調用的,它是在 Object 類中定義的,因此所有的類都繼承了它,子類覆蓋 finalize() 方法以整理系統資源或者執行其他清理工作,finalize() 方法在垃圾收集器刪除對象之前對這個對象調用的。

參考:

你以為你真的了解final嗎?

Java final關鍵字

 

3、Java 中 hashCode() 的作用

hashCode() 的作用是為了提高在散列結構存儲中查找的效率,在線性表中沒有作用;

只有每個對象的 hash 碼盡可能不同才能保證散列的存取性能,事實上 Object 類提供的默認實現確實保證每個對象的 hash 碼不同(在對象的內存地址基礎上經過特定算法返回一個 hash 碼)。

在 Java 有些集合類(HashSet)中要想保證元素不重復可以在每增加一個元素就通過對象的 equals 方法比較一次,那么當元素很多時后添加到集合中的元素比較的次數就非常多了,會大大降低效率。

於是 Java 采用了哈希表的原理,這樣當集合要添加新的元素時會先調用這個元素的 hashCode 方法就一下子能定位到它應該放置的物理位置上(實際可能並不是),如果這個位置上沒有元素則它就可以直接存儲在這個位置上而不用再進行任何比較了,如果這個位置上已經有元素了則就調用它的 equals 方法與新元素進行比較,相同的話就不存,不相同就散列其它的地址,這樣一來實際調用 equals 方法的次數就大大降低了,幾乎只需要一兩次,而 hashCode 的值對於每個對象實例來說是一個固定值。

參考:

詳解equals()方法和hashCode()方法

淺談Java中的hashcode方法

 

4、抽象類(abstract class)和接口(interface)有什么區別

含有 abstract 修飾符的 class 為抽象類,abstract 類不能創建實例對象,含有 abstract 方法的類必須定義為 abstract class,abstract class 類中的方法不必是抽象的。

abstract class 類中定義的抽象方法必須在具體的子類中實現,所以不能有抽象構造方法或抽象靜態方法,如果子類沒有實現抽象父類中的所有抽象方法則子類也必須定義為 abstract 類型。

對於接口可以說是抽象類的一種特例,接口中的所有方法都必須是抽象的(接口中的方法定義默認為 public abstract 類型,接口中的成員變量類型默認為 public static final)。

具體的區別如下:

  • 抽象類可以有構造方法;接口中不能有構造方法。
  • 抽象類中可以有普通成員變量或者常量或者靜態變量;接口中沒有普通成員變量和靜態變量,只能是常量(默認修飾符為 publci static final)。
  • 抽象類中可以包含非抽象的普通方法和抽象方法及靜態方法;接口中的所有方法必須都是抽象的,不能有非抽象的普通方法和靜態方法(默認修飾符為 public abstract)。
  • 抽象類中的抽象方法訪問類型可以是 public、protected 的;接口中的抽象方法只能是 public 的(默認修飾符為 public abstract)。一個子類可以實現多個接口,但只能繼承一個抽象類。

參考:

深入理解Java的接口和抽象類

Java 內部類詳解

詳解匿名內部類

 

5、為什么 ArrayList 的增加或刪除操作相對來說效率比較低

ArrayList 在小於擴容容量的情況下其實增加操作效率是非常高的,在涉及擴容的情況下添加操作效率確實低,刪除操作需要移位拷貝,效率是低點。

因為 ArrayList 中增加(擴容)或者是刪除元素要調用 System.arrayCopy 這種效率很低的方法進行處理,所以如果遇到了數據量略大且需要頻繁插入或刪除的操作效率就比較低了,具體可查看 ArrayList 的 add 和 remove 方法實現,但是 ArrayList 頻繁訪問元素的效率是非常高的,因此遇到類似場景我們應該盡可能使用 LinkedList 進行替代效率會高一些。

參考:

Java集合系列[1]ArrayList源碼分析

ArrayList和LinkedList的區別

 

6、 LinkedList 工作原理和實現

LinkedList 是以雙向鏈表實現,鏈表無容量限制(但是雙向鏈表本身需要消耗額外的鏈表指針空間來操作),其內部主要成員為 first 和 last 兩個 Node 節點,在每次修改列表時用來指引當前雙向鏈表的首尾部位。

所以 LinkedList 不僅僅實現了 List 接口,還實現了 Deque 雙端隊列接口(該接口是 Queue 隊列的子接口),故 LinkedList 自動具備雙端隊列的特性,當我們使用下標方式調用列表的 get(index)、set(index, e) 方法時需要遍歷鏈表將指針移動到位進行訪問(會判斷 index 是否大於鏈表長度的一半決定是首部遍歷還是尾部遍歷,訪問的復雜度為 O(N/2)),無法像 ArrayList 那樣進行隨機訪問。

(如果i>數組大小的一半,會從末尾移起),只有在鏈表兩頭的操作(譬如 add()、addFirst()、removeLast() 或用在 iterator() 上的 remove() 操作)才不需要進行遍歷尋找定位。具體感興趣可以去看下 LinkedList 的源碼。

參考:

Java集合系列[2]LinkedList源碼分析

ArrayList和LinkedList的區別

Java 集合類詳解

 

7、介紹 HashMap 的底層原理

當我們往 HashMap 中 put 元素時,先根據 key 的 hash 值得到這個 Entry 元素在數組中的位置(即下標),然后把這個 Entry 元素放到對應的位置中,如果這個 Entry 元素所在的位子上已經存放有其他元素就在同一個位子上的 Entry 元素以鏈表的形式存放,新加入的放在鏈頭,從 HashMap 中 get  Entry 元素時先計算 key 的 hashcode,找到數組中對應位置的某一 Entry 元素,然后通過 key 的 equals 方法在對應位置的鏈表中找到需要的 Entry 元素。

所以 HashMap 的數據結構是數組和鏈表的結合,此外 HashMap 中 key 和 value 都允許為 null,key 為 null 的鍵值對永遠都放在以 table[0] 為頭結點的鏈表中。

之所以 HashMap 這么設計的實質是由於數組存儲區間是連續的,占用內存嚴重,故空間復雜度大,但二分查找時間復雜度小(O(1)),所以尋址容易而插入和刪除困難;而鏈表存儲區間離散,占用內存比較寬松,故空間復雜度小,但時間復雜度大(O(N)),所以尋址困難而插入和刪除容易;

所以就產生了一種新的數據結構叫做哈希表,哈希表既滿足數據的查找方便,同時不占用太多的內容空間,使用也十分方便,哈希表有多種不同的實現方法,HashMap 采用的是鏈表的數組實現方式。

對於 JDK 1.8 開始 HashMap 實現原理變成了數組+鏈表+紅黑樹的結構,數組鏈表部分基本不變,紅黑樹是為了解決哈希碰撞后鏈表索引效率的問題,所以在 JDK 1.8 中當鏈表的節點大於 8 個時就會將鏈表變為紅黑樹。

區別是 JDK 1.8 以前碰撞節點會在鏈表頭部插入,而 JDK 1.8 開始碰撞節點會在鏈表尾部插入,對於擴容操作后的節點轉移 JDK 1.8 以前轉移前后鏈表順序會倒置,而 JDK 1.8 中依然保持原序。

參考:

Java集合系列[3]HashMap源碼分析

LinkedHashMap和HashMap的比較使用

Java集合系列[4]LinkedHashMap源碼分析

 

8、Hashtable 與 HashMap 的區別

Hashtable 算是一個過時的集合類,因為 JDK1.5 中提供的 ConcurrentHashMap 是 HashTable 的替代品,其擴展性比 HashTable 更好。由於 HashMap 和 Hashtable 都實現了 Map 接口,所以其主要的區別如下:

  1. HashMap 是非 synchronized 的,而 Hashtable 是 synchronized 的。
  2. HashMap 可以接受 null 的鍵和值,而 Hashtable 的 key 與 value 均不能為 null 值。
  3. HashMap 的迭代器 Iterator 是 fail-fast 機制的,而 Hashtable 的 Enumerator 迭代器不是 fail-fast 機制的(歷史原因)。
  4. 單線程情況下使用 HashMap 性能要比 Hashtable 好,因為 HashMap 是沒有同步操作的。
  5. Hashtable 繼承自 Dictionary 類且實現了 Map 接口,而 HashMap 繼承自 AbstractMap 類且實現了 Map 接口。
  6. HashTable 的默認容量為11,而 HashMap 為 16(安卓中為 4)。
  7. Hashtable 不要求底層數組的容量一定是 2 的整數次冪,而 HashMap 則要求一定為 2 的整數次冪。
  8. Hashtable 擴容時將容量變為原來的 2 倍加 1,而 HashMap 擴容時將容量變為原來的 2 倍。
  9. Hashtable 有 contains 方法,而 HashMap 有 containsKey 和 containsValue 方法。

參考:

HashMap和HashTable到底哪不同?

 

9、 java 類加載器的理解及加載機制

通過 java 命令運行 java 程序的步驟就是指定包含 main 方法的完整類名以及一個 classpath 類路徑,類路徑可以有多個,對於直接的 class 文件路徑就是 class 文件的根目錄,對於 jar 包文件路徑是 jar 包的完整路徑,包含 jar 包名字;

java 運行時會根據類的完全限定名尋找並加載,尋找的方式基本就是在系統類和指定的路徑中尋找,如果是 class 文件的根目錄則直接查看是否有對應的子目錄及文件,如果是 jar 包則首先在內存中解壓文件,然后再查看是否有對應的類;

負責類加載的類就是 ClassLoader 類加載器,它的輸入是完全限定的類名,輸出是 Class 對象,java 虛擬機中可以安裝多個類加載器,系統默認主要有三個類加載器,每個類負責加載特定位置的類,也可以自定義類加載器,自定義的加載器必須繼承 ClassLoader,如下:

啟動類加載器(Bootstrap ClassLoader):此加載器為虛擬機實現的一部分,不是 java 語言上層實現的,一般為 C++ 實現,主要負責加載 java 基礎類(譬如<JAVA_HOME>/lib/rt.jar,常用的 String、List 等都位於此包下),啟動類加載器無法被 java 程序直接引用。

擴展類加載器(Extension ClassLoader):此加載器實現類為 sun.misc.Launcher$ExtClassLoader,負責加載 java 的一些擴展類(一般為<JAVA_HOME>/lib/ext目錄下的 jar 包),開發者可直接使用。

應用程序類加載器(Application ClassLoader):此加載器實現類為 sun.misc.Launcher$AppClassLoader,負責加載應用程序的類,包括自己寫的和引用的第三方類庫,即 classpath 類路徑中指定的類,開發者可直接使用,一個程序運行時會創建一個這個加載器,程序中用到加載器的地方如果沒有特殊指定一般都是這個加載器,所以也被稱為 System 系統類加載器。

這三個加載器具備父子委派關系(非繼承父子關系),在 java 中每個類都是由某個類加載器的實體來載入的,所以在 Class 類的實體中都會有字段記錄載入它的類加載器的實體(當為 null 時,其指 Bootstrap ClassLoader),在 java 類加載器中除了引導類加載器(既 Bootstrap ClassLoader)。

所有的類加載器都有一個父類加載器(因為他們本身自己就是 java 類),子 ClassLoader 有一個變量 parent 指向父 ClassLoader,在子 ClassLoader 加載類時一般會先通過父 ClassLoader 加載,所以在加載一個 class 文件時首先會判斷是否已經加載過了,加載過則直接返回 Class 對象(一個類只會被一個 ClassLoader 加載一次)。

沒加載過則先讓父 ClassLoader 去加載,如果加載成功返回得到的 Class 對象,父沒有加載成功則嘗試自己加載,自己加載不成功則拋出 ClassNotFoundException,整個加載流程就是雙親委派模型,即優先讓父 ClassLoader 加載;雙親委派可以從優先級的策略上避免 Java 類庫被覆蓋的問題。

例如類 java.long.Object 存放在 rt.jar 中,無論哪個類加載器要加載這個類最終都會委派給啟動類加載器進行加載,因此 Object 類在程序的各種類加載器環境中都是同一個類,相反如果我們自己寫了一個類名為 java.long.Object 且放在了程序的 classpath 中,那系統中將會出現多個不同的 Object 類,java 類型體系中最基礎的行為也無法保證,所以一般遵循雙親委派的加載器就不會存在這個問題。

類加載機制中的雙親委派模型只是一般情況下的機制,有些時候我們可以自定義加載順序(不建議)就不用遵守雙親委派模型了,同時以 java 開頭的類也不能被自定義類加載器加載,這是 java 安全機制保證的;

ClassLoader 一般是系統提供的,不需要自己實現,不過通過自定義 ClassLoader 可以實現一些靈活強大的功能,譬如熱部署(不重啟 Java 程序的情況下動態替換類實現)、應用的模塊化和隔離化(不同 ClassLoader 可以加載相同的類,但是互相隔離互不影響,tomcat 就是利用這個特性管理多 web 應用的)、靈活加載等,通過自定義類加載器我們可以加載其它位置的類或 jar,自定義類加載器主要步驟為繼承 java.lang.ClassLoader 然后重寫父類的 findClass 方法。

之所以一般只重寫這一個方法是因為 JDK 已經在 loadClass 方法中幫我們實現了 ClassLoader 搜索類的算法,當在 loadClass 方法中搜索不到類時 loadClass 方法會主動調用 findClass 方法來搜索類,所以我們只需重寫該方法即可,如沒有特殊的要求,一般不建議重寫 loadClass 搜索類的算法。

JVM 在判定兩個 Class 是否相同時不僅會判斷兩個類名是否相同而且會判斷是否由同一個類加載器實例加載的,只有兩者同時滿足的情況下 JVM 才認為這兩個 Class 是相同的,就算兩個 Class 是同一份 class 字節碼文件,如果被兩個不同的 ClassLoader 實例所加載 JVM 也會認為它們是兩個不同 Class。

而對於 JVM 來說它們是兩個不同的實例對象,但它們確實是同一份字節碼文件,當試圖將這個 Class 實例生成具體的對象進行轉換時就會拋運行時異常 java.lang.ClassCaseException 提示這是兩個不同的類型。此外一個 ClassLoader 創建時如果沒有指定 parent 則 parent 默認就是 AppClassLoader。

參考:

類加載器詳解

 

10、Java 中 sleep() 與 wait() 方法的區別

sleep() 方法使當前線程進入停滯狀態(阻塞當前線程),讓出 CUP 的使用,目的是不讓當前線程獨自霸占該進程所獲的 CPU 資源。該方法是 Thread 類的靜態方法,當在一個 synchronized 塊中調用 sleep() 方法時,線程雖然休眠了,但是其占用的鎖並沒有被釋放;當 sleep() 休眠時間期滿后,該線程不一定會立即執行,因為其它線程可能正在運行而且沒有被調度為放棄執行,除非此線程具有更高的優先級。

wait() 方法是 Object 類的,當一個線程執行到 wait() 方法時就進入到一個和該對象相關的等待池中,同時釋放對象的鎖(對於 wait(long timeout) 方法來說是暫時釋放鎖,因為超時時間到后還需要返還對象鎖),其他線程可以訪問。wait() 使用 notify() 或 notifyAll() 或者指定睡眠時間來喚醒當前等待池中的線程。wait() 必須放在 synchronized 塊中使用,否則會在運行時拋出 IllegalMonitorStateException 異常。

由此可以看出它們之間的區別如下:

  1. sleep() 不釋放同步鎖,wait() 釋放同步鎖。
  2. sleep(milliseconds) 可以用時間指定來使他自動醒過來,如果時間沒到則只能調用 interreput() 方法來強行打斷(不建議,會拋出 InterruptedException),而 wait() 可以用 notify() 直接喚起。
  3. sleep() 是 Thread 的靜態方法,而 wait() 是 Object 的方法。
  4. wait()、notify()、notifyAll() 方法只能在同步控制方法或者同步控制塊里面使用,而 sleep() 方法可以在任何地方使用。

多線程與並發參考:

Java 並發編程簡介

並發編程的優缺點

線程的狀態轉換以及基本操作

Java並發專欄

Java多線程和線程池

多線程的優點

JAVA多線程實現和應用總結

 

11、對 ClassLoader 的理解

ClassLoader 的作用是根據一個指定的類名稱找到或者生成其對應的字節代碼,然后把字節碼轉換成一個 Java 類(即 java.lang.Class 實例),除此之外還負責加載 Java 應用所需的資源、Native lib 庫等。

Java 的類加載器大致可以分成系統類加載器和應用開發自定義類加載器。系統類加載器主要有如下幾個:

  1. 引導類加載器(bootstrap class loader):用來加載 Java 核心庫,是虛擬機中用原生代碼實現的,沒有繼承自 ClassLoader。
  2. 擴展類加載器(extensions class loader):用來加載 Java 的擴展庫,虛擬機的實現會提供一個默認的擴展庫目錄,該類加載器在此目錄里面查找並加載 Java 類。
  3. 系統類加載器(system class loader):用來加載應用類路徑(CLASSPATH)下的 class,一般來說 Java 應用的類都是由它來完成加載的,可以通過 ClassLoader.getSystemClassLoader() 來獲取它。

除了引導類加載器之外,所有的其他類加載器都有一個父類加載器(可以通過 ClassLoader 的 getParent() 方法得到)。系統類加載器的父類加載器是擴展類加載器,而擴展類加載器的父類加載器是引導類加載器。

開發自定義的類加載器的父類加載器是加載此類加載器的 Java 類的類加載器。所以類加載器在嘗試自己去加載某個類時會先通過 getParent() 代理給其父類加載器,由父類加載器先去嘗試加載這個類,依次類推,從而形成了雙親委派模式。類加載機制是通過 loadClass 方法觸發的,查找類有沒有被加載和該代理給哪個層級的加載器加載是由 findClass 方法實現的,而真正完成類加載工作是 defineClass 方法實現的。

 

12、ArrayList和Vector有何異同點?

ArrayList和Vector在很多時候都很類似。
(1)兩者都是基於索引的,內部由一個數組支持。
(2)兩者維護插入的順序,我們可以根據插入順序來獲取元素。
(3)ArrayList和Vector的迭代器實現都是fail-fast的。
(4)ArrayList和Vector兩者允許null值,也可以使用索引值對元素進行隨機訪問。

以下是ArrayList和Vector的不同點。
(1)Vector是同步的,而ArrayList不是。然而,如果你尋求在迭代的時候對列表進行改變,你應該使用CopyOnWriteArrayList。
(2)ArrayList比Vector快,它因為有同步,不會過載。
(3)ArrayList更加通用,因為我們可以使用Collections工具類輕易地獲取同步列表和只讀列表。

 

13、SpringMVC運行原理

  1. 客戶端請求提交到DispatcherServlet
  2. 由DispatcherServlet控制器查詢HandlerMapping,找到並分發到指定的Controller中。
  3. Controller調用業務邏輯處理后,返回ModelAndView
  4. DispatcherServlet查詢一個或多個ViewResoler視圖解析器,找到ModelAndView指定的視圖
  5. 視圖負責將結果顯示到客戶端

參考:

SpringMVC架構淺析

SpringMVC工作原理

自己手寫一個SpringMVC框架

手寫spring+springmvc+mybatis框架篇【springmvc】

 

14、說說熟悉的排序算法(很可能手寫偽代碼)

基數排序

堆排序

歸並排序

選擇排序

拓撲排序之Java詳解

希爾排序

直接插入排序

快速排序

冒泡排序

 

15、項目中用到了什么設計模式(也可能偽代碼)

設計模式專欄:https://www.javazhiyin.com/category/sjms


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM