1. 程式人生 > 其它 >面試題總結(一)

面試題總結(一)

1. JDK、JRE、JVM的區別和聯絡

JDK(java程式開發包)=JRE +Tools

JRE=JVM(虛擬機器)+API

2. 採用位元組碼的好處

Java中引入了JVM,即在機器和編譯程式之間加了一層抽象的虛擬機器器,這臺機器在任何平臺上都提供給編譯程式一個共同的介面。

  • 編譯程式只需要面向虛擬機器,生成虛擬機器能夠理解的程式碼,然後由直譯器來將虛擬機器程式碼轉換為特定系統的機器碼來執行。在Java中,這種供虛擬機器理解的程式碼叫做位元組碼(.class),它不面向任何特定的處理器,只面向虛擬機器
  • 每一種平臺的直譯器是不同的,但是實現的虛擬機器是相同的。Java源程式通過編譯器進行編譯後轉換為位元組碼,位元組碼在虛擬機器上執行,虛擬機器將每一條要執行的位元組碼送給直譯器,直譯器將其翻譯成特定機器上的機器碼,然後在特定的機器上執行。這也解釋了Java的編譯與解釋共存的特點。
Java原始碼-->編譯器-->jvm可執行的java位元組碼-->jvm中直譯器-->機器可執行的二進位制機器碼-->程式執行

Java語言採用位元組碼的方式,一定程度上解決了傳統解釋型語言執行效率低(執行需要解釋環境,速度比編譯的要慢,佔用資源也要多一些)的問題,同時又保留了解釋型語言可移植的特點,所以Java程式執行時很高效,此外,由於位元組碼不針對一種特定的機器,因此Java源程式無需重新編譯即可在不同的計算機上執行,實現一次編譯,多次執行。

3. 介面和抽象類的區別

1️⃣從語法上來說

  • 抽象類可以存在普通成員函式,而介面中只能存在public abstract方法。

  • 抽象類中的成員變數可以是各種型別的,而介面中的成員變數只能是public static final型別的。

  • 抽象類只能繼承一個,介面可以實現多個

2️⃣從設計目的來說

  • 介面是用來對

    類的形為進行約束。也就是提供了一種機制,可以強制要求所有的類具有相同的形為,只約束了行為的有無,不限制形為的具體實現。
  • 抽象類是為了

    程式碼複用。當不同的類具有相同的行為A,且其中一部分形為B的實現方式一致時,可以讓這些類都派生於一個抽象類,這個抽象類中實現了B,避免讓所有的子類來實現B,以此來達到程式碼複用的目的。而A-B的部分,交給各個子類自己實現,正是由於這裡A-B的行為沒有實現,所以抽象類不允許例項化。

3️⃣從本質上來說

  • 介面

    對行為的抽象,表達的是like a的關係,比如 Bird like a Aircraft(鳥像飛行器一樣可以飛);介面的核心是定義行為,即介面的實現類可以做什麼,至於實現類如何實現,主體是誰,介面並不關心。
  • 抽象類

    對類本質的抽象,表達的是is a的關係,比如 BaoMa is a Car(寶馬是一輛車);抽象類包含並實現子類的通用特性,將子類存在差異化的特性進行抽象,交給子類去實現。

總結:

  • 當你關注一個事物的本質的時候,用抽象類;當你關注一個操作的時候,用介面。

  • 抽象類的功能要遠超過介面,但是,定義抽象類的代價高。因為高階語言來說(從實際設計上來說也是)每個類只能繼承一個類。在這個類中,你必須繼承或編寫出其所有子類的所有共性。雖然介面在功能上會弱化許多,但是它只是針對一個動作的描述。而且你可以在一個類中同時實現多個介面。在設計階段會降低難度

4. 面向物件的四大特性

1️⃣抽象

將一類物件的共同特徵總結出來構造類的過程

2️⃣封裝

將過程和資料包圍起來,對資料的訪問只能通過特定的介面(例如私有變數的get/set方法)

3️⃣繼承

從現有類派生出新類的過程

4️⃣多型

  • 編譯時多型:同一方法可根據物件的不同產生不同的效果,也就是方法的過載
  • 執行時多型:父類的引用指向子類物件,一個物件的實際型別確定,但是指向其的引用型別可以有很多

5. 面向物件和麵向過程

面向過程(Procedure Oriented)和麵向物件(Object Oriented,OO)都是對軟體分析、設計和開發的一種思想,它指導著人們以不同的方式去分析、設計和開發軟體。早期先有面向過程思想,隨著軟體規模的擴大,問題複雜性的提高,面向過程的弊端越來越明顯的顯示出來,出現了面向物件思想併成為目前主流的方式。兩者都貫穿於軟體分析、設計和開發各個階段,對應面向物件就分別稱為面向物件分析(OOA)、面向物件設計(OOD)和麵向物件程式設計(OOP)。C語言是一種典型的面向過程語言,Java是一種典型的面嚮物件語言。

面向物件和麵向過程是兩種不同的處理問題角度

  • 面向過程注重事情的每一步以及順序

  • 面向過程諸眾事情有哪些參與者(物件),以及各自需要做什麼

比如:洗衣機洗衣服

  • 面向過程會將任務拆解成一系列的步驟(函式):

    1、開啟洗衣機–>2、放衣服–>3、放洗衣粉–>4、清洗–>5、烘乾

  • 面向物件會拆出人和洗衣機兩個物件:

    人:開啟洗衣機放衣服放洗衣粉

    洗衣機:清洗烘乾

由此可見,面向過程比較直接高效,而面向物件更易於複用、擴充套件和維護

面向物件和麵向過程的總結

  1. 都是解決問題的思維方式,都是程式碼組織的方式。

  2. 解決簡單問題可以使用面向過程

  3. 解決複雜問題:巨集觀上使用面向物件把握,微觀處理上仍然是面向過程。

  4. 面向物件具有三大特徵:封裝性、繼承性和多型性,而面向過程沒有繼承性和多型性,並且面向過程的封裝只是封裝功能,而面向物件可以封裝資料和功能。所以面向物件優勢更明顯。

6. 靜態繫結&動態繫結

在Java方法呼叫的過程中,JVM是如何知道呼叫的是哪個類的方法原始碼呢?這就涉及到程式繫結,程式繫結指的是一個方法的呼叫與方法所在的類(方法主體)關聯起來。

對Java來說,繫結分為靜態繫結和動態繫結,或者叫做前期繫結和後期繫結。

1️⃣靜態繫結

針對Java,可以簡單地理解為程式編譯期的繫結。

這裡特別說明一點,Java當中的方法只有final,static,private和構造方法是靜態繫結。

 關於final,static,private和構造方法是前期繫結的理解:
對於private的方法,首先一點它不能被繼承,既然不能被繼承那麼就沒辦法通過它子類的物件來呼叫,而只能通過這個類自身的物件來呼叫。因此就可以說private方法和定義這個方法的類繫結在了一起。
final方法雖然可以被繼承,但不能被重寫(覆蓋),雖然子類物件可以呼叫,但是呼叫的都是父類中所定義的那個final方法,(由此我們可以知道將方法宣告為final型別,一是為了防止方法被覆蓋,二是為了有效地關閉java中的動態繫結)。
構造方法也是不能被繼承的(網上也有說子類無條件地繼承父類的無引數建構函式作為自己的建構函式,不過個人認為這個說法不太恰當,因為我們知道子類是通過super()來呼叫父類的無參構造方法,來完成對父類的初始化, 而我們使用從父類繼承過來的方法是不用這樣做的,因此不應該說子類繼承了父類的構造方法),因此編譯時也可以知道這個構造方法到底是屬於哪個類。
對於static方法,具體的原理我也說不太清。不過根據網上的資料和我自己做的實驗可以得出結論:static方法可以被子類繼承,但是不能被子類重寫(覆蓋),但是可以被子類隱藏。(這裡意思是說如果父類裡有一個static方法,它的子類裡如果沒有對應的方法,那麼當子類物件呼叫這個方法時就會使用父類中的方法。而如果子類中定義了相同的方法,則會呼叫子類的中定義的方法。唯一的不同就是,當子類物件上轉型為父類物件時,不論子類中有沒有定義這個靜態方法,該物件都會使用父類中的靜態方法。因此這裡說靜態方法可以被隱藏而不能被覆蓋。這與子類隱藏父類中的成員變數是一樣的。隱藏和覆蓋的區別在於,子類物件轉換成父類物件後,能夠訪問父類被隱藏的變數和方法,而不能訪問父類被覆蓋的方法)
由上面我們可以得出結論,如果一個方法不可被繼承或者繼承後不可被覆蓋,那麼這個方法就採用的靜態繫結。

2️⃣動態繫結

在執行時根據具體物件的型別進行繫結。也就是說,編譯器此時依然不知道物件的型別,但方法呼叫機制能自己去調查,找到正確的方法主體

動態繫結的過程:

  1. 虛擬機器提取物件的實際型別的方法表;

  2. 虛擬機器搜尋方法簽名

  3. 呼叫方法

7. 過載和重寫

重寫:發生在父子類中,方法名、引數列表必須相同;子類的返回值範圍小於等於父類,丟擲異常範圍小於等於父類,訪問修飾符範圍大於等於父類;如果父類方法訪問修飾符為private則子類不能重寫該方法。

過載:發生在同一個類中,引數型別不同、個數不同、順序不同都可以構成過載;

過載方法的返回值可以不同,但是不能僅僅返回值不同,否則編譯時報錯

過載方法的訪問控制符也可以不同,但是不能僅僅訪問控制符不同,否則編譯時報錯

8. Java異常體

Java中的所有異常都來自頂級父類Throwable,Throwable有兩個子類ExceptionError

  • Error是程式無法處理的錯誤,一旦出現錯誤,則程式將被迫停止執行

  • Exception不會導致程式停止,又分為RunTimeException和和CheckedException

  • //除0錯誤:ArithmeticException
    //錯誤的強制型別轉換錯誤:ClassCastException
    //陣列索引越界:ArrayIndexOutOfBoundsException
    //使用了空物件:NullPointerException
  • CheckedException常常發生在程式編譯過程中,會導致程式編譯不通過

例如:開啟不存在的檔案

9. final關鍵字

1.作用

  • 修飾類:表示類不可被繼承

  • 修飾方法:表示方法不可被子類覆蓋,但是可以過載

  • 修飾變數:表示變數一旦被賦值就不可以更改它的值

2.修飾不同變數的區別

1️⃣修飾成員變數

  • 如果final修飾的是類變數,只能在靜態初始化塊中指定初始值或者宣告該類變數時指定初始值。

  • 如果final修飾的是成員變數,可以在非靜態初始化塊、宣告該變數或者構造器中執行初始值。

2️⃣修飾區域性變數

系統不會為區域性變數進行初始化,區域性變數必須由程式設計師顯示初始化。因此使用final修飾區域性變數時,即可以在定義時指定預設值(後面的程式碼不能對變數再賦值),也可以不指定預設值,而在後面的程式碼中對final變數賦初值(僅一次)。

3️⃣修飾基本資料型別和引用型別資料

  • 如果是基本資料型別的變數,則其數值一旦在初始化之後便不能更改
  • 如果是引用型別的變數,則在對其初始化之後便不能再讓其指向另一個物件。但是引用的值可變。

2.為什麼區域性內部類和匿名內部類只能訪問區域性final變數

區域性內部類或匿名內部類編譯之後會產生兩個class檔案:Test.classTest$1.class,一個是類class,一個是內部類class

區域性內部類:

首先需要知道的一點是:內部類和外部類是處於同一個級別的,內部類不會因為定義在方法中就會隨著方法的執行完畢就被銷燬。

這裡就會產生問題:當外部類的方法結束時,區域性變數就會被銷燬了,但是內部類物件可能還存在(只有沒有人再引用它時,才會死亡),這裡就出現了一個矛盾:內部類物件訪問了一個不存在的變數。為了解決這個問題,就將區域性變數複製了一份作為內部類的成員變數,這樣當局部變數死亡後,內部類仍可以訪問它,實際訪問的是區域性變數的"copy".這樣就好像延長了區域性變數的生命週期

將區域性變數複製為內部類的成員變數時,必須保證這兩個變數是一樣的,也就是如果我們在內部類中修改了成員變數,方法中的區域性變數也得跟著改變,怎麼解決問題呢?

就將區域性變數設定為final,對它初始化後,我就不讓你再去修改這個變數,就保證了內部類的成員變數和方法的區域性變數的一致性。這實際上也是一種妥協。使得區域性變數與內部類內建立的拷貝保持一致。

10. String、StringBuilder、StringBuffer

  • String底層是final修飾的char[]陣列,不可變,每次操作都會產生新的String物件

  • StringBuffer和StringBuilder都是在原物件上操作

  • StringBuffer執行緒安全(所有方法都用synchronized修飾),StringBuilder執行緒不安全

效能:StringBuilder>StringBuffer>String

使用場景:經常需要改變字串內容時使用後面兩個,優先使用 StringBuilder,多執行緒使用共享變數時使用 StringBuffer。

11. 單例模式

暫時未總結,後期更新