Android面試整理(2)-java 深入原始碼級的面試題

2020-08-12 18:39:29

1、哪些情況下的物件會被垃圾回收機制 機製處理掉?

利用可達性分析演算法,虛擬機器會將一些物件定義爲 GC Roots,從 GC Roots 出發沿着參照鏈
向下尋找,如果某個物件不能通過 GC Roots 尋找到,虛擬機器就認爲該物件可以被回收掉。

1.1 哪些物件可以被看做是 GC Roots 呢?

1)虛擬機器棧(棧幀中的本地變數表)中參照的物件;
2)方法區中的類靜態屬性參照的物件,常數參照的物件;
3)本地方法棧中 JNI(Native 方法)參照的物件;

1.2 物件不可達,一定會被垃圾收集器回收麼?

即使不可達,物件也不一定會被垃圾收集器回收,1)先判斷物件是否有必要執行 finalize()
方法,物件必須重寫 finalize()方法且沒有被執行過。2)若有必要執行,會把物件放到一個
佇列中,JVM 會開一個執行緒去回收它們,這是物件最後一次可以逃逸清理的機會。

2、講一下常見編碼方式?

編碼的意義:計算機中儲存的最小單元是一個位元組即 8bit,所能表示的字元範圍是 255 個,
而人類要表示的符號太多,無法用一個位元組來完全表示,固需要將符號編碼,將各種語言翻
譯成計算機能懂的語言。
1)ASCII 碼:總共 128 個,用一個位元組的低 7 位表示,0〜31 控制字元如換回車刪除等;32~126
是列印字元,可通過鍵盤輸入並顯示出來;
2)ISO-8859-1,用來擴充套件 ASCII 編碼,256 個字元,涵蓋了大多數西歐語言字元。
3)GB2312:雙位元組編碼,總編碼範圍是 A1-A7,A1-A9 是符號區,包含 682 個字元,B0-B7 是
漢字區,包含 6763 個漢字;
4)GBK 爲了擴充套件 GB2312,加入了更多的漢字,編碼範圍是 8140~FEFE,有 23940 個碼位,能
表示 21003 個漢字。
5)UTF-16: ISO 試圖想建立一個全新的超語言字典,世界上所有語言都可通過這本字典
Unicode 來相互翻譯,而 UTF-16 定義了 Unicode 字元在計算機中存取方法,用兩個位元組來表
示 Unicode 轉化格式。不論什麼字元都可用兩位元組表示,即 16bit,固叫 UTF-16。
6)UTF-8:UTF-16 統一採用兩位元組表示一個字元,但有些字元只用一個位元組就可表示,浪
費儲存空間,而 UTF-8 採用一種變長技術,每個編碼區域有不同的字碼長度。 不同類型的
字 符 可 以 由 1~6 個 字 節 組成。

3、utf-8 編碼中的中文佔幾個位元組;int 型幾個位元組?

utf-8 是一種變長編碼技術,utf-8 編碼中的中文佔用的位元組不確定,可能 2 個、3 個、4 個,
int 型佔 4 個位元組。

4、靜態代理和動態代理的區別,什麼場景使用?

代理是一種常用的設計模式,目的是:爲其他物件提供一個代理以控制對某個物件的存取,
將兩個類的關係解耦。代理類和委託類都要實現相同的介面,因爲代理真正呼叫的是委託類
的方法。
區別:
1)靜態代理:由程式設計師建立或是由特定工具生成,在程式碼編譯時就確定了被代理的類是哪
一個是靜態代理。靜態代理通常只代理一個類;
2)動態代理:在程式碼執行期間,運用反射機制 機製動態建立生成。動態代理代理的是一個介面
下的多個實現類;
實現步驟:
a.實現 InvocationHandler 介面建立自己的呼叫處理器;
b.給 Proxy 類提供ClassLoader 和代理介面型別陣列建立動態代理類;c.利用反射機制 機製得到動態代理類別建構函式;
d.利用動態代理類別建構函式建立動態代理類物件;使用場景:Retrofit 中直接呼叫介面的方法;Spring 的 AOP 機制 機製;

5、Java 的異常體系

Java 中 Throwable 是所有異常和錯誤的超類,兩個直接子類是 Error(錯誤)和 Exception(異
常):
1)Error 是程式無法處理的錯誤,由 JVM 產生和拋出,如 OOM、ThreadDeath 等。這些異常
發生時,JVM 一般會選擇終止程式。
2)Exception 是程式本身可以處理的異常,又分爲執行時異常(RuntimeException)(也叫 Checked
Eception) 和 非 運 行 時 異 常 ( 不 檢 查 異 常 Unchecked Exception) 。 運 行 時 異 常 有
NullPointerException\IndexOutOfBoundsException 等,這些異常一般是由程式邏輯錯誤引起
的,應儘可能避免。非執行時異常有 IOException\SQLException\FileNotFoundException 以及
由使用者自定義的 Exception 異常等。

6、談談你對解析與分派的認識。

解析指方法在執行前,即編譯期間就可知的,有一個確定的版本,執行期間也不會改變。解
析是靜態的,在類載入的解析階段就可將符號參照轉變成直接參照。
分派可分爲靜態分派和動態分派,過載屬於靜態分派,覆蓋屬於動態分派。靜態分派是指在
過載時通過參數的靜態型別而非實際型別作爲判斷依據,在編譯階段,編譯器可根據參數的
靜態型別決定使用哪一個過載版本。動態分派則需要根據實際型別來呼叫相應的方法。

7、修改物件 A 的 equals 方法的簽名,那麼使用 HashMap 存放這個物件

範例的時候,會呼叫哪個 equals 方法?
會呼叫物件的 equals 方法,如果物件的 equals 方法沒有被重寫,equals 方法和==都是比較
棧內區域性變數表中指向堆記憶體地址值是否相等。

8、Java 中實現多型的機制 機製是什麼?

多型是指程式中定義的參照變數所指向的具體型別和通過該參照變數發出的方法呼叫在編
譯時不確定,在執行期間才確定,一個參照變數到底會指向哪個類的範例。這樣就可以不用
修改源程式,就可以讓參照變數系結到各種不同的類實現上。Java 實現多型有三個必要條件:
繼承、重定、向上轉型,在多型中需要將子類的參照賦值給父類別物件,只有這樣該參照才能 纔能
夠具備呼叫父類別方法和子類的方法。

9、如何將一個 Java 物件序列化到檔案裡?

ObjectOutputStream.writeObject()負責將指定的流寫入,ObjectInputStream.readObject()從指
定流讀取序列化數據。
//寫入
try {
ObjectOutputStream os = new ObjectOutputStream(new
FileOutputStream("D:/student.txt"));
os.writeObject(studentList);
os.close();
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}

10、說說你對 Java 反射的理解

在執行狀態中,對任意一個類,都能知道這個類的所有屬性和方法,對任意一個物件,都能
呼叫它的任意一個方法和屬性。這種能動態獲取資訊及動態呼叫物件方法的功能稱爲 java
語言的反射機制 機製。
反射的作用:開發過程中,經常會遇到某個類的某個成員變數、方法或屬性是私有的,或只
對系統應用開放,這裏就可以利用 java 的反射機制 機製通過反射來獲取所需的私有成員或是方
法。
1) 獲取類的 Class 物件範例 Class clz = Class.forName("com.zhenai.api.Apple");
2) 根 據 Class 對 象 實 例 獲 取 Constructor 對 象 Constructor appConstructor =
clz.getConstructor();
3) 使 用 Constructor 對 象 的 newInstance 方 法 獲 取 反 射 類 對 象 Object appleObj =
appConstructor.newInstance();
4) 獲取方法的 Method 物件 Method setPriceMethod = clz.getMethod("setPrice", int.class);
5) 利用 invoke 方法呼叫方法 setPriceMethod.invoke(appleObj, 14);
6) 通過 getFields()可以獲取 Class 類的屬性,但無法獲取私有屬性,而 getDeclaredFields()可
以獲取到包括私有屬性在內的所有屬性。帶有 Declared 修飾的方法可以反射到私有的方法,
沒有 Declared 修飾的只能用來反射公有的方法,其他如 Annotation\Field\Constructor 也是如
此。

11、說說你對 Java 註解的理解

註解是通過@interface 關鍵字來進行定義的,形式和介面差不多,只是前面多了一個@
public @interface TestAnnotation {
}
使用時@TestAnnotation 來參照,要使註解能正常工作,還需要使用元註解,它是可以註解
到註解上的註解。元標籤有@Retention @Documented @Target @Inherited @Repeatable 五種
@Retention 說明註解的存活時間,取值有 RetentionPolicy.SOURCE 註解只在原始碼階段保留,
在編譯器進行編譯時被丟棄;RetentionPolicy.CLASS 註解只保留到編譯進行的時候,並不會
被載入到 JVM 中。RetentionPolicy.RUNTIME 可以留到程式執行的時候,它會被載入進入到 JVM
中,所以在程式執行時可以獲取到它們。
@Documented 註解中的元素包含到 javadoc 中去
@Target 限 定 注 解 的 應 用 場 景 , ElementType.FIELD 給 屬 性 進 行 注 解 ;
ElementType.LOCAL_VARIABLE 可以給區域性變數進行註解;ElementType.METHOD 可以給方法
進行註解;ElementType.PACKAGE 可以給一個包進行註解 ElementType.TYPE 可以給一個型別
進行註解,如類、介面、列舉
@Inherited 若一個超類被@Inherited 註解過的註解進行註解,它的子類沒有被任何註解應用
的話,該子類就可繼承超類的註解;
註解的作用:
1)提供資訊給編譯器:編譯器可利用註解來探測錯誤和警告資訊
2)編譯階段:軟體工具可以利用註解資訊來生成程式碼、html 文件或做其它相應處理;
3)執行階段:程式執行時可利用註解提取程式碼
註解是通過反射獲取的,可以通過 Class 物件的 isAnnotationPresent()方法判斷它是否應用了
某個註解,再通過 getAnnotation()方法獲取 Annotation 物件

12、說一下泛型原理,並舉例說明

泛型就是將型別變成參數傳入,使得可以使用的型別多樣化,從而實現解耦。Java 泛型是在
Java1.5 以後出現的,爲保持對以前版本的相容,使用了擦除的方法實現泛型。擦除是指在
一定程度無視型別參數 T,直接從 T 所在的類開始向上 T 的父類別去擦除,如呼叫泛型方法,
傳入型別參數 T 進入方法內部,若沒在宣告時做類似 public T methodName(T extends Father
t){},Java 就進行了向上型別的擦除,直接把參數 t 當做 Object 類來處理,而不是傳進去的 T。
即在有泛型的任何類和方法內部,它都無法知道自己的泛型參數,擦除和轉型都是在邊界上
發生,即傳進去的參在進入類或方法時被擦除掉,但傳出來的時候又被轉成了我們設定的 T。
在泛型類或方法內,任何涉及到具體型別(即擦除後的型別的子類)操作都不能進行,如
new T(),或者 T.play()(play 爲某子類的方法而不是擦除後的類的方法)

13、Java 中 String 的瞭解

1)String 類是 final 型,固 String 類不能被繼承,它的成員方法也都預設爲 final 方法。String
物件一旦建立就固定不變了,對 String 物件的任何改變都不影響到原物件,相關的任何改變
操作都會生成新的 String 物件。
2)String 類是通過 char 陣列來儲存字串的,String 對 equals 方法進行了重定,比較的是
值相等。
String a = "test"; String b = "test"; String c = new String("test");
a、b 和字面上的 test 都是指向 JVM 字串常數池中的"test"物件,他們指向同一個物件。而
new 關鍵字一定會產生一個物件 test,該物件儲存在堆中。所以 new String("test")產生了兩
個物件,儲存在棧中的 c 和儲存在堆中的 test。而在 java 中根本就不存在兩個完全一模一樣
的字串物件,故在堆中的 test 應該是參照字串常數池中的 test。
例:
String str1 = "abc"; //棧中開闢一塊空間存放參照 str1,str1 指向池中 String 常數"abc" String str2 = "def"; //棧中開闢一塊空間存放參照 str2,str2 指向池中 String 常數"def" String str3 = str1 + str2;//棧中開闢一塊空間存放參照 str3
//str1+str2 通過 StringBuilder 的最後一步 toString()方法返回一個新的 String 物件"abcdef"
//會在堆中開闢一塊空間存放此物件,參照str3指向堆中的(str1+str2)所返回的新String物件。
System.out.println(str3 == "abcdef");//返回 false
因爲 str3 指向堆中的"abcdef"物件,而"abcdef"是字元池中的物件,所以結果爲 false。JVM
對 String str="abc"物件放在常數池是在編譯時做的,而 String str3=str1+str2 是在執行時才知
道的,new 物件也是在執行時才做的。

14、String 爲什麼要設計成不可變的?

1)字串常數池需要 String 不可變。因爲 String 設計成不可變,當建立一個 String 物件時,
若此字串值已經存在於常數池中,則不會建立一個新的物件,而是參照已經存在的物件。
如果字串變數允許必變,會導致各種邏輯錯誤,如改變一個物件會影響到另一個獨立物件。
2)String 物件可以快取 hashCode。字串的不可變性保證了 hash 碼的唯一性,因此可以緩
存 String 的 hashCode,這樣不用每次去重新計算雜湊碼。在進行字串比較時,可以直接
比較 hashCode,提高了比較效能;
3)安全性。String 被許多 java 類用來當作參數,如 url 地址,檔案 path 路徑,反射機制 機製所
需的 Strign 參數等,若 String 可變,將會引起各種安全隱患。