1. 程式人生 > >常見面試題及答案(一)

常見面試題及答案(一)

1. 什麼是 Java 虛擬機器?為什麼 Java 被稱作是 “ 平臺無關的程式語言 ” ?

Java 虛擬機器是一個可以執行 Java 位元組碼的虛擬機器程序。 Java 原始檔被編譯成能被 Java 虛擬機器執行的位元組碼檔案。 
Java 
被設計成允許應用程式可以執行在任意的平臺,而不需要程式設計師為每一個平臺單獨重寫或者是重新編譯。 
Java 
虛擬機器讓這個變為可能,因為它知道底層硬體平臺的指令長度和其他特性。

2.JDK 和 JRE 的區別是什麼?

JDK:  java 開發工具包 , 包含了 JRE 、編譯器和其它工具(如: javaDOc  java 偵錯程式 )
JRE: 

 java 執行環境 , 包含 java 虛擬機器和 java 程式所需的核心類庫。 如果只是想跑 java 程式,那麼只需安裝 JRE ,如果要寫 java 程式並且執行,那就需要 JDK 了。

3."static" 關鍵字是什麼意思? Java 中是否可以覆蓋一個 private 或者是 static 的方法?

如果一個類的變數或者方法前面有 static 修飾,那麼表明這個方法或者變數屬於這個類,也就是說可以在不建立物件的情況下直接使用 當父類的方法被 private 修飾時,表明該方法為父類私有,對其他任何類都是不可見的,因此如果子類定了一個與父類一樣的方法,這對於子類來說相當於是一個新的私有方法,且如果要進行向上轉型,然後去呼叫該

  覆蓋方法  ,會產生編譯錯誤

class Parent {
    private fun() {
        ...
    }   
}
class Child extends Parent {
    private fun() {
        ...
    }
}
class Test {
    public static void main(String[] args) {
        Parent c = new Child();
        c.fun(); //編譯出錯
    }
}

static 方法時編譯時靜態繫結的,屬於類,而覆蓋是執行時動態繫結的

 ( 動態繫結的多型 ), 因此不能覆蓋.

4.Java 支援的基本資料型別有哪些?什麼是自動拆裝箱?

java 支援的基本資料型別有以下 9  :byte,shot,int,long,float,double,char,boolean,void.
自動拆裝箱是 java  jdk1.5 引用,目的是將原始型別自動的裝換為相對應的物件,也可以逆向進行,即拆箱。這也體現 java 中一切皆物件的宗旨。 所謂自動裝箱就是將原始型別自動的轉換為對應的物件,而拆箱就是將物件型別轉換為基本型別。 java中的自動拆裝箱通常發生在變數賦值的過程中,如:

    Integer object = 3; //自動裝箱
    int o = object; //拆箱

 java 中,應該注意自動拆裝箱,因為有時可能因為 java 自動裝箱機制,而導致建立了許多物件,對於記憶體小的平臺會造成壓力。

5. 覆蓋和過載是什麼 ?

覆蓋 也叫 重寫 ,發生在子類與父類之間,表示子類中的方法可以與父類中的某個方法的名稱和引數完全相同,通過子類建立的例項物件呼叫這個方法時,將呼叫子類中的定義方法,這相當於把父類中定義的那個完全相同的方法給覆蓋了,這也是面向物件程式設計的多型性的一種表現。 過載 是指在一個類中,可以有多個相同名稱的方法,但是他們的引數列表的個數或型別不同,當呼叫該方法時,根據傳遞的引數型別呼叫對應引數列表的方法。當引數列表相同但返回值不同時,將會出現編譯錯誤,這並不是過載,因為 jvm 無法根據返回值型別來判斷應該呼叫哪個方法。

6.Java 支援多繼承麼?如果不支援,如何實現 ?

 java 中是單繼承的,也就是說一個類只能繼承一個父類。 
java
 中實現多繼承有兩種方式 , 一是介面,而是內部類 .

//實現多個介面 如果兩個介面的變數相同 那麼在呼叫該變數的時候 編譯出錯
interface interface1 {
    static String field = "dd";
    public void fun1();
    }
interface interface2 {
static String field = "dddd";
    public void fun2();
    }
class child implements interface1,interface2 {
    static String field = "dddd";
    @Override
    public void fun2() {
    }
    @Override
    public void fun1() {
    }   
}
//內部類 間接多繼承
class Child {
class Father {
    private void strong() {
        System.out.println("父類");
    }
}
class Mother {
    public void getCute() {
        System.out.println("母親");
    }
}
public void getStrong() {
    Father f = new Father();
    f.strong();
    }
public void getCute() {
    Mother m = new Mother();
    m.getCute();
    }
}

7. 什麼是值傳遞和引用傳遞? java 中是值傳遞還是引用傳遞,還是都有 ?

值傳遞  就是在方法呼叫的時候,實參是將自己的一份拷貝賦給形參,在方法內,對該引數值的修改不影響原來實參,常見的例子就是剛開始學習 c 語言的時候那個交換方法的例子了。 引用傳遞  是在方法呼叫的時候,實參將自己的地址傳遞給形參,此時方法內對該引數值的改變,就是對該實參的實際操作。  java 中只有一種傳遞方式,那就是 值傳遞 . 可能比較讓人迷惑的就是 java 中的物件傳遞時,對形參的改變依然會意向到該物件的內容。 下面這個例子來說明 java 中是值傳遞 .

    public class Test {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("hello ");
        getString(sb);
        System.out.println(sb);
    }
    public static void getString(StringBuffer s) {
        //s = new StringBuffer("ha");
        s.append("world");
    }
}

在上面這個例子中 , 當前輸出結果為 :hello world 。這並沒有什麼問題,可能就是大家平常所理解的引用傳遞,那麼當然會改變 StringBuffer 的內容。但是如果把上面的註釋去掉,那麼就會輸出 :hello. 此時 sb的值並沒有變成 ha hello. 假如說是引用傳遞的話,那麼形參的 s 也就是 sb 的地址,此時在方法裡 new StringBuffer (),並將該物件賦給 s ,也就是說 s 現在指向了這個新建立的物件 . 按照引用傳遞的說法,此時對 s 的改變就是對 sb 的操作,也就是說 sb 應該也指向新建立的物件,那麼輸出的結果應該為ha world. 但實際上輸出的僅是 hello. 這說明 sb 指向的還是原來的物件,而形參 s 指向的才是建立的物件, 這也就驗證了 java 中的物件傳遞也是值傳遞。

8. 介面和抽象類的區別是什麼 ?

不同點在於:

1.    介面中所有的方法隱含的都是抽象的。而抽象類則可以同時包含抽象和非抽象的方法。

2.    類可以實現很多個介面,但是隻能繼承一個抽象類

3.    類如果要實現一個介面,它必須要實現介面宣告的所有方法。但是,類可以不實現抽象類宣告的所有方法,當然,在這種情況下,類也必須得宣告成是抽象的。

4.    抽象類可以在不提供介面方法實現的情況下實現介面。

5.    Java 介面中宣告的變數預設都是 final 的。抽象類可以包含非 final 的變數。

6.    Java 介面中的成員函式預設是 public 的。抽象類的成員函式可以是 private  protected 或者是 public

7.    介面是絕對抽象的,不可以被例項化 (java 8 已支援在介面中實現預設的方法 ) 。抽象類也不可以被例項化,但是,如果它包含 main 方法的話是可以被呼叫的。

9. 構造器( constructor )是否可被重寫( override ) ?

構造方法是不能被子類重寫的,但是構造方法可以過載,也就是說一個類可以有多個構造方法。

10.Math.round(11.5) 等於多少 ? Math.round(-11.5) 等於多少 ?

Math.round(11.5)==12 Math.round(-11.5)==-11 round 方法返回與引數 最接近的長整數,引數加 1/2 後求其 floor.

11. String, StringBuffer StringBuilder 的區別。

tring 的長度是不可變的;

StringBuffer 的長度是可變的,如果你對字串中的內容經常進行操作,特別是內容要修改時,那麼使用StringBuffer ,如果最後需要 >String ,那麼使用 StringBuffer  toString() 方法;執行緒安全;

StringBuilder 是從 JDK 5 開始,為 StringBuffer 該類補充了一個單個執行緒使用的等價類;通常應該優先使用 StringBuilder 類,因 > 為它支援所有相同的操作,但由於它不執行同步,所以速度更快。 使用字串的時候要特別小心,如果對一個字串要經常改變的話,就一定不要用 String, 否則會建立許多無用的物件出來 來看一下比較

String s = "hello"+"world"+"i love you";
StringBuffer Sb = new StringBuilder("hello").append("world").append("i love you");

這個時候 s 有多個字串進行拼接,按理來說會有多個物件產生,但是 jvm 會對此進行一個優化,也就是說只建立了一個物件,此時它的執行速度要比 StringBuffer 拼接快 . 再看下面這個 :

String s2 = "hello"; 
String s3 = "world"; 
String s4 = "i love you"; 
String s1 = s2 + s3 + s4;

上面這種情況,就會多創建出來三個物件,造成了記憶體空間的浪費 .

12.JVM 記憶體分哪幾個區,每個區的作用是什麼 ?

java 虛擬機器主要分為以下一個區 :

方法區: 
1. 
有時候也成為 永久代 ,在該區內很少發生垃圾回收,但是並不代表不發生 GC ,在這裡進行的 GC 主要是對方法區裡的常量池和對型別的解除安裝 
2. 
方法區主要用來儲存已被虛擬機器載入的類的資訊、常量、靜態變數和即時編譯器編譯後的程式碼等資料。
3. 
該區域是被執行緒共享的。 
4. 
方法區裡有一個執行時常量池,用於存放靜態編譯產生的字面量和符號引用。該常量池具有動態性,也就是說常量並不一定是編譯時確定,執行時生成的常量也會存在這個常量池中。

虛擬機器棧 : 
1. 
虛擬機器棧也就是我們平常所稱的 棧記憶體 , 它為 java 方法服務,每個方法在執行的時候都會建立一個棧幀,用於儲存區域性變量表、運算元棧、動態連結和方法出口等資訊。 
2. 
虛擬機器棧是執行緒私有的,它的生命週期與執行緒相同。 
3. 
區域性變量表裡儲存的是基本資料型別、 returnAddress 型別(指向一條位元組碼指令的地址)和物件引用,這個物件引用有可能是指向物件起始地址的一個指標,也有可能是代表物件的控制代碼或者與物件相關聯的位置。區域性變數所需的記憶體空間在編譯器間確定 
4.
 運算元棧的作用主要用來儲存運算結果以及運算的運算元,它不同於區域性變量表通過索引來訪問,而是壓棧和出棧的方式 
5.
 每個棧幀都包含一個指向執行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支援方法呼叫過程中的動態連線 . 動態連結就是將常量池中的符號引用在執行期轉化為直接引用。

本地方法棧 本地方法棧和虛擬機器棧類似,只不過本地方法棧為 Native 方法服務。

 
java
 堆是所有執行緒所共享的一塊記憶體,在虛擬機器啟動時建立,幾乎所有的物件例項都在這裡建立,因此該區域經常發生垃圾回收操作。

程式計數器 記憶體空間小,位元組碼直譯器工作時通過改變這個計數值可以選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理和執行緒恢復等功能都需要依賴這個計數器完成。該記憶體區域是唯一一個 java 虛擬機器規範沒有規定任何 OOM 情況的區域。

13. 如和判斷一個物件是否存活 ?( 或者 GC 物件的判定方法 )

判斷一個物件是否存活有兩種方法 :
1.  
引用計數法 所謂引用計數法就是給每一個物件設定一個引用計數器,每當有一個地方引用這個物件時,就將計數器加一,引用失效時,計數器就減一。當一個物件的引用計數器為零時,說明此物件沒有被引用,也就是  死物件 ”, 將會被垃圾回收 .
引用計數法有一個缺陷就是無法解決迴圈引用問題,也就是說當物件 A 引用物件 B ,物件 B 又引用者物件 A ,那麼此時 A,B 物件的引用計數器都不為零,也就造成無法完成垃圾回收,所以主流的虛擬機器都沒有采用這種演算法。

2. 可達性演算法 ( 引用鏈法 )
該演算法的思想是:從一個被稱為 GC Roots 的物件開始向下搜尋,如果一個物件到 GC Roots 沒有任何引用鏈相連時,則說明此物件不可用。  java 中可以作為 GC Roots 的物件有以下幾種 :

·         虛擬機器棧中引用的物件

·         方法區類靜態屬性引用的物件

·         方法區常量池引用的物件

·         本地方法棧 JNI 引用的物件

雖然這些演算法可以判定一個物件是否能被回收,但是當滿足上述條件時,一個物件比 不一定會被回收 。當一個物件不可達 GC Root 時,這個物件並  不會立馬被回收 ,而是出於一個死緩的階段,若要被真正的回收需要經歷兩次標記 如果物件在可達性分析中沒有與 GC Root 的引用鏈,那麼此時就會被第一次標記並且進行一次篩選,篩選的條件是是否有必要執行 finalize() 方法。當物件沒有覆蓋 finalize() 方法或者已被虛擬機器呼叫過,那麼就認為是沒必要的。 如果該物件有必要執行 finalize() 方法,那麼這個物件將會放在一個稱為 F-Queue 的對佇列中,虛擬機器會觸發一個 Finalize() 執行緒去執行,此執行緒是低優先順序的,並且虛擬機器不會承諾一直等待它執行完,這是因為如果 finalize() 執行緩慢或者發生了死鎖,那麼就會造成 F-Queue 佇列一直等待,造成了記憶體回收系統的崩潰。 GC 對處於 F-Queue 中的物件進行第二次被標記,這時,該物件將被移除 " 即將回收 " 集合,等待回收。

14. 簡述 java 垃圾回收機制 ?

 java 中,程式設計師是不需要顯示的去釋放一個物件的記憶體的,而是由虛擬機器自行執行。在 JVM 中,有一個垃圾回收執行緒,它是低優先順序的,在正常情況下是不會執行的,只有在虛擬機器空閒或者當前堆記憶體不足時,才會觸發執行,掃面那些沒有被任何引用的物件,並將它們新增到要回收的集合中,進行回收。

15.java 中垃圾收集的方法有哪些 ?

1.    標記 - 清除 : 這是垃圾收集演算法中最基礎的,根據名字就可以知道,它的思想就是標記哪些要被回收的物件,然後統一回收。這種方法很簡單,但是會有兩個主要問題: 1. 效率不高,標記和清除的效率都很低; 2. 會產生大量不連續的記憶體碎片,導致以後程式在分配較大的物件時,由於沒有充足的連續記憶體而提前觸發一次 GC動作。

2.    複製演算法 : 為了解決效率問題,複製演算法將可用記憶體按容量劃分為相等的兩部分,然後每次只使用其中的一塊,當一塊記憶體用完時,就將還存活的物件複製到第二塊記憶體上,然後一次性清楚完第一塊記憶體,再將第二塊上的物件複製到第一塊。但是這種方式,記憶體的代價太高,每次基本上都要浪費一般的記憶體。 於是將該演算法進行了改進,記憶體區域不再是按照 1  1 去劃分,而是將記憶體劃分為 8:1:1 三部分,較大那份記憶體交 Eden 區,其餘是兩塊較小的記憶體區叫 Survior 區。每次都會優先使用 Eden 區,若 Eden 區滿,就將物件複製到第二塊記憶體區上,然後清除 Eden 區,如果此時存活的物件太多,以至於 Survivor 不夠時,會將這些物件通過分配擔保機制複製到老年代中。 (java 堆又分為新生代和老年代 )

3.    標記 - 整理 該演算法主要是為了解決標記 - 清除,產生大量記憶體碎片的問題;當物件存活率較高時,也解決了複製演算法的效率問題。它的不同之處就是在清除物件的時候現將可回收物件移動到一端,然後清除掉端邊界以外的物件,這樣就不會產生記憶體碎片了。 分代收集  現在的虛擬機器垃圾收集大多采用這種方式,它根據物件的生存週期,將堆分為新生代和老年代。在新生代中,由於物件生存期短,每次回收都會有大量物件死去,那麼這時就採用 複製 演算法。老年代裡的物件存活率較高,沒有額外的空間進行分配擔保,所以可以使用 標記 - 整理  或者  標記 - 清除 

16.java 記憶體模型

java 記憶體模型 (JMM) 是執行緒間通訊的控制機制 .JMM 定義了主記憶體和執行緒之間抽象關係。執行緒之間的共享變數儲存在主記憶體( main memory )中,每個執行緒都有一個私有的本地記憶體( local memory ),本地記憶體中儲存了該執行緒以讀 / 寫共享變數的副本。本地記憶體是 JMM 的一個抽象概念,並不真實存在。它涵蓋了快取,寫緩衝區,暫存器以及其他的硬體和編譯器優化。 Java 記憶體模型的抽象示意圖如下: 
 
從上圖來看,執行緒 A 與執行緒 B 之間如要通訊的話,必須要經歷下面 2 個步驟: 
1. 
首先,執行緒 A 把本地記憶體 A 中更新過的共享變數重新整理到主記憶體中去。 
2. 
然後,執行緒 B 到主記憶體中去讀取執行緒 A 之前已更新過的共享變數。 寫的很好 :http://www.infoq.com/cn/articles/java-memory-model-1

17.java 類載入過程 ?

java 類載入需要經歷一下 7 個過程: 載入 載入時類載入的第一個過程,在這個階段,將完成一下三件事情: 
1. 
通過一個類的全限定名獲取該類的二進位制流。 
2. 
將該二進位制流中的靜態儲存結構轉化為方法去執行時資料結構。 3. 在記憶體中生成該類的 Class 物件,作為該類的資料訪問入口。

驗證 驗證的目的是為了確保 Class 檔案的位元組流中的資訊不回危害到虛擬機器 . 在該階段主要完成以下四鍾驗證:
1. 
檔案格式驗證:驗證位元組流是否符合 Class 檔案的規範,如主次版本號是否在當前虛擬機器範圍內,常量池中的常量是否有不被支援的型別 .
2. 
元資