2019年Android開發者常見面試題(四)

這篇文章,繼續關於Java常見的面試題總結。

問題 20哪些情況下的對象會被垃圾回收機制處理掉

答案【答案不唯一,可自己衡量】:

Java對象符合以下條件便會被垃圾回收:

1.所有實例都沒有活動線程訪問。

2.沒有被其他任何實例訪問的循環引用實例。

3.Java 中有不同的引用類型。判斷實例是否符合垃圾收集的條件都依賴於它的引用類型。

 

在編譯過程中作爲一種優化技術,Java 編譯器能選擇給實例賦 null 值,從而標記實例爲可回收。
class Animal {
        public static void main(String[] args) {
               Animal lion =new Animal();
               System.out.println("Main is completed.");
        }
        protected void finalize() {
               System.out.println("Rest in Peace!");
        }
}

問題 20講一下常見編碼方式

答案【答案不唯一,可自己衡量】:

什麼是編碼:

編碼是從一種形式或格式轉換爲另一種形式的過程也稱爲計算機編程語言的代碼簡稱編碼。

計算機中存儲信息的最小單元是一個字節,即8個bit。

常見的編碼方式:

ASCII碼:共有128個,用一個字節的低7位表示

ISO8859-1:在ASCII碼的基礎上涵蓋了大多數西歐語言字符,仍然是單字節編碼,它總共能表示256個字符

GB2312:全稱爲《信息交換用漢字編碼字符集基本集》,它是雙字節編碼,總的編碼範圍是A1~F7

A1~A9 ·符號區

B0~F7 漢字區

GBK:數字交換用漢字編碼字符集》,它可能是單字節、雙字節或者四字節編碼,與GB2312編碼兼容

UTF-16:具體定義了Unicode字符在計算機中的存取方法。採用2字節來表示Unicode轉化格式,它是定長的表示方法,不論什麼字符都可以用兩個字節表示

UTF-8: UTF-8採用一種變長技術,每個編碼區域有不同的字碼長度,不同的字符可以由1~6個字節組成。

如果一個字節,最高位爲0,表示這是一個ASCII字符(00~7F)

如果一個字節,以11開頭,連續的1的個數暗示這個字符的字節數

 

問題 21utf-8編碼中的中文佔幾個字節;int型幾個字節

答案【答案不唯一,可自己衡量】:

utf-8的編碼規則:

如果一個字節,最高位爲0,表示這是一個ASCII字符(00~7F)
如果一個字節,以11開頭,連續的1的個數暗示這個字符的字節數

一個utf8數字佔1個字節

一個utf8英文字母佔1個字節

少數是漢字每個佔用3個字節,多數佔用4個字節。

 

問題 22Java的異常體系

答案【答案不唯一,可自己衡量】:

 

Throwable 類是 Java 語言中所有錯誤或異常的超類。

只有當對象是此類(或其子類之一)的實例時,才能通過 Java 虛擬機或者 Java throw 語句拋出。類似地,只有此類或其子類之一纔可以是 catch 子句中的參數類型。

Throwable 包含了其線程創建時線程執行堆棧的快照。它還包含了給出有關錯誤更多信息的消息字符串。

最後,它還可以包含 cause(原因):另一個導致此 throwable 拋出的 throwable。此 cause 設施在 1.4 版本中首次出現。它也稱爲異常鏈 設施,因爲 cause 自身也會有 cause,依此類推,就形成了異常鏈,每個異常都是由另一個異常引起的。

1.1 Error

  • Error 是 Throwable 的子類,用於指示合理的應用程序不應該試圖捕獲的嚴重問題
  • 大多數這樣的錯誤都是異常條件。雖然 ThreadDeath 錯誤是一個“正規”的條件,但它也是 Error 的子類,因爲大多數應用程序都不應該試圖捕獲它。
  • 在執行該方法期間,無需在其 throws 子句中聲明可能拋出但是未能捕獲的 Error 的任何子類,因爲這些錯誤可能是再也不會發生的異常條件。
  • Java 程序通常不捕獲錯誤。錯誤一般發生在嚴重故障時,它們在Java程序處理的範疇之外。

1.2 Exception

Exception 異常主要分爲兩類

    • 一類是 IOException(I/O 輸入輸出異常),其中 IOException 及其子類異常又被稱作「受查異常」
    • 另一類是 RuntimeException(運行時異常),RuntimeException 被稱作「非受查異常」。

受查異常就是指,編譯器在編譯期間要求必須得到處理的那些異常,你必須在編譯期處理了。

1.2.1 常見的非檢查性異常:

 

1.2.2 常見的檢查性異常:

 

2 自定義異常類型

Java 的異常機制中所定義的所有異常不可能預見所有可能出現的錯誤,某些特定的情境下,則需要我們自定義異常類型來向上報告某些錯誤信息。

在 Java 中你可以自定義異常。編寫自己的異常類時需要記住下面的幾點。

所有異常都必須是 Throwable 的子類。

如果希望寫一個檢查性異常類,則需要繼承 Exception 類。

如果你想寫一個運行時異常類,那麼需要繼承 RuntimeException 類。

3 異常的處理方式

3.1 try...catch關鍵字

• 使用 try 和 catch 關鍵字可以捕獲異常。

• try/catch 代碼塊放在異常可能發生的地方。

try/catch代碼塊中的代碼稱爲保護代碼,使用 try/catch 的語法如下:

try {
   // 程序代碼
} catch(ExceptionName e1) {
   //Catch 塊
}

• Catch 語句包含要捕獲異常類型的聲明。當保護代碼塊中發生一個異常時,try 後面的 catch 塊就會被檢查。如果發生的異常包含在 catch 塊中,異常會被傳遞到該 catch 塊,這和傳遞一個參數到方法是一樣。

• 一個 try 代碼塊後面跟隨多個 catch 代碼塊的情況就叫多重捕獲。

• 多重捕獲塊的語法如下所示:

try{
   // 程序代碼
}catch(異常類型1 異常的變量名1){
  // 程序代碼
}catch(異常類型2 異常的變量名2){
  // 程序代碼
}catch(異常類型2 異常的變量名2){
  // 程序代碼
}

3.2 throws/throw 關鍵字

如果一個方法沒有捕獲一個檢查性異常,那麼該方法必須使用 throws 關鍵字來聲明。throws 關鍵字放在方法簽名的尾部。也可以使用 throw 關鍵字拋出一個異常,無論它是新實例化的還是剛捕獲到的。

下面方法的聲明拋出一個 RemoteException 異常:

public class className {
  public void deposit(double amount) throws RemoteException {
    // Method implementation
    throw new RemoteException();
  }
  //Remainder of class definition
}

一個方法可以聲明拋出多個異常,多個異常之間用逗號隔開。

3.3 finally關鍵字

finally 關鍵字用來創建在 try 代碼塊後面執行的代碼塊。

無論是否發生異常,finally 代碼塊中的代碼總會被執行。在 finally 代碼塊中,可以運行清理類型等收尾善後性質的語句。

finally 代碼塊出現在 catch 代碼塊最後,語法如下:

try{
  // 程序代碼
}catch(異常類型1 異常的變量名1){
  // 程序代碼
}catch(異常類型2 異常的變量名2){
  // 程序代碼
}finally{
  // 程序代碼
}

 

問題 23Java中實現多態的機制是什麼

答案【答案不唯一,可自己衡量】:

面向對象編程有三大特性:封裝、繼承、多態。

封裝隱藏了類的內部實現機制,可以在不影響使用的情況下改變類的內部結構,同時也保護了數據。對外界而已它的內部細節是隱藏的,暴露給外界的只是它的訪問方法。

繼承是爲了重用父類代碼。兩個類若存在IS-A的關係就可以使用繼承。

所謂多態就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時並不確定,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。因爲在程序運行時才確定具體的類,這樣,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現上,從而導致該引用調用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態,這就是多態性。

但是向上轉型存在一些缺憾,那就是它必定會導致一些方法和屬性的丟失,而導致我們不能夠獲取它們。所以父類類型的引用可以調用父類中定義的所有屬性和方法,對於只存在與子類中的方法和屬性它就望塵莫及了。

class Wine {
     public void fun1(){
         System.out.println("Wine 的Fun.....");
         fun2();
     }
 
     public void fun2(){
         System.out.println("Wine 的Fun2...");
     }
 }
 
 
  class JNC extends Wine{
     /**
      * @desc 子類重載父類方法
      *        父類中不存在該方法,向上轉型後,父類是不能引用該方法的
      * @param a
      * @return void
      */
     public void fun1(String a){
         System.out.println("JNC 的 Fun1...");
         fun2();
     }
 
     /**
      * 子類重寫父類方法
      * 指向子類的父類引用調用fun2時,必定是調用該方法
      */
     public void fun2(){
         System.out.println("JNC 的Fun2...");
     }
 }
 
 public class test24 {
     public static void main(String[] args) {
         Wine a = new JNC();
         a.fun1();
     }
 }
 
 

分析:在這個程序中子類JNC重載了父類Wine的方法fun1(),重寫fun2(),而且重載後的fun1(String a)與 fun1()不是同一個方法,由於父類中沒有該方法,向上轉型後會丟失該方法,所以執行JNC的Wine類型引用是不能引用fun1(String a)方法。而子類JNC重寫了fun2() ,那麼指向JNC的Wine引用會調用JNC中fun2()方法。

所以對於多態我們可以總結如下:

指向子類的父類引用由於向上轉型了,它只能訪問父類中擁有的方法和屬性,而對於子類中存在而父類中不存在的方法,該引用是不能使用的,儘管是重載該方法。若子類重寫了父類中的某些方法,在調用該些方法的時候,必定是使用子類中定義的這些方法(動態連接、動態調用)。

多態分爲編譯時多態和運行時多態。其中編輯時多態是靜態的,主要是指方法的重載,它是根據參數列表的不同來區分不同的函數,通過編輯之後會變成兩個不同的函數,在運行時談不上多態。而運行時多態是動態的,它是通過動態綁定來實現的,也就是我們所說的多態性。

多態的實現

Java實現多態有三個必要條件:繼承、重寫、向上轉型。

繼承:在多態中必須存在有繼承關係的子類和父類。

重寫:子類對父類中某些方法進行重新定義,在調用這些方法時就會調用子類的方法。

向上轉型:在多態中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調用父類的方法和子類的方法。

只有滿足了上述三個條件,我們才能夠在同一個繼承結構中使用統一的邏輯實現代碼處理不同的對象,從而達到執行不同的行爲。

對於Java而言,它多態的實現機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。

實現形式:

在Java中有兩種形式可以實現多態。繼承和接口。

class A{
 public String show(D obj){
 return ("A and D");
 }
 public String show(A obj){
 return ("A and A");
 }
 }
 class B extends A{
 public String show(B obj){
 return("B and B");
 }
 public String show(A obj){
 return("B and A");
 }
 }
 class C extends B{}
 class D extends B{}
 public class test25 {
 public static void main(String[] args) {
 A a1=new A();
 A a2=new B();
 B b=new B();
 C c=new C();
 D d=new D();
 
 System.out.println("1--"+a1.show(b));
 System.out.println("2--"+a1.show(c));
 System.out.println("3--"+a1.show(d));
 System.out.println("4--"+a2.show(b));
 System.out.println("5--"+a2.show(c));
 System.out.println("6--"+a2.show(d));
 System.out.println("7--"+b.show(b));
 System.out.println("8--"+b.show(c));
 System.out.println("9--"+b.show(d));
 }
 }
 
 

結果爲:

1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D

當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。這句話對多態進行了一個概括。其實在繼承鏈中對象方法的調用存在一個優先級:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

首先我們分析5,a2.show(c),a2是A類型的引用變量,所以this就代表了A,a2.show(c),它在A類中找發現沒有找到,於是到A的超類中找(super),由於A沒有超類(Object除外),所以跳到第三級,也就是this.show((super)O),C的超類有B、A,所以(super)O爲B、A,this同樣是A,這裏在A中找到了show(A obj),同時由於a2是B類的一個引用且B類重寫了show(A obj),因此最終會調用子類B類的show(A obj)方法,結果也就是B and A。

方法已經找到了但是我們這裏還是存在一點疑問,我們還是來看這句話:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。這我們用一個例子來說明這句話所代表的含義:a2.show(b);

這裏a2是引用變量,爲A類型,它引用的是B對象,因此按照上面那句話的意思是說有B來決定調用誰的方法,所以a2.show(b)應該要調用B中的show(B obj),產生的結果應該是“B and B”,但是爲什麼會與前面的運行結果產生差異呢?這裏我們忽略了後面那句話“但是這兒被調用的方法必須是在超類中定義過的”,那麼show(B obj)在A類中存在嗎?根本就不存在!所以這句話在這裏不適用?那麼難道是這句話錯誤了?非也!其實這句話還隱含這這句話:它仍然要按照繼承鏈中調用方法的優先級來確認。所以它纔會在A類中找到show(A obj),同時由於B重寫了該方法所以纔會調用B類中的方法,否則就會調用A類中的方法。

所以多態機制遵循的原則概括爲:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法,但是它仍然要根據繼承鏈中方法調用的優先級來確認方法,該優先級爲:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章