Java初中級面試題及答案

Java知音發表於2018-08-10

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、專案中用到了什麼設計模式(也可能虛擬碼)

設計模式專欄:www.javazhiyin.com/category/sj…


相關文章