面向對象
1. 面向對象的特征有什么?
抽象,抽象是將一類對象的共同特征總結出來構造類的過程,包括數據抽象和行為抽象兩方面。
繼承,承是從已有類得到繼承信息創(chuàng)建新類的過程。
封裝,通常認為封裝是把數據和操作數據的方法綁定起來,對數據的訪問只能通過已定義的接口。
多態(tài),多態(tài)性是指允許不同子類型的對象對同一消息作出不同的響應。
2. 訪問修飾符public,private,protected,以及不寫(默認)時的區(qū)別?
答:區(qū)別如下:
作用域 當前類 同包 子類 其他
public √ √ √ √
protected √ √ √ ×
default √ √ × ×
private √ × × ×
類的成員不寫訪問修飾時默認為default。默認對于同一個包中的其他類相當于公開(public),對于不是同一個包中的其他類相當于私有(private)。受保護(protected)對子類相當于公開,對不是同一包中的沒有父子關系的類相當于私有。
3. String 是最基本的數據類型嗎?
答:不是。Java中的基本數據類型只有8個:byte、short、int、long、float、double、char、boolean;除了基本類型(primitive type)和枚舉類型(enumeration type),剩下的都是引用類型(reference type)。
4. float f=3.4;是否正確?
答:不正確。3.4是雙精度數,將雙精度型(double)賦值給浮點型(float)屬于下轉型(down-casting,也稱為窄化)會造成精度損失,因此需要強制類型轉換float f =(float)3.4; 或者寫成float f =3.4F;。
5. short s1 = 1; s1 = s1 + 1;有錯嗎?short s1 = 1; s1 += 1;有錯嗎?
答:對于short s1 = 1; s1 = s1 + 1;由于1是int類型,因此s1+1運算結果也是int 型,需要強制轉換類型才能賦值給short型。而short s1 = 1; s1 += 1;可以正確編譯,因為s1+= 1;相當于s1 = (short)(s1 + 1);其中有隱含的強制類型轉換。
6. int 和Integer 有什么區(qū)別?
Java為每一個基本數據類型都引入了對應的包裝類型(wrapper class),int的包裝類就是Integer
7. Integer 的 IntegerCache 緩存
Integer a = new Integer(3);
Integer b = 3;
int c = 3
a == b false 裝箱使用的是引用,包裝類型比較使用equals
a == c true a 自動拆箱為int 類型進行值比較
Integer f1 = 100,f2 = 100, f3 = 150, f4 = 150;
System.out.println(f1 == f2);
System.out.println(f3 == f4);
f1、f2、f3、f4四個變量都是Integer對象,所以下面的==運算比較的不是值而是引用。裝箱的本質是什么呢?當我們給一個Integer對象賦一個int值的時候,會調用Integer類的靜態(tài)方法valueOf,在IntegerCache 范圍內直接引用常量池中的對象,不會新創(chuàng)建對象。
字面量的值在-128到127之間,那么不會new新的Integer對象,而是直接引用常量池中的Integer對象,所以上面的面試題中f1==f2的結果是true,而f3==f4的結果是false。
8. &和&&的區(qū)別?
&運算符有兩種用法:(1)按位與;(2)邏輯與。&&運算符是短路與運算。
&&左邊的表達式的值是false,右邊的表達式會被直接短路掉,不會進行運算。
9. 解釋內存中的棧(stack)、堆(heap)和靜態(tài)存儲區(qū)的用法。
通常我們定義一個基本數據類型的變量,一個對象的引用,還有就是函數調用的現(xiàn)場保存都使用內存中的??臻g;而通過new關鍵字和構造器創(chuàng)建的對象放在堆空間;程序中的字面量(literal)如直接書寫的100、“hello”和常量都是放在靜態(tài)存儲區(qū)中。??臻g操作最快但是也很小,通常大量的對象都是放在堆空間,整個內存包括硬盤上的虛擬內存都可以被當成堆空間來使用。
String str = new String(“hello”);
上面的語句中str放在棧上,用new創(chuàng)建出來的字符串對象放在堆上,而“hello”這個字面量放在靜態(tài)存儲區(qū)。
10. switch 不支持 類型
switch(expr)中,expr可以是byte、short、char、int。從1.5版開始,Java中引入了枚舉類型(enum),expr也可以是枚舉,從JDK 1.7版開始,還可以是字符串(String)。長整型(long)是不可以的。
2 << 3(左移3位相當于乘以2的3次方)
11. java跳出多重嵌套循環(huán)
在最外層循環(huán)前加一個標記如A,然后用break A;可以跳出多重循環(huán)。但是盡量不要使用
12. 構造器是否可以被重寫?
構造器不能被繼承,因此不能被重寫,但可以被重載。
13. 兩個對象值相同(x.equals(y) == true),但卻可有不同的hash code,這句話對不對?
答:不對,如果兩個對象x和y滿足x.equals(y) == true,它們的哈希碼(hash code)應當相同。Java對于eqauls方法和hashCode方法是這樣規(guī)定的:(1)如果兩個對象相同(equals方法返回true),那么它們的hashCode值一定要相同;(2)如果兩個對象的hashCode相同,它們并不一定相同。
14. 當一個對象被當作參數傳遞到一個方法后,此方法可改變這個對象的屬性,并可返回變化后的結果,那么這里到底是值傳遞還是引用傳遞?
依然是值傳遞。Java 編程語言只有值傳遞參數。當一個對象實例作為一個參數被傳遞到方法中時,參數的值就是對該對象的引用。對象的屬性可以在被調用過程中被改變,但對象的引用是永遠不會改變的。
15. String 和StringBuilder、StringBuffer 的區(qū)別?
String和StringBuffer / StringBuilder,它們可以儲存和操作字符串。其中String是只讀字符串,也就意味著String引用的字符串內容是不能被改變的。而StringBuffer和StringBuilder類表示的字符串對象可以直接進行修改。StringBuilder是JDK 1.5中引入的,它和StringBuffer的方法完全相同,區(qū)別在于它是在單線程環(huán)境下使用的,因為它的所有方面都沒有被synchronized修飾,因此它的效率也比StringBuffer略高。
StringBuilder和StringBuffer的大部分方法均調用父類AbstractStringBuilder的實現(xiàn)。其擴容機制首先是把容量變?yōu)樵瓉砣萘康?倍加2。最大容量是Integer.MAX_VALUE,也就是0x7fffffff。
StringBuilder和StringBuffer的默認容量都是16,最好預先估計好字符串的大小避免擴容帶來的時間消耗。
StringBuffer為了實現(xiàn)同步,很多方法使用lSynchronized修飾
16. 有沒有哪種情況用+做字符串連接比調用StringBuffer / StringBuilder對象的append方法性能更好?
如果連接后得到的字符串在靜態(tài)存儲區(qū)中是早已存在的,那么用+做字符串連接是優(yōu)于StringBuffer / StringBuilder的append方法的。
17. JVM 字符串常量池引用關系
String a = "chenssy";
String b = "chenssy";
String c = new String("chenssy");
a、b和字面上的chenssy都是指向JVM字符串常量池中的"chenssy"對象,他們指向同一個對象。
new關鍵字一定會產生一個對象chenssy(注意這個chenssy和上面的chenssy不同),同時這個對象是存儲在堆中。所以上面應該產生了兩個對象:保存在棧中的c和保存堆中chenssy。但是在Java中根本就不存在兩個完全一模一樣的字符串對象。故堆中的chenssy應該是引用字符串常量池中chenssy。所以c、chenssy、池chenssy的關系應該是:c--->chenssy--->池chenssy
可以看出new是先在堆中建立對象,再在jvm字符串常量池中查找是否存在,存在也是通過指針去指向它。
用new String() 創(chuàng)建的字符串不是常量,不能在編譯期就確定,所以new String() 創(chuàng)建的字符串不放入常量池中,它們有自己的地址空間。
String使用private final char value[]來實現(xiàn)字符串的存儲,也就是說String對象創(chuàng)建之后,就不能再修改此對象中存儲的字符串內容
String 的編輯功能是通過創(chuàng)建一個新的對象來實現(xiàn)的,而不是對原有對象進行修改。
18. 重載和重寫的區(qū)別?
方法的重載和重寫都是實現(xiàn)多態(tài)的方式,區(qū)別在于前者實現(xiàn)的是編譯時的多態(tài)性,而后者實現(xiàn)的是運行時的多態(tài)性。重載發(fā)生在一個類中,同名的方法如果有不同的參數列表(參數類型不同、參數個數不同或者二者都不同)則視為重載;重寫發(fā)生在子類與父類之間,重寫要求子類被重寫方法與父類被重寫方法有相同的返回類型,比父類被重寫方法更好訪問,不能比父類被重寫方法聲明更多的異常(里氏代換原則)。重載對返回類型沒有特殊的要求。
重載可以返回類型相同但是參數不同
19. char 型變量中能不能存儲一個中文漢字
char類型可以存儲一個中文漢字,因為Java中使用的編碼是Unicode,一個char類型占2個字節(jié)(16bit)
20. 抽象類和接口有什么區(qū)別?
抽象類和接口都不能夠實例化,但可以定義抽象類和接口類型的引用。
一個類如果繼承了某個抽象類或者實現(xiàn)了某個接口都需要對其中的抽象方法全部進行實現(xiàn),否則該類仍然需要被聲明為抽象類。
接口比抽象類更加抽象,因為抽象類中可以定義構造器,可以有抽象方法和具體方法,而接口中不能定義構造器而且其中的方法全部都是抽象方法。
抽象類中的成員可以是private、默認、protected、public的,而接口中的成員全都是public的。
抽象類中可以定義成員變量,而接口中定義的成員變量實際上都是常量。有抽象方法的類必須被聲明為抽象類,而抽象類未必要有抽象方法。
21. List 實現(xiàn)類之間的區(qū)別?
ArrayList 是最常用的 List 實現(xiàn)類,內部是通過數組實現(xiàn)的,它允許對元素進行快速隨機訪問。數 組的缺點是每個元素之間不能有間隔,當數組大小不滿足時需要增加存儲能力,就要將已經有數 組的數據復制到新的存儲空間中。當從 ArrayList 的中間位置插入或者刪除元素時,需要對數組進 行復制、移動、代價比較高。因此,它適合隨機查找和遍歷,不適合插入和刪除。
Vector與ArrayList一樣,也是通過數組實現(xiàn)的,不同的是它支持線程的同步,即某一時刻只有一 個線程能夠寫 Vector
LinkedList是用鏈表結構存儲數據的,很適合數據的動態(tài)插入和刪除,隨機訪問和遍歷速度比較 慢。另外,他還提供了List接口中沒有定義的方法,專門用于操作表頭和表尾元素,可以當作堆 棧、隊列和雙向隊列使用。
22. Set 實現(xiàn)類之間的區(qū)別?
如果想要讓兩個不同的對象視為相等的,就必須覆蓋Object的hashCode方法和equals方 法。
HashSet存儲元素的順序并不是按照存入時的順序(和List顯然不 同) 而是按照哈希值來存的所以取數據也是按照哈希值取得。元素的哈希值是通過元素的 hashcode方法來獲取的, HashSet首先判斷兩個元素的哈希值,如果哈希值一樣,接著會比較 equals方法 如果 equls結果為true ,HashSet就視為同一個元素。如果equals 為false就不是 同一個元素。
TreeSet()是使用二叉樹的原理對新add()的對象按照指定的順序排序(升序、降序),每增 加一個對象都會進行排序,將對象插入的二叉樹指定的位置。 2. Integer和String對象都可以進行默認的TreeSet排序,而自定義類的對象是不可以的,自 己定義的類必須實現(xiàn)Comparable接口,并且覆寫相應的compareTo()函數,才可以正常使 用。 3. 在覆寫compare()函數時,要返回相應的值才能使TreeSet按照一定的規(guī)則來排序 4. 比較此對象與指定對象的順序。如果該對象小于、等于或大于指定對象,則分別返回負整 數、零或正整數。
LinkedHashSet 而言,它繼承與 HashSet、又基于 LinkedHashMap 來實現(xiàn)的。 LinkedHashSet 底層使用 LinkedHashMap 來保存所有元素,它繼承與 HashSet,其所有的方法 操作上又與HashSet相同
23. HashMap java7 與 java8 的異同?
hashmap 的 key 和 value 都可以為null
HashMap 非線程安全,即任一時刻可以有多個線程同時寫 HashMap,可能會導 致數據的不一致。如果需要滿足線程安全,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有線程安全的能力,或者使用 ConcurrentHashMap
java 7 :
HashMap 里面是一個數組,然后數組中每個元素是一個單向鏈表.實體是嵌套類 Entry 的實例,Entry 包含四個屬性:key, value, hash 值和用于單向鏈表的 next
java 8 :
Java8 對 HashMap 進行了一些修改,最大的不同就是利用了紅黑樹,所以其由 數組+鏈表+紅黑 樹 組成.
在 Java8 中,當鏈表中的元素超過了 8 個以后, 會將鏈表轉換為紅黑樹,在這些位置進行查找的時候可以降低時間復雜度為 O(logN)。
HashMap 默認的初始化大小為16。HashMap 會將其擴充為2的冪次方大小.
用之前還要先做對數組的長度取模運算,得到的余數才能用來要存放的位置也就是對應的數組下標。這個數組下標的計算方法是“ (n - 1) & hash ”。(n代表數組長度)。這也就解釋了 HashMap 的長度為什么是2的冪次方。
取余(%)操作中如果除數是2的冪次則等價于與其除數減一的與(&)操作(也就是說 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)?!?并且 采用二進制位操作 &,相對于%能夠提高運算效率,這就解釋了 HashMap 的長度為什么是2的冪次方
24. HashMap 多線程造成死鎖問題?
在多線程下,進行 put 操作會導致 HashMap 死循環(huán),原因在于 HashMap 的擴容 resize()方法。由于擴容是新建一個數組,復制原數據到數組。由于數組下標掛有鏈表,所以需要復制鏈表,但是多線程操作有可能導致環(huán)形鏈表。復制鏈表過程如下:
以下模擬2個線程同時擴容。假設,當前 HashMap 的空間為2(臨界值為1),hashcode 分別為 0 和 1,在散列地址 0 處有元素 A 和 B,這時候要添加元素 C,C 經過 hash 運算,得到散列地址為 1,這時候由于超過了臨界值,空間不夠,需要調用 resize 方法進行擴容,那么在多線程條件下,會出現(xiàn)條件競爭.先將 A 復制到新的 hash 表中,然后接著復制 B 到鏈頭(A 的前邊:B.next=A),本來 B.next=null,到此也就結束了(跟線程二一樣的過程),但是,由于線程二擴容的原因,將 B.next=A,所以,這里繼續(xù)復制A,讓 A.next=B,由此,環(huán)形鏈表出現(xiàn):B.next=A; A.next=B.
25. ConcurrentHashMap java 7 和 java 8
在JDK1.7的時候,ConcurrentHashMap(分段鎖) 對整個桶數組進行了分割分段(Segment),每一把鎖只鎖容器其中一部分數據,多線程訪問容器里不同數據段的數據,就不會存在鎖競爭,提高并發(fā)訪問率。(默認分配16個Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的時候已經摒棄了Segment的概念,而是直接用 Node 數組+鏈表+紅黑樹的數據結構來實現(xiàn),并發(fā)控制使用 synchronized 和 CAS 來操作。
ConcurrentHashMap取消了Segment分段鎖,采用CAS和synchronized來保證并發(fā)安全。數據結構跟HashMap1.8的結構類似,數組+鏈表/紅黑二叉樹。
synchronized只鎖定當前鏈表或紅黑二叉樹的首節(jié)點,這樣只要hash不沖突,就不會產生并發(fā),效率又提升N倍。
26. Java 內部類
- 靜態(tài)內部類
靜態(tài)內部類可以訪問外部類所有的靜態(tài)變量和方法,即使是private的也一樣。
靜態(tài)內部類和一般類一致,可以定義靜態(tài)變量、方法,構造方法等。
其它類使用靜態(tài)內部類需要使用“外部類.靜態(tài)內部類”方式,如下所示:Out.Inner inner = new Out.Inner();inner.print();
Java集合類HashMap內部就有一個靜態(tài)內部類Entry
- 成員內部類
成員內部類不能定義靜態(tài)方法和變量
- 局部內部類
只在某個方法中使用,則可以考慮使用局部類。
- 匿名內部類
匿名內部類我們必須要繼承一個父類或者實現(xiàn)一個接口,當然也僅能只繼承一個父類或者實現(xiàn)一 個接口。
27. Java 中會存在內存泄漏嗎 ?
Java因為有垃圾回收機制(GC)不會存在內存泄露問題(這也是Java被廣泛使用于服務器端編程的一個重要原因);然而在實際開發(fā)中,可能會存在無用但可達的對象,這些對象不能被GC回收也會發(fā)生內存泄露。
28. 抽象方法是否可以同時是靜態(tài)的,同時是本地方法,同時被synchronized修飾?
都不能。抽象方法需要子類重寫,而靜態(tài)的方法是無法被重寫的,因此二者是矛盾的。本地方法是由本地代碼(如C代碼)實現(xiàn)的方法,而抽象方法是沒有實現(xiàn)的,也是矛盾的。synchronized和方法的實現(xiàn)細節(jié)有關,抽象方法不涉及實現(xiàn)細節(jié),因此也是相互矛盾的。
29. 如何實現(xiàn)對象的克???
有兩種方式:
1.實現(xiàn)Cloneable接口并重寫Object類中的clone()方法;
2.實現(xiàn)Serializable接口,通過對象的序列化和反序列化實現(xiàn)克隆,可以實現(xiàn)真正的深度克隆。推薦這種,可以檢查出對象是否支持序列化。
30. 什么是類型擦除 ?
Java 中的泛型基本上都是在編譯器這個層次來實現(xiàn)的。在生成的 Java 字節(jié)代碼中是不包含泛 型中的類型信息的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。這個 過程就稱為類型擦除。如在代碼中定義的 List<Object>和 List<String>等類型,在編譯之后 都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的
31. try{}里有一個return語句,那么緊跟在這個try后的finally{}里的code會不會被執(zhí)行,什么時候被執(zhí)行,在return前還是后?
答:會執(zhí)行,在方法返回調用者前執(zhí)行。Java允許在finally中改變返回值的做法是不好的,因為如果存在finally代碼塊,try中的return語句不會立馬返回調用者,而是記錄下返回值待finally代碼塊執(zhí)行完畢之后再向調用者返回其值,然后如果在finally中修改了返回值。