相信每次面試之前,大家都會大量刷一下面試題來應該對各種公司的面試吧,下面整理一下android方面的面試題分享給大家。
本文主要分爲以下幾部分:
- java面試題
- Android面試題
- 高級開發技術面試題
- 跨平臺Hybrid 開發
1、Java中equals、hascode和==的區別
==(雙等號):對於基本數據類型(byte、short、char、int、long、float、double、boolean)來說,比較的是他們的值;對於引用類型(各種new出來的對象)來說比較的是他們的地址。
equals:默認情況(沒有覆蓋equals方法)下equals方法都是調用Object類的equals方法,而Object的equals方法是判斷對象的內存地址。一般地都是覆蓋這個方法,根據對象的內容是否相等來判斷對象是否相等。如:String類就是覆蓋這個方法,先判斷對象的內存地址是否相等,之後再判斷內容是否相等。
hascode:hascode()方法返回的就是一個數值,其目的是生成一個hash碼,hash碼的主要用途就是在對對象進行散列的時候作爲key輸入,我們需要每個對象的hash碼儘可能不同,這樣才能保證散列的存取性能。
在java中一般的equals()和hascode()通常要遵從如下幾點約定,這樣才能保證這兩個方法在java中的一個規範,這也是爲什麼重寫equals()方法需要重寫hascode()方法的原因:
- 如果兩個對象equals,java運行時環境會認爲他們的hashcode一定相等;
- 如果兩個對象不equals,他們的hashcode有可能相等。
- 如果兩個對象hashcode相等,他們不一定hashcode。
- 如果兩個對象hashcode不相等,他們一定不equals。
2、int、char、long各佔多少字節數
int佔4個字節、char佔2個字節、long佔8個字節。
3、int與integer的區別
int是基本數據類型,直接存數據。而integer是對象,用一個引用指向這個對象,是int的封裝類。
4、談談對java多態的理解
多態是指:父類引用指向子類對象,在執行期間會根據引用對象的實際類型,調用具體對象的相應方法。
一般可以通過兩種方式實現多態:重寫、重載。
實現多態的三要素:集成、重寫、父類引用指向子類對象。
5、String、StringBuffer、StringBuilder區別
String:
- 是字符串常量,一旦創建就不能修改。對於已經存在了的String對象的修改都是重新創建一個新的對象,然後把新的值保存進去。
- String是final類,不能被繼承。
- String覆蓋了equals方法和hashCode方法。
StringBuffer:
- 是字符串可變對象,可以對字符串進行操作,修改字符串是不會新建一個對象。
- 執行效率較慢,但是線程安全。
- StringBuffer沒有覆蓋了equals方法和hashCode方法。
StringBuilder:
- 也是字符串可變對象,同StringBuffer一樣,可以對字符串進行操作,也 不會新建對象。
- 執行效率高,但是線程不安全。
6、什麼是內部類?內部類的作用
什麼是內部類:
將一個類定義在另一個類裏面或者一個方法裏面,這樣的類稱爲內部類。
作用:
- 解決多繼承問題。在java中一個類只能單繼承,而內類提供了一種多繼承的解決方案。
- 方便將存在一定邏輯關係的組織在一起,又可以對外界隱藏。
- 方便編寫事件驅動程序。
- 方便編寫線程代碼。
7、抽象類和接口的區別
- 抽象類要被類繼承,接口要被類實現;
- 接口只能做方法的聲明,抽象類中可以作方法的聲明,也可以作方法的實現;
- 接口裏定義的變量只能是公共的靜態常量,抽象類中的變量可以是普通變量;
- 接口是設計的結果,抽象類是重構的結果;
- 抽象類和接口都是用來抽象具體對象的,但是接口的抽象級別最高;
- 抽象類可以有具體的方法和屬性,接口只能有抽象方法和常量;
- 抽象類主要用來抽象類別,接口主要用來抽象功能;
8、抽象類的意義
- 爲子類提供一個公共的類型;
- 封裝子類中重複內容(成員變量和方法);
- 定義抽象方法,子類雖然有不同的實現,但改方法的定義是一致的。
9、抽象類與接口的應用場景
接口的應用場景:
- 類與類之間需要特定的接口進行協調,而不在乎如何實現;
- 作爲能夠實現特定功能的標識存在,也可以是什麼接口方法都沒有的純粹標識。
- 需要將一組類視爲單一的類,而調用者只通過接口來與這組類發生聯繫;
- 需要實現特定的多項功能,而這些功能之間可能完全沒有任何聯繫;
抽象類的應用場景:
- 定義了一組接口,但又不想強迫每個實現類都必須實現所有的接口,可以用abstract定義一組方法,甚至可以是空方法體,然後有子類選擇自己所感興趣的方法來覆蓋;
- 某些場合下,只靠純粹的接口不能滿足類與類之間的協調,還必須類中表示狀態的變量來區別不同的關係。Abstract的中介作用可以很好地滿足這一點;
- 規範了一組相互協調的方法,其中一些方法是共同的,與狀態無關的,可以共享的,無需子類分別實現;而另一些方法卻需要各個子類根據自己特定的狀態來實現特定的功能;
10、接口的意義
- 重要性:在java中,abstract class 和interface 是支持抽象類定義的兩種機制,接口是特殊的抽象類,在java中一個類只能繼承一個父類卻可以實現多個接口。正是由於這兩種機制的存在,才賦予了java強大的面向對象能力。java的特性封裝、繼承、多態。在java中有兩種形式可以實現多態:繼承和接口;
- 簡單、規範性:如果一個項目比較龐大,那麼就需要一個能理清所有業務的架構師來定義一些主要的接口,這些接口不僅告訴開發人員你需要實現哪些業務,而且也將命名規範限制住了(防止一些開發人員隨便命名導致別的程序員無法看明白);
- 維護、拓展性:由於接口只做定義,而不關注實現,在某些功能模塊中,可以通過接口去應用實現類,這能很好的降低功能模塊之間的耦合性,從而修改某些模塊的實現,不會影響到其他模塊,方便後期的維護、拓展。
- 安全、嚴密性:接口是實現軟件松耦合的重要手段,它描述了系統對外的所有服務,而不涉及任何具體的實現細節。這樣就比較安全、嚴密一些。
11、泛型中extends和super的區別
泛型中extends主要作用是類型的設定上界通配符,<? extends T>對應的爲T及其子類對象;
泛型中super與extends是完全相反的,其定義是下界通配符,<? super T>對應的爲T及其子類對象;
12、父類的靜態方法能否被子類重寫
不能。父類的靜態方法能夠被子類繼承,但是不能夠被子類重寫,即使子類中的靜態方法與父類中的靜態方法完全一樣,也是兩個完全不同的方法。對於靜態方法和靜態變量來說,雖然在代碼中可以通過子類對象來調用,當時在編譯的時候就將其與類綁定在一起,既然它們在編譯的時候就決定了調用的方法、變量,那就和重寫沒有關係了。重寫指的是根據運行時對象的類型來決定調用哪個方法,而不是根據編譯時的類型。
13、進程和線程的區別
- 進程是資源分配的最小單位,線程是資源調度的最小單位(程序執行的最小單位);
- 進程有自己的獨立地址空間,每啓動一個進程,系統就會爲它分配地址空間,建立數據表來維護代碼段、堆棧段和數據段,這種操作非常昂貴。而線程是共享進程中的數據的,使用相同的地址空間,因此CPU切換一個線程的花費遠比進程要小很多,同時創建一個線程的開銷也比進程要小很多。
- 線程之間的通信更方便,同一進程下的線程共享全局變量、靜態變量等數據,而進程之間的通信需要以通信的方式(IPC)進行。
- 但是多進程程序更健壯,多線程程序只要有一個線程死掉,整個進程也死掉了,而一個進程死掉並不會對另外一個進程造成影響,因爲進程有自己獨立的地址空間。
14、final,finally,finalize的區別
final:
在java中,final可以用來修飾類,方法和變量(成員變量或局部變量)。
- 修飾類:當用final修飾類的時,表明該類不能被其他類所繼承;
- 修飾方法:即此方法不能被重寫;
- 修飾變量:表示常量,只能被賦值一次,賦值後其值不再改變;
finally:
finally作爲異常處理的一部分,它只能用在try/catch語句中,並且附帶一個語句塊,表示這段語句最終一定會被執行(不管有沒有拋出異常,哪怕有return語句,都會執行),經常被用在需要釋放資源的情況下。
finalize
finalize()是在java.lang.Object裏定義的,也就是說每一個對象都有這麼個方法。這個方法在gc啓動,該對象被回收的時候被調用。其實gc可以回收大部分的對象(凡是new出來的對象,gc都能搞定,一般情況下我們又不會用new以外的方式去創建對象),所以一般是不需要程序員去實現finalize的。
特殊情況下,需要程序員實現finalize,當對象被回收的時候釋放一些資源,比如:一個socket鏈接,在對象初始化時創建,整個生命週期內有效,那麼就需要實現finalize,關閉這個鏈接。
一個對象的finalize()方法只會被調用一次,而且finalize()被調用不意味着gc會立即回收該對象,所以有可能調用finalize()後,該對象又不需要被回收了,然後到了真正要被回收的時候,因爲前面調用過一次,所以不會調用finalize(),產生問題。 所以,推薦不要使用finalize()方法,它跟析構函數不一樣。
15、堆與棧內存的區別
堆:是大家共有的空間,分全局堆和局部堆。全局堆就是所有沒有分配的空間,局部堆就是用戶分配的空間。堆在操作系統對進程初始化的時候分配,運行過程中也可以向系統要額外的堆,但是記得用完了要還給操作系統,要不然就是內存泄漏。
棧:是個線程獨有的,保存其運行狀態和局部自動變量的。棧在線程開始的時候初始化,每個線程的棧互相獨立,因此,棧是 thread safe的。操作系統在切換線程的時候會自動的切換棧,就是切換 SS/ESP寄存器。棧空間不需要在高級語言裏面顯式的分配和釋放。
16、序列化的方式
- 實現Serializable接口(隱式序列化):通過實現Serializable接口,這種是隱式序列化(不需要手動),這種是最簡單的序列化方式,會自動序列化所有非static和 transient關鍵字修飾的成員變量。
- 實現Externalizable接口(顯式序列化):Externalizable接口繼承自Serializable, 我們在實現該接口時,必須實現writeExternal()和readExternal()方法,而且只能通過手動進行序列化,並且兩個方法是自動調用的,因此,這個序列化過程是可控的,可以自己選擇哪些部分序列化。
- 實現Serializable接口+添加writeObject()和readObject()方法。(顯+隱序列化):先實現Serializable接口,並且添加writeObject()和readObject()方法。注意這裏是添加,不是重寫或者覆蓋。但是添加的這兩個方法必須有相應的格式。
- 方法必須要被private修飾; ----->才能被調用
- 第一行調用默認的defaultRead/WriteObject(); ----->隱式序列化非static和transient
- 調用read/writeObject()將獲得的值賦給相應的值; —>顯式序列化
17、Serializable 和Parcelable 的區別
1、平臺區別
- Serializable是屬於 Java 自帶的,表示一個對象可以轉換成可存儲或者可傳輸的狀態,序列化後的對象可以在網絡上進行傳輸,也可以存儲到本地。
- Parcelable 是屬於 Android 專用。不過不同於Serializable,Parcelable實現的原理是將一個完整的對象進行分解。而分解後的每一部分都是Intent所支持的數據類型。
2、編寫上的區別
- Serializable代碼量少,寫起來方便;
- Parcelable代碼多一些,略複雜;
3、選擇的原則
- 如果是僅僅在內存中使用,比如activity、service之間進行對象的傳遞,強烈推薦使用Parcelable,因爲Parcelable比Serializable性能高很多。因爲Serializable在序列化的時候會產生大量的臨時變量, 從而引起頻繁的GC。
- 如果是持久化操作,推薦Serializable,雖然Serializable效率比較低,但是還是要選擇它,因爲在外界有變化的情況下,Parcelable不能很好的保存數據的持續性。
4、本質的區別
- Serializable的本質是使用了反射,序列化的過程比較慢,這種機制在序列化的時候會創建很多臨時的對象,比引起頻繁的GC;
- Parcelable方式的本質是將一個完整的對象進行分解,而分解後的每一部分都是Intent所支持的類型,這樣就實現了傳遞對象的功能了。
18、靜態屬性和靜態方法是否可以被繼承?是否可以被重寫?以及原因?
可以繼承、不能重載;
原因:
因爲靜態屬性、方法從程序開始運行後就已經分配了內存。所有通過對象來訪問該屬性、方法的方式,都會在編譯的時候就將其與類綁定在一起,就決定了調用的方法、變量,對象無關。
19、靜態內部類的設計意圖
20、成員內部類、靜態內部類、局部內部類和匿名內部類的理解,以及項目中的應用
21、談談對kotlin的理解
22、閉包和局部內部類的區別
閉包:是能獲取其他函數內部變量的函數。是一種能被調用的對象,它保存了創建它的作用域的信息。JAVA並不能顯式地支持閉包,但是在JAVA中,閉包可以通過“接口+內部類”來實現。
局部內部類:局部內部類就像是方法裏面的一個局部變量一樣,是不能有public、protected、private以及static修飾符的。
23、string 轉換成 integer的方式及原理
integer.parseInt(string str)方法調用Integer內部的parseInt(string str,10)方法,默認基數爲10,parseInt內部首先判斷字符串是否包含符號(-或者+),則對相應的negative和limit進行賦值,然後再循環字符串,對單個char進行數值計算Character.digit(char ch, int radix)在這個方法中,函數肯定進入到0-9字符的判斷(相對於string轉換到int),否則會拋出異常,數字就是如上面進行拼接然後生成的int類型數值。
24、哪些情況下的對象會被垃圾回收機制處理掉?
- 沒有引用指向;
- 只有弱引用指向並且不回收弱引用對象的話存儲區無空間;
- 虛引用指向的對象;
- 不能通過GC Roots尋找到(Java虛擬機採用可達性原理回收時),以下幾點對象可以被看做是GC Roots:
● 虛擬機棧(棧楨中的本地變量表)中的引用的對象;
● 方法區中的類靜態屬性引用的對象;
● 方法區中的常量引用的對象;
● 本地方法棧中JNI(Native方法)的引用的對象;
25、講一下常見編碼方式?
常見的一些字符編碼方式無非有:ASCII、拓展ASCII編碼、Unicode編碼集、GBK/GB2312/GB18030、UTF-8;
- ASCII編碼:用來表示英文,它使用1個字節表示,其中第一位規定爲0,其他7位存儲數據,一共可以表示128個字符。
- 拓展ASCII編碼:用於表示更多的歐洲文字,用8個位存儲數據,一共可以表示256個字符GBK/GB2312/GB18030:表示漢字。GBK/GB2312表示簡體中文,GB18030表示繁體中文。
- Unicode編碼:包含世界上所有的字符,是一個字符集。
- UTF-8:是Unicode字符的實現方式之一,它使用1-4個字符表示一個符號,根據不同的符號而變化字節長度。
26、utf-8編碼中的中文佔幾個字節;int型幾個字節?
UTF-8最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度。UTF-8的編碼規則很簡單,只有兩條:
- 對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的unicode碼。因此對於英語字母,UTF-8編碼和ASCII碼是相同的。
- 對於n字節的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一律設爲10。剩下的沒有提及的二進制位,全部爲這個符號的unicode碼。
少數是漢字每個佔用3個字節,多數佔用4個字節。int類型在java中佔4個字節。
27、靜態代理和動態代理的區別,什麼場景使用?
28、Java的異常體系
29、談談你對解析與分派的認識。
30、修改對象A的equals方法的簽名,那麼使用HashMap存放這個對象實例的時候,會調用哪個equals方法?
31、Java中實現多態的機制是什麼?
java中實現多態的機制是依靠父類或接口的引用指向子類。從而實現了一個對象多種形態的特性。其中父類的引用是在程序運行時動態的指向具體的實例,調用該引用的方法時,不是根據引用變量的類型中定義的方法來運行,而是根據具體的實例的方法。
32、如何將一個Java對象序列化到文件裏?
33、說說你對Java反射的理解
34、說說你對Java註解的理解
35、說說你對依賴注入的理解
依賴注入也叫依賴倒轉原則,是java設計理論中一條非常著名的原則。其核心思想就是要將這種具體類之間的依賴,儘量轉換成抽象依賴,也就是說類A應該依賴於抽象類IB,而不是具體的類B。這個注入的過程,通常是由一個控制程序來完成的,無需對象去關心,舉例如下:
Public Person{
private ICar car;
public Person(ICar onecar){
car=onecar;
}
public void drive(){
car.掛檔;
car.踩油門;
car.打方向;
}
}
這個時候,進行注入並且調用的過程,就很簡單了,如下:
Car car=new Car ();
Person boy=new Person(car);
boy.drive();
36、說一下泛型原理,並舉例說明
其實Java中的泛型是僞泛型,在編譯期間,所有的泛型信息都會被擦除掉。在生成的Java字節碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會在編譯器在編譯的時候去掉。這個過程就稱爲類型擦除。如在代碼中定義的List和List等類型,在編譯後都會編程List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的。Java編譯器會在編譯時儘可能的發現可能出錯的地方,但是仍然無法避免在運行時刻出現類型轉換異常的情況。
可以通過兩個簡單的例子,來證明java泛型的類型擦除:
ArrayList<String> array1=new ArrayList<String>();
array1.add("aaaa");
ArrayList<Integer> array2=new ArrayList<Integer>();
array2.add(10);
System.out.println(array1.getClass()==array2.getClass());
我們通過array1對象和array2對象的getClass方法獲取它們的類的信息,最後發現結果爲true。說明泛型類型String和Integer都被擦除掉了,只剩下了原始類型。
ArrayList<Integer> arrayList3=new ArrayList<Integer>();
arrayList3.add(1);//這樣調用add方法只能存儲整形,因爲泛型類型的實例爲Integer
arrayList3.getClass().getMethod("add", Object.class).invoke(arrayList3, "asd");
for (int i=0;i<arrayList3.size();i++) {
System.out.println(arrayList3.get(i));
}
當我們利用反射調用add方法的時候,卻可以存儲字符串。這說明了Integer泛型實例在編譯之後被擦除了,只保留了原始類型。
37、Java中String的瞭解
38、String爲什麼要設計成不可變的?
-
字符串常量池的需要
字符串常量池(String pool, String intern pool, String保留池) 是Java堆內存中一個特殊的存儲區域, 當創建一個String對象時,假如此字符串值已經存在於常量池中,則不會創建一個新的對象,而是引用已經存在的對象。 假若字符串對象允許改變,那麼將會導致各種邏輯錯誤,比如改變一個對象會影響到另一個獨立對象。嚴格來說,這種常量池的思想,是一種優化手段。 -
允許String對象緩存HashCode
Java中String對象的哈希碼被頻繁地使用, 比如在hashMap 等容器中。字符串不變性保證了hash碼的唯一性,因此可以放心地進行緩存.這也是一種性能優化手段,意味着不必每次都去計算新的哈希碼。 -
安全性
String被許多的Java類(庫)用來當做參數,例如 網絡連接地址URL,文件路徑path,還有反射機制所需要的String參數等, 假若String不是固定不變的,將會引起各種安全隱患。
39、Object類的equal和hashCode方法重寫,爲什麼?
因爲當把Object對象放到集合中時,通過equals比較對象,不做處理還是會出現重複的問題,根據hash原則,對象的映射地址是根據算法生成,因爲hash碰撞的存在,即兩個不同的對象hash地址可能一樣的情況,這樣在hash地址相等的情況下還需要去重寫equal方法進行比較。有兩種辦法可以解決這個問題,第一個就是重寫Object類的equal和hashCode方法;第二個就是把對象轉換成String再放入集合中,因爲String類源碼已經重寫了這兩個方法。