java 多态判断非空_收藏Java 面试题全梳理
? 腳本之家
你與百萬(wàn)開(kāi)發(fā)者在一起
來(lái)源 | Java建設(shè)者(ID:javajianshe)
作者 |cxuan
如若轉(zhuǎn)載請(qǐng)聯(lián)系原公眾號(hào)
Java 基礎(chǔ)篇
Java 有哪些特點(diǎn)
- 并發(fā)性的:你可以在其中執(zhí)行許多語(yǔ)句,而不必一次執(zhí)行它
- 面向?qū)ο蟮?#xff1a;基于類(lèi)和面向?qū)ο蟮木幊陶Z(yǔ)言。
- 獨(dú)立性的:支持一次編寫(xiě),到處運(yùn)行的獨(dú)立編程語(yǔ)言,即編譯后的代碼可以在支持 Java 的所有平臺(tái)上運(yùn)行。
Java 的特性
Java 的特性有如下這幾點(diǎn)
簡(jiǎn)單,Java 會(huì)讓你的工作變得更加輕松,使你把關(guān)注點(diǎn)放在主要業(yè)務(wù)邏輯上,而不必關(guān)心指針、運(yùn)算符重載、內(nèi)存回收等與主要業(yè)務(wù)無(wú)關(guān)的功能。
便攜性,Java 是平臺(tái)無(wú)關(guān)性的,這意味著在一個(gè)平臺(tái)上編寫(xiě)的任何應(yīng)用程序都可以輕松移植到另一個(gè)平臺(tái)上。
安全性, 編譯后會(huì)將所有的代碼轉(zhuǎn)換為字節(jié)碼,人類(lèi)無(wú)法讀取。它使開(kāi)發(fā)無(wú)病毒,無(wú)篡改的系統(tǒng)/應(yīng)用成為可能。
動(dòng)態(tài)性,它具有適應(yīng)不斷變化的環(huán)境的能力,它能夠支持動(dòng)態(tài)內(nèi)存分配,從而減少了內(nèi)存浪費(fèi),提高了應(yīng)用程序的性能。
分布式,Java 提供的功能有助于創(chuàng)建分布式應(yīng)用。使用遠(yuǎn)程方法調(diào)用(RMI),程序可以通過(guò)網(wǎng)絡(luò)調(diào)用另一個(gè)程序的方法并獲取輸出。您可以通過(guò)從互聯(lián)網(wǎng)上的任何計(jì)算機(jī)上調(diào)用方法來(lái)訪問(wèn)文件。這是革命性的一個(gè)特點(diǎn),對(duì)于當(dāng)今的互聯(lián)網(wǎng)來(lái)說(shuō)太重要了。
健壯性,Java 有強(qiáng)大的內(nèi)存管理功能,在編譯和運(yùn)行時(shí)檢查代碼,它有助于消除錯(cuò)誤。
高性能,Java 最黑的科技就是字節(jié)碼編程,Java 代碼編譯成的字節(jié)碼可以輕松轉(zhuǎn)換為本地機(jī)器代碼。通過(guò) JIT 即時(shí)編譯器來(lái)實(shí)現(xiàn)高性能。
解釋性,Java 被編譯成字節(jié)碼,由 Java 運(yùn)行時(shí)環(huán)境解釋。
多線程性,Java支持多個(gè)執(zhí)行線程(也稱為輕量級(jí)進(jìn)程),包括一組同步原語(yǔ)。這使得使用線程編程更加容易,Java 通過(guò)管程模型來(lái)實(shí)現(xiàn)線程安全性。
描述一下值傳遞和引用傳遞的區(qū)別
要想真正理解的話,可以參考這篇文章 :https://www.zhihu.com/question/31203609
簡(jiǎn)單理解的話就是
值傳遞是指在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)復(fù)制一份到函數(shù)中,這樣的話如果函數(shù)對(duì)其傳遞過(guò)來(lái)的形式參數(shù)進(jìn)行修改,將不會(huì)影響到實(shí)際參數(shù)
引用傳遞是指在調(diào)用函數(shù)時(shí)將對(duì)象的地址直接傳遞到函數(shù)中,如果在對(duì)形式參數(shù)進(jìn)行修改,將影響到實(shí)際參數(shù)的值。
== 和 equals 區(qū)別是什么
==是 Java 中一種操作符,它有兩種比較方式
- 對(duì)于基本數(shù)據(jù)類(lèi)型來(lái)說(shuō), == 判斷的是兩邊的值是否相等
????Person?person1?=?new?Person(24,"boy");
????Person?person2?=?new?Person(24,"girl");
????int?c?=?10;
????private?void?doubleCompare(){
????????int?a?=?10;
????????int?b?=?10;
????????System.out.println(a?==?b);
????????System.out.println(a?==?c);
????????System.out.println(person1.getId()?==?person2.getId());
????}
}
- 對(duì)于引用類(lèi)型來(lái)說(shuō), == 判斷的是兩邊的引用是否相等,也就是判斷兩個(gè)對(duì)象是否指向了同一塊內(nèi)存區(qū)域。
??System.out.println(person1.getName().equals(person2.getName()));
}
equals是 Java 中所有對(duì)象的父類(lèi),即Object類(lèi)定義的一個(gè)方法。它只能比較對(duì)象,它表示的是引用雙方的值是否相等。所以記住,并不是說(shuō) == 比較的就是引用是否相等,equals 比較的就是值,這需要區(qū)分來(lái)說(shuō)的。
equals 用作對(duì)象之間的比較具有如下特性
- 自反性:對(duì)于任何非空引用 x 來(lái)說(shuō),x.equals(x) 應(yīng)該返回 true。
- 對(duì)稱性:對(duì)于任何非空引用 x 和 y 來(lái)說(shuō),若x.equals(y)為 true,則y.equals(x)也為 true。
- 傳遞性:對(duì)于任何非空引用的值來(lái)說(shuō),有三個(gè)值,x、y 和 z,如果x.equals(y) 返回true,y.equals(z) 返回true,那么x.equals(z) 也應(yīng)該返回true。
- 一致性:對(duì)于任何非空引用 x 和 y 來(lái)說(shuō),如果 x.equals(y) 相等的話,那么它們必須始終相等。
- 非空性:對(duì)于任何非空引用的值 x 來(lái)說(shuō),x.equals(null) 必須返回 false。
String 中的 equals 是如何重寫(xiě)的
String 代表的是 Java 中的字符串,String 類(lèi)比較特殊,它整個(gè)類(lèi)都是被final修飾的,也就是說(shuō),String 不能被任何類(lèi)繼承,任何修改String 字符串的方法都是創(chuàng)建了一個(gè)新的字符串。
equals 方法是 Object 類(lèi)定義的方法,Object 是所有類(lèi)的父類(lèi),當(dāng)然也包括 String,String 重寫(xiě)了equals方法,下面我們來(lái)看看是怎么重寫(xiě)的
- 首先會(huì)判斷要比較的兩個(gè)字符串它們的引用是否相等。如果引用相等的話,直接返回 true ,不相等的話繼續(xù)下面的判斷
- 然后再判斷被比較的對(duì)象是否是 String 的實(shí)例,如果不是的話直接返回 false,如果是的話,再比較兩個(gè)字符串的長(zhǎng)度是否相等,如果長(zhǎng)度不想等的話也就沒(méi)有比較的必要了;長(zhǎng)度如果相同,會(huì)比較字符串中的每個(gè)字符是否相等,一旦有一個(gè)字符不相等,就會(huì)直接返回 false。
下面是它的流程圖
這里再提示一下,你可能有疑惑什么時(shí)候是
if?(this?==?anObject)?{??return?true;
}
這個(gè)判斷語(yǔ)句如何才能返回 true?因?yàn)槎际亲址?#xff0c;字符串比較的不都是堆空間嗎,猛然一看發(fā)現(xiàn)好像永遠(yuǎn)也不會(huì)走,但是你忘記了String.intern()方法,它表示的概念在不同的 JDK 版本有不同的區(qū)分
在 JDK1.7 及以后調(diào)用 intern 方法是判斷運(yùn)行時(shí)常量池中是否有指定的字符串,如果沒(méi)有的話,就把字符串添加到常量池中,并返回常量池中的對(duì)象。
驗(yàn)證過(guò)程如下
private?void?StringOverrideEquals(){??String?s1?=?"aaa";
??String?s2?=?"aa"?+?new?String("a");
??String?s3?=?new?String("aaa");
??System.out.println(s1.intern().equals(s1));
??System.out.println(s1.intern().equals(s2));
??System.out.println(s3.intern().equals(s1));
}
首先 s1.intern.equals(s1) 這個(gè)無(wú)論如何都返回 true,因?yàn)?s1 字符串創(chuàng)建出來(lái)就已經(jīng)在常量池中存在了。
然后第二條語(yǔ)句返回 false,因?yàn)?s1 返回的是常量池中的對(duì)象,而 s2 返回的是堆中的對(duì)象
第三條語(yǔ)句 s3.intern.equals(s1),返回 true ,因?yàn)?s3 對(duì)象雖然在堆中創(chuàng)建了一個(gè)對(duì)象,但是 s3 中的 "aaa" 返回的是常量池中的對(duì)象。
為什么重寫(xiě) equals 方法必須重寫(xiě) hashcode 方法
equals 方法和 hashCode 都是 Object 中定義的方法,它們經(jīng)常被一起重寫(xiě)。
equals 方法是用來(lái)比較對(duì)象大小是否相等的方法,hashcode 方法是用來(lái)判斷每個(gè)對(duì)象 hash 值的一種方法。如果只重寫(xiě) equals 方法而不重寫(xiě) hashcode 方法,很可能會(huì)造成兩個(gè)不同的對(duì)象,它們的 hashcode 也相等,造成沖突。比如
String?str1?=?"通話";String?str2?=?"重地";
它們兩個(gè)的 hashcode 相等,但是 equals 可不相等。
我們來(lái)看一下 hashCode 官方的定義
總結(jié)起來(lái)就是
- 如果在 Java 運(yùn)行期間對(duì)同一個(gè)對(duì)象調(diào)用 hashCode 方法后,無(wú)論調(diào)用多少次,都應(yīng)該返回相同的 hashCode,但是在不同的 Java 程序中,執(zhí)行 hashCode 方法返回的值可能不一致。
- 如果兩個(gè)對(duì)象的 equals 相等,那么 hashCode 必須相同
- 如果兩個(gè)對(duì)象 equals 不相等,那么 hashCode 也有可能相同,所以需要重寫(xiě) hashCode 方法,因?yàn)槟悴恢?hashCode 的底層構(gòu)造(反正我是不知道,有大牛可以傳授傳授),所以你需要重寫(xiě) hashCode 方法,來(lái)為不同的對(duì)象生成不同的 hashCode 值,這樣能夠提高不同對(duì)象的訪問(wèn)速度。
- hashCode 通常是將地址轉(zhuǎn)換為整數(shù)來(lái)實(shí)現(xiàn)的。
String s1 = new String("abc") 在內(nèi)存中創(chuàng)建了幾個(gè)對(duì)象
一個(gè)或者兩個(gè),String s1 是聲明了一個(gè) String 類(lèi)型的 s1 變量,它不是對(duì)象。使用new關(guān)鍵字會(huì)在堆中創(chuàng)建一個(gè)對(duì)象,另外一個(gè)對(duì)象是abc,它會(huì)在常量池中創(chuàng)建,所以一共創(chuàng)建了兩個(gè)對(duì)象;如果 abc 在常量池中已經(jīng)存在的話,那么就會(huì)創(chuàng)建一個(gè)對(duì)象。
詳細(xì)請(qǐng)翻閱筆者的另外一篇文章一篇與眾不同的 String、StringBuffer、StringBuilder 詳解
String 為什么是不可變的、jdk 源碼中的 String 如何定義的、為什么這么設(shè)計(jì)。
首先了解一下什么是不可變對(duì)象,不可變對(duì)象就是一經(jīng)創(chuàng)建后,其對(duì)象的內(nèi)部狀態(tài)不能被修改,啥意思呢?也就是說(shuō)不可變對(duì)象需要遵守下面幾條原則
- 不可變對(duì)象的內(nèi)部屬性都是 final 的
- 不可變對(duì)象的內(nèi)部屬性都是 private 的
- 不可變對(duì)象不能提供任何可以修改內(nèi)部狀態(tài)的方法、setter 方法也不行
- 不可變對(duì)象不能被繼承和擴(kuò)展
與其說(shuō)問(wèn) String 為什么是不可變的,不如說(shuō)如何把 String 設(shè)計(jì)成不可變的。
String 類(lèi)是一種對(duì)象,它是獨(dú)立于 Java 基本數(shù)據(jù)類(lèi)型而存在的,String 你可以把它理解為字符串的集合,String 被設(shè)計(jì)為 final 的,表示 String 對(duì)象一經(jīng)創(chuàng)建后,它的值就不能再被修改,任何對(duì) String 值進(jìn)行修改的方法就是重新創(chuàng)建一個(gè)字符串。String 對(duì)象創(chuàng)建后會(huì)存在于運(yùn)行時(shí)常量池中,運(yùn)行時(shí)常量池是屬于方法區(qū)的一部分,JDK1.7 后把它移到了堆中。
不可變對(duì)象不是真的不可變,可以通過(guò)反射來(lái)對(duì)其內(nèi)部的屬性和值進(jìn)行修改,不過(guò)一般我們不這樣做。
static 關(guān)鍵字是干什么用的?談?wù)勀愕睦斫?span style="font-weight:bold;">
static 是 Java 中非常重要的關(guān)鍵字,static 表示的概念是靜態(tài)的,在 Java 中,static 主要用來(lái)
- 修飾變量,static 修飾的變量稱為靜態(tài)變量、也稱為類(lèi)變量,類(lèi)變量屬于類(lèi)所有,對(duì)于不同的類(lèi)來(lái)說(shuō),static 變量只有一份,static 修飾的變量位于方法區(qū)中;static 修飾的變量能夠直接通過(guò)類(lèi)名.變量名來(lái)進(jìn)行訪問(wèn),不用通過(guò)實(shí)例化類(lèi)再進(jìn)行使用。
- 修飾方法,static 修飾的方法被稱為靜態(tài)方法,靜態(tài)方法能夠直接通過(guò)類(lèi)名.方法名來(lái)使用,在靜態(tài)方法內(nèi)部不能使用非靜態(tài)屬性和方法
- static 可以修飾代碼塊,主要分為兩種,一種直接定義在類(lèi)中,使用static{},這種被稱為靜態(tài)代碼塊,一種是在類(lèi)中定義靜態(tài)內(nèi)部類(lèi),使用static class xxx來(lái)進(jìn)行定義。
- static 可以用于靜態(tài)導(dǎo)包,通過(guò)使用import static xxx?來(lái)實(shí)現(xiàn),這種方式一般不推薦使用
- static 可以和單例模式一起使用,通過(guò)雙重檢查鎖來(lái)實(shí)現(xiàn)線程安全的單例模式。
詳情請(qǐng)參考這篇文章一篇 static 還能難得住我?
final 關(guān)鍵字是干什么用的?談?wù)勀愕睦斫?span style="font-weight:bold;">
final 是 Java 中的關(guān)鍵字,它表示的意思是不可變的,在 Java 中,final 主要用來(lái)
- 修飾類(lèi),final 修飾的類(lèi)不能被繼承,不能被繼承的意思就是不能使用extends來(lái)繼承被 final 修飾的類(lèi)。
- 修飾變量,final 修飾的變量不能被改寫(xiě),不能被改寫(xiě)的意思有兩種,對(duì)于基本數(shù)據(jù)類(lèi)型來(lái)說(shuō),final 修飾的變量,其值不能被改變,final 修飾的對(duì)象,對(duì)象的引用不能被改變,但是對(duì)象內(nèi)部的屬性可以被修改。final 修飾的變量在某種程度上起到了不可變的效果,所以,可以用來(lái)保護(hù)只讀數(shù)據(jù),尤其是在并發(fā)編程中,因?yàn)槊鞔_的不能再為 final 變量進(jìn)行賦值,有利于減少額外的同步開(kāi)銷(xiāo)。
- 修飾方法,final 修飾的方法不能被重寫(xiě)。
- final 修飾符和 Java 程序性能優(yōu)化沒(méi)有必然聯(lián)系
抽象類(lèi)和接口的區(qū)別是什么
抽象類(lèi)和接口都是 Java 中的關(guān)鍵字,抽象類(lèi)和接口中都允許進(jìn)行方法的定義,而不用具體的方法實(shí)現(xiàn)。抽象類(lèi)和接口都允許被繼承,它們廣泛的應(yīng)用于 JDK 和框架的源碼中,來(lái)實(shí)現(xiàn)多態(tài)和不同的設(shè)計(jì)模式。
不同點(diǎn)在于
- 抽象級(jí)別不同:類(lèi)、抽象類(lèi)、接口其實(shí)是三種不同的抽象級(jí)別,抽象程度依次是 接口 > 抽象類(lèi) > 類(lèi)。在接口中,只允許進(jìn)行方法的定義,不允許有方法的實(shí)現(xiàn),抽象類(lèi)中可以進(jìn)行方法的定義和實(shí)現(xiàn);而類(lèi)中只允許進(jìn)行方法的實(shí)現(xiàn),我說(shuō)的方法的定義是不允許在方法后面出現(xiàn){}
- 使用的關(guān)鍵字不同:類(lèi)使用class來(lái)表示;抽象類(lèi)使用abstract class來(lái)表示;接口使用interface來(lái)表示
- 變量:接口中定義的變量只能是公共的靜態(tài)常量,抽象類(lèi)中的變量是普通變量。
重寫(xiě)和重載的區(qū)別
在 Java 中,重寫(xiě)和重載都是對(duì)同一方法的不同表現(xiàn)形式,下面我們針對(duì)重寫(xiě)和重載做一下簡(jiǎn)單的區(qū)分
- 子父級(jí)關(guān)系不同,重寫(xiě)是針對(duì)子級(jí)和父級(jí)的不同表現(xiàn)形式,而重載是在同一類(lèi)中的不同表現(xiàn)形式;
- 概念不同,子類(lèi)重寫(xiě)父類(lèi)的方法一般使用@override來(lái)表示;重寫(xiě)后的方法其方法的聲明和參數(shù)類(lèi)型、順序必須要與父類(lèi)完全一致;重載是針對(duì)同一類(lèi)中概念,它要求重載的方法必須滿足下面任何一個(gè)要求:方法參數(shù)的順序,參數(shù)的個(gè)數(shù),參數(shù)的類(lèi)型任意一個(gè)保持不同即可。
byte的取值范圍是多少,怎么計(jì)算出來(lái)的
byte 的取值范圍是 -128 -> 127 之間,一共是 256 。一個(gè) byte 類(lèi)型在計(jì)算機(jī)中占據(jù)一個(gè)字節(jié),那么就是 8 bit,所以最大就是 2^7 = 1111 1111。
Java 中用補(bǔ)碼來(lái)表示二進(jìn)制數(shù),補(bǔ)碼的最高位是符號(hào)位,最高位用 0 表示正數(shù),最高位 1 表示負(fù)數(shù),正數(shù)的補(bǔ)碼就是其本身,由于最高位是符號(hào)位,所以正數(shù)表示的就是 0111 1111 ,也就是 127。最大負(fù)數(shù)就是 1111 1111,這其中會(huì)涉及到兩個(gè) 0 ,一個(gè) +0 ,一個(gè) -0 ,+0 歸為正數(shù),也就是 0 ,-0 歸為負(fù)數(shù),也就是 -128,所以 byte 的范圍就是 -128 - 127。
HashMap 和 HashTable 的區(qū)別
相同點(diǎn)
HashMap 和 HashTable 都是基于哈希表實(shí)現(xiàn)的,其內(nèi)部每個(gè)元素都是key-value鍵值對(duì),HashMap 和 HashTable 都實(shí)現(xiàn)了 Map、Cloneable、Serializable 接口。
不同點(diǎn)
父類(lèi)不同:HashMap 繼承了AbstractMap類(lèi),而 HashTable 繼承了Dictionary類(lèi)
空值不同:HashMap 允許空的 key 和 value 值,HashTable 不允許空的 key 和 value 值。HashMap 會(huì)把 Null key 當(dāng)做普通的 key 對(duì)待。不允許 null key 重復(fù)。
- 線程安全性:HashMap 不是線程安全的,如果多個(gè)外部操作同時(shí)修改 HashMap 的數(shù)據(jù)結(jié)構(gòu)比如 add 或者是 delete,必須進(jìn)行同步操作,僅僅對(duì) key 或者 value 的修改不是改變數(shù)據(jù)結(jié)構(gòu)的操作。可以選擇構(gòu)造線程安全的 Map 比如Collections.synchronizedMap或者是ConcurrentHashMap。而 HashTable 本身就是線程安全的容器。
- 性能方面:雖然 HashMap 和 HashTable 都是基于單鏈表的,但是 HashMap 進(jìn)行 put 或者 get? 操作,可以達(dá)到常數(shù)時(shí)間的性能;而 HashTable 的 put 和 get 操作都是加了synchronized鎖的,所以效率很差。
- 初始容量不同:HashTable 的初始長(zhǎng)度是11,之后每次擴(kuò)充容量變?yōu)橹暗?2n+1(n為上一次的長(zhǎng)度)而 HashMap 的初始長(zhǎng)度為16,之后每次擴(kuò)充變?yōu)樵瓉?lái)的兩倍。創(chuàng)建時(shí),如果給定了容量初始值,那么HashTable 會(huì)直接使用你給定的大小,而 HashMap 會(huì)將其擴(kuò)充為2的冪次方大小。
HashMap 和 HashSet 的區(qū)別
HashSet 繼承于 AbstractSet 接口,實(shí)現(xiàn)了 Set、Cloneable,、java.io.Serializable 接口。HashSet 不允許集合中出現(xiàn)重復(fù)的值。HashSet 底層其實(shí)就是 HashMap,所有對(duì) HashSet 的操作其實(shí)就是對(duì) HashMap 的操作。所以 HashSet 也不保證集合的順序,也不是線程安全的容器。
HashMap 的底層結(jié)構(gòu)
JDK1.7 中,HashMap 采用位桶 + 鏈表的實(shí)現(xiàn),即使用鏈表來(lái)處理沖突,同一 hash 值的鏈表都存儲(chǔ)在一個(gè)數(shù)組中。但是當(dāng)位于一個(gè)桶中的元素較多,即 hash 值相等的元素較多時(shí),通過(guò) key 值依次查找的效率較低。
所以,與 JDK 1.7 相比,JDK 1.8 在底層結(jié)構(gòu)方面做了一些改變,當(dāng)每個(gè)桶中元素大于 8 的時(shí)候,會(huì)轉(zhuǎn)變?yōu)榧t黑樹(shù),目的就是優(yōu)化查詢效率。
HashMap 的長(zhǎng)度為什么是 2 的冪次方
這道題我想了幾天,之前和群里小伙伴們探討每日一題的時(shí)候,問(wèn)他們?yōu)槭裁?length%hash == (n - 1) & hash,它們說(shuō)相等的前提是 length 的長(zhǎng)度 2 的冪次方,然后我回了一句難道 length 還能不是 2 的冪次方嗎?其實(shí)是我沒(méi)有搞懂因果關(guān)系,因?yàn)?HashMap 的長(zhǎng)度是 2 的冪次方,所以使用余數(shù)來(lái)判斷在桶中的下標(biāo)。如果 length 的長(zhǎng)度不是 2 的冪次方,小伙伴們可以舉個(gè)例子來(lái)試試
例如長(zhǎng)度為 9 時(shí)候,3 & (9-1) = 0,2 & (9-1) = 0 ,都在 0 上,碰撞了;
這樣會(huì)增大 HashMap 碰撞的幾率。
HashMap 多線程操作導(dǎo)致死循環(huán)問(wèn)題
HashMap 不是一個(gè)線程安全的容器,在高并發(fā)場(chǎng)景下,應(yīng)該使用ConcurrentHashMap,在多線程場(chǎng)景下使用 HashMap 會(huì)造成死循環(huán)問(wèn)題(基于 JDK1.7),出現(xiàn)問(wèn)題的位置在rehash處,也就是
do?{????Entry?next?=?e.next;?//?int?i?=?indexFor(e.hash,?newCapacity);
????e.next?=?newTable[i];
????newTable[i]?=?e;
????e?=?next;
}?while?(e?!=?null);
這是 JDK1.7 的 rehash 代碼片段,在并發(fā)的場(chǎng)景下會(huì)形成環(huán)。
JDK1.8 也會(huì)造成死循環(huán)問(wèn)題。
HashMap 線程安全的實(shí)現(xiàn)有哪些
因?yàn)?HashMap 不是一個(gè)線程安全的容器,所以并發(fā)場(chǎng)景下推薦使用ConcurrentHashMap,或者使用線程安全的 HashMap,使用Collections包下的線程安全的容器,比如說(shuō)
Collections.synchronizedMap(new?HashMap());還可以使用 HashTable ,它也是線程安全的容器,基于 key-value 存儲(chǔ),經(jīng)常用 HashMap 和 HashTable 做比較就是因?yàn)?HashTable 的數(shù)據(jù)結(jié)構(gòu)和 HashMap 相同。
上面效率最高的就是 ConcurrentHashMap。
講一下 HashMap put 的過(guò)程
首先會(huì)使用 hash 函數(shù)來(lái)計(jì)算 key,然后執(zhí)行真正的插入方法
final?V?putVal(int?hash,?K?key,?V?value,?boolean?onlyIfAbsent,boolean?evict)?{??Node[]?tab;?Node?p;?int?n,?i;//?如果table?為null?或者沒(méi)有為table分配內(nèi)存,就resize一次if?((tab?=?table)?==?null?||?(n?=?tab.length)?==?0)
????n?=?(tab?=?resize()).length;//?指定hash值節(jié)點(diǎn)為空則直接插入,這個(gè)(n?-?1)?&?hash才是表中真正的哈希if?((p?=?tab[i?=?(n?-?1)?&?hash])?==?null)
????tab[i]?=?newNode(hash,?key,?value,?null);//?如果不為空else?{
????Node?e;?K?k;//?計(jì)算表中的這個(gè)真正的哈希值與要插入的key.hash相比if?(p.hash?==?hash?&&
????????((k?=?p.key)?==?key?||?(key?!=?null?&&?key.equals(k))))
??????e?=?p;//?若不同的話,并且當(dāng)前節(jié)點(diǎn)已經(jīng)在?TreeNode?上了else?if?(p?instanceof?TreeNode)//?采用紅黑樹(shù)存儲(chǔ)方式
??????e?=?((TreeNode)p).putTreeVal(this,?tab,?hash,?key,?value);//?key.hash?不同并且也不再?TreeNode?上,在鏈表上找到?p.next==nullelse?{for?(int?binCount?=?0;?;?++binCount)?{if?((e?=?p.next)?==?null)?{//?在表尾插入
??????????p.next?=?newNode(hash,?key,?value,?null);//?新增節(jié)點(diǎn)后如果節(jié)點(diǎn)個(gè)數(shù)到達(dá)閾值,則進(jìn)入?treeifyBin()?進(jìn)行再次判斷if?(binCount?>=?TREEIFY_THRESHOLD?-?1)?//?-1?for?1st
????????????treeifyBin(tab,?hash);break;
????????}//?如果找到了同hash、key的節(jié)點(diǎn),那么直接退出循環(huán)if?(e.hash?==?hash?&&
????????????((k?=?e.key)?==?key?||?(key?!=?null?&&?key.equals(k))))break;//?更新?p?指向下一節(jié)點(diǎn)
????????p?=?e;
??????}
????}//?map中含有舊值,返回舊值if?(e?!=?null)?{?//?existing?mapping?for?key
??????V?oldValue?=?e.value;if?(!onlyIfAbsent?||?oldValue?==?null)
????????e.value?=?value;
??????afterNodeAccess(e);return?oldValue;
????}
??}//?map調(diào)整次數(shù)?+?1
??++modCount;//?鍵值對(duì)的數(shù)量達(dá)到閾值,需要擴(kuò)容if?(++size?>?threshold)
????resize();
??afterNodeInsertion(evict);return?null;
}
HashMap put 方法的核心就是在putval方法,它的插入過(guò)程如下
- 首先會(huì)判斷 HashMap 中是否是新構(gòu)建的,如果是的話會(huì)首先進(jìn)行 resize
- 然后判斷需要插入的元素在 HashMap 中是否已經(jīng)存在(說(shuō)明出現(xiàn)了碰撞情況),如果不存在,直接生成新的k-v 節(jié)點(diǎn)存放,再判斷是否需要擴(kuò)容。
- 如果要插入的元素已經(jīng)存在的話,說(shuō)明發(fā)生了沖突,這就會(huì)轉(zhuǎn)換成鏈表或者紅黑樹(shù)來(lái)解決沖突,首先判斷鏈表中的 hash,key 是否相等,如果相等的話,就用新值替換舊值,如果節(jié)點(diǎn)是屬于 TreeNode 類(lèi)型,會(huì)直接在紅黑樹(shù)中進(jìn)行處理,如果 hash ,key 不相等也不屬于 TreeNode 類(lèi)型,會(huì)直接轉(zhuǎn)換為鏈表處理,進(jìn)行鏈表遍歷,如果鏈表的 next 節(jié)點(diǎn)是 null,判斷是否轉(zhuǎn)換為紅黑樹(shù),如果不轉(zhuǎn)換的話,在遍歷過(guò)程中找到 key 完全相等的節(jié)點(diǎn),則用新節(jié)點(diǎn)替換老節(jié)點(diǎn)
ConcurrentHashMap 底層實(shí)現(xiàn)
ConcurrentHashMap 是線程安全的 Map,它也是高并發(fā)場(chǎng)景下的首選數(shù)據(jù)結(jié)構(gòu),ConcurrentHashMap 底層是使用分段鎖來(lái)實(shí)現(xiàn)的。
Integer 緩存池
Integer 緩存池也就是IntegerCache,它是 Integer 的靜態(tài)內(nèi)部類(lèi)。
它的默認(rèn)值用于緩存 -128 - 127 之間的數(shù)字,如果有 -128 - 127 之間的數(shù)字的話,使用 new Integer 不用創(chuàng)建對(duì)象,會(huì)直接從緩存池中取,此操作會(huì)減少堆中對(duì)象的分配,有利于提高程序的運(yùn)行效率。
例如創(chuàng)建一個(gè) Integer a = 24,其實(shí)是調(diào)用 Integer 的valueOf,可以通過(guò)反編譯得出這個(gè)結(jié)論
然后我們看一下 valueOf 方法
如果在指定緩存池范圍內(nèi)的話,會(huì)直接返回緩存的值而不用創(chuàng)建新的 Integer 對(duì)象。
緩存的大小可以使用XX:AutoBoxCacheMax來(lái)指定,在 VM 初始化時(shí),java.lang.Integer.IntegerCache.high屬性會(huì)設(shè)置和保存在sun.misc.VM的私有系統(tǒng)屬性中。
UTF-8 和 Unicode 的關(guān)系
由于每個(gè)國(guó)家都有自己獨(dú)有的字符編碼,所以Unicode 的發(fā)展旨在創(chuàng)建一個(gè)新的標(biāo)準(zhǔn),用來(lái)映射當(dāng)今使用的大多數(shù)語(yǔ)言中的字符,這些字符有一些不是必要的,但是對(duì)于創(chuàng)建文本來(lái)說(shuō)卻是不可或缺的。Unicode 統(tǒng)一了所有字符的編碼,是一個(gè) Character Set,也就是字符集,字符集只是給所有的字符一個(gè)唯一編號(hào),但是卻沒(méi)有規(guī)定如何存儲(chǔ),不同的字符其存儲(chǔ)空間不一樣,有的需要一個(gè)字節(jié)就能存儲(chǔ),有的則需要2、3、4個(gè)字節(jié)。
UTF-8 只是眾多能夠?qū)ξ谋咀址M(jìn)行解碼的一種方式,它是一種變長(zhǎng)的方式。UTF-8 代表 8 位一組表示 Unicode 字符的格式,使用 1 - 4 個(gè)字節(jié)來(lái)表示字符。
U+ 0000 ~ U+ 007F: 0XXXXXXXU+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXX
U+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXX
U+10000 ~ U+1FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX
可以看到,UTF-8 通過(guò)開(kāi)頭的標(biāo)志位位數(shù)實(shí)現(xiàn)了變長(zhǎng)。對(duì)于單字節(jié)字符,只占用一個(gè)字節(jié),實(shí)現(xiàn)了向下兼容 ASCII,并且能和 UTF-32 一樣,包含 Unicode 中的所有字符,又能有效減少存儲(chǔ)傳輸過(guò)程中占用的空間。
項(xiàng)目為 UTF-8 環(huán)境,char c = '中',是否合法
可以,因?yàn)?Unicode 編碼采用 2 個(gè)字節(jié)的編碼,UTF-8 是 Unicode 的一種實(shí)現(xiàn),它使用可變長(zhǎng)度的字符集進(jìn)行編碼,char c = '中' 是兩個(gè)字節(jié),所以能夠存儲(chǔ)。合法。
Arrays.asList 獲得的 List 應(yīng)該注意什么
Arrays.asList是 Array 中的一個(gè)靜態(tài)方法,它能夠?qū)崿F(xiàn)把數(shù)組轉(zhuǎn)換成為 List 序列,需要注意下面幾點(diǎn)
- Arrays.asList 轉(zhuǎn)換完成后的 List 不能再進(jìn)行結(jié)構(gòu)化的修改,什么是結(jié)構(gòu)化的修改?就是不能再進(jìn)行任何 List 元素的增加或者減少的操作。
??Integer[]?integer?=?new?Integer[]?{?1,?2,?3,?4?};
??List?integetList?=?Arrays.asList(integer);
??integetList.add(5);
}
結(jié)果會(huì)直接拋出
Exception?in?thread?"main"?java.lang.UnsupportedOperationException我們看一下源碼就能發(fā)現(xiàn)問(wèn)題
//?這是?java.util.Arrays?的內(nèi)部類(lèi),而不是?java.util.ArrayList?private?static?class?ArrayList<E>?extends?AbstractList<E>implements?RandomAccess,?java.io.Serializable
繼承 AbstractList 中對(duì) add、remove、set 方法是直接拋異常的,也就是說(shuō)如果繼承的子類(lèi)沒(méi)有去重寫(xiě)這些方法,那么子類(lèi)的實(shí)例去調(diào)用這些方法是會(huì)直接拋異常的。
下面是AbstractList中方法的定義,我們可以看到具體拋出的異常:
public?void?add(int?index,?E?element)?{??throw?new?UnsupportedOperationException();
}
public?E?remove(int?index)?{
??throw?new?UnsupportedOperationException();
}
public?E?set(int?index,?E?element)?{
??throw?new?UnsupportedOperationException();
}
雖然 set 方法也拋出了一場(chǎng),但是由于 內(nèi)部類(lèi) ArrayList 重寫(xiě)了 set 方法,所以支持其可以對(duì)元素進(jìn)行修改。
- Arrays.asList 不支持基礎(chǔ)類(lèi)型的轉(zhuǎn)換
Java 中的基礎(chǔ)數(shù)據(jù)類(lèi)型(byte,short,int,long,float,double,boolean)是不支持使用 Arrays.asList 方法去轉(zhuǎn)換的
Collection 和 Collections 的區(qū)別
Collection 和 Collections 都是位于java.util包下的類(lèi)
Collection 是集合類(lèi)的父類(lèi),它是一個(gè)頂級(jí)接口,大部分抽象類(lèi)比如說(shuō)AbstractList、AbstractSet都繼承了 Collection 類(lèi),Collection 類(lèi)只定義一節(jié)標(biāo)準(zhǔn)方法比如說(shuō) add、remove、set、equals 等,具體的方法由抽象類(lèi)或者實(shí)現(xiàn)類(lèi)去實(shí)現(xiàn)。
Collections 是集合類(lèi)的工具類(lèi),Collections 提供了一些工具類(lèi)的基本使用
- sort 方法,對(duì)當(dāng)前集合進(jìn)行排序, 實(shí)現(xiàn) Comparable 接口的類(lèi),只能使用一種排序方案,這種方案叫做自然比較
- 比如實(shí)現(xiàn)線程安全的容器Collections.synchronizedList、Collections.synchronizedMap等
- reverse 反轉(zhuǎn),使用 reverse 方法可以根據(jù)元素的自然順序 對(duì)指定列表按降序進(jìn)行排序。
- fill,使用指定元素替換指定列表中的所有元素。
有很多用法,讀者可以翻閱 Collections 的源碼查看,Collections 不能進(jìn)行實(shí)例化,所以 Collections 中的方法都是由Collections.方法直接調(diào)用。
你知道 fail-fast 和 fail-safe 嗎
fail-fast是 Java 中的一種快速失敗機(jī)制,java.util 包下所有的集合都是快速失敗的,快速失敗會(huì)拋出ConcurrentModificationException異常,fail-fast 你可以把它理解為一種快速檢測(cè)機(jī)制,它只能用來(lái)檢測(cè)錯(cuò)誤,不會(huì)對(duì)錯(cuò)誤進(jìn)行恢復(fù),fail-fast 不一定只在多線程環(huán)境下存在,ArrayList 也會(huì)拋出這個(gè)異常,主要原因是由于modCount 不等于 expectedModCount。
fail-safe是 Java 中的一種安全失敗機(jī)制,它表示的是在遍歷時(shí)不是直接在原集合上進(jìn)行訪問(wèn),而是先復(fù)制原有集合內(nèi)容,在拷貝的集合上進(jìn)行遍歷。由于迭代時(shí)是對(duì)原集合的拷貝進(jìn)行遍歷,所以在遍歷過(guò)程中對(duì)原集合所作的修改并不能被迭代器檢測(cè)到,所以不會(huì)觸發(fā) ConcurrentModificationException。java.util.concurrent包下的容器都是安全失敗的,可以在多線程條件下使用,并發(fā)修改。
ArrayList、LinkedList 和 Vector 的區(qū)別
這也是一道老生常談的問(wèn)題了
ArrayList、LinkedList、Vector都是位于java.util包下的工具類(lèi),它們都實(shí)現(xiàn)了 List 接口。
- ArrayList 的底層是動(dòng)態(tài)數(shù)組,它是基于數(shù)組的特性而演變出來(lái)的,所以ArrayList 遍歷訪問(wèn)非常快,但是增刪比較慢,因?yàn)闀?huì)涉及到數(shù)組的拷貝。ArrayList 是一個(gè)非線程安全的容器,在并發(fā)場(chǎng)景下會(huì)造成問(wèn)題,如果想使用線程安全的容器的話,推薦使用Collections.synchronizedList;ArrayList 在擴(kuò)容時(shí)會(huì)增加 50% 的容量。
- LinkedList 的底層是雙向鏈表,所以 LinkedList 的增加和刪除非常快,只需把元素刪除,把各自的指針指向新的元素即可。但是 LinkedList 遍歷比較慢,因?yàn)橹挥忻看卧L問(wèn)一個(gè)元素才能知道下一個(gè)元素的值。LinkedList 也是一個(gè)非線程安全的容器,推薦使用Collections.synchronizedList
- Vector 向量是最早出現(xiàn)的集合容器,Vector 是一個(gè)線程安全的容器,它的每個(gè)方法都粗暴的加上了synchronized鎖,所以它的增刪、遍歷效率都很低。Vector 在擴(kuò)容時(shí),它的容量會(huì)增加一倍。
Exception 和 Error 有什么區(qū)別
Exception 泛指的是異常,Exception 主要分為兩種異常,一種是編譯期出現(xiàn)的異常,稱為checkedException,一種是程序運(yùn)行期間出現(xiàn)的異常,稱為uncheckedException,常見(jiàn)的 checkedException 有IOException,uncheckedException 統(tǒng)稱為RuntimeException,常見(jiàn)的 RuntimeException 主要有NullPointerException、IllegalArgumentException、ArrayIndexOutofBoundException等,Exception 可以被捕獲。
Error 是指程序運(yùn)行過(guò)程中出現(xiàn)的錯(cuò)誤,通常情況下會(huì)造成程序的崩潰,Error 通常是不可恢復(fù)的,Error 不能被捕獲。
詳細(xì)可以參考這篇文章看完這篇 Exception 和 Error ,和面試官扯皮就沒(méi)問(wèn)題了
String、StringBuilder 和 StringBuffer 有什么區(qū)別
String 特指的是 Java 中的字符串,String 類(lèi)位于java.lang包下,String 類(lèi)是由 final 修飾的,String 字符串一旦創(chuàng)建就不能被修改,任何對(duì) String 進(jìn)行修改的操作都相當(dāng)于重新創(chuàng)建了一個(gè)字符串。String 字符串的底層使用 StringBuilder 來(lái)實(shí)現(xiàn)的
StringBuilder 位于java.util包下,StringBuilder 是一非線程安全的容器,StringBuilder 的 append 方法常用于字符串拼接,它的拼接效率要比 String 中+號(hào)的拼接效率高。StringBuilder 一般不用于并發(fā)環(huán)境
StringBuffer 位于java.util包下,StringBuffer 是一個(gè)線程安全的容器,多線程場(chǎng)景下一般使用 StringBuffer 用作字符串的拼接
StringBuilder 和 StringBuffer 都是繼承于AbstractStringBuilder類(lèi),AbstractStringBuilder 類(lèi)實(shí)現(xiàn)了 StringBuffer 和 StringBuilder 的常規(guī)操作。
動(dòng)態(tài)代理是基于什么原理
代理一般分為靜態(tài)代理和動(dòng)態(tài)代理,它們都是代理模式的一種應(yīng)用,靜態(tài)代理指的是在程序運(yùn)行前已經(jīng)編譯好,程序知道由誰(shuí)來(lái)執(zhí)行代理方法。
而動(dòng)態(tài)代理只有在程序運(yùn)行期間才能確定,相比于靜態(tài)代理, 動(dòng)態(tài)代理的優(yōu)勢(shì)在于可以很方便的對(duì)代理類(lèi)的函數(shù)進(jìn)行統(tǒng)一的處理,而不用修改每個(gè)代理類(lèi)中的方法。可以說(shuō)動(dòng)態(tài)代理是基于反射實(shí)現(xiàn)的。通過(guò)反射我們可以直接操作類(lèi)或者對(duì)象,比如獲取類(lèi)的定義,獲取聲明的屬性和方法,調(diào)用方法,在運(yùn)行時(shí)可以修改類(lèi)的定義。
動(dòng)態(tài)代理是一種在運(yùn)行時(shí)構(gòu)建代理、動(dòng)態(tài)處理方法調(diào)用的機(jī)制。動(dòng)態(tài)代理的實(shí)現(xiàn)方式有很多,Java 提供的代理被稱為JDK 動(dòng)態(tài)代理,JDK 動(dòng)態(tài)代理是基于類(lèi)的繼承。
int 和 Integer 的區(qū)別
int 和 Integer 區(qū)別可就太多了
- int 是 Java 中的基本數(shù)據(jù)類(lèi)型,int 代表的是整型,一個(gè) int 占 4 字節(jié),也就是 32 位,int 的初始值是默認(rèn)值是 0 ,int 在 Java 內(nèi)存模型中被分配在棧中,int 沒(méi)有方法。
- Integer 是 Java 中的基本數(shù)據(jù)類(lèi)型的包裝類(lèi),Integer 是一個(gè)對(duì)象,Integer 可以進(jìn)行方法調(diào)用,Integer 的默認(rèn)值是 null,Integer 在 Java 內(nèi)存模型中被分配在堆中。int 和 Integer 在計(jì)算時(shí)可以進(jìn)行相互轉(zhuǎn)換,int -> Integer 的過(guò)程稱為裝箱,Integer -> int 的過(guò)程稱為拆箱,Integer 還有 IntegerCache ,會(huì)自動(dòng)緩存 -128 - 127 中的值
Java 提供了哪些 I/O 方式
Java I/O 方式有很多種,傳統(tǒng)的 I/O 也稱為BIO,主要流有如下幾種
Java I/O 包的實(shí)現(xiàn)比較簡(jiǎn)單,但是容易出現(xiàn)性能瓶頸,傳統(tǒng)的 I/O 是基于同步阻塞的。
JDK 1.4 之后提供了NIO,也就是位于java.nio包下,提供了基于channel、Selector、Buffer的抽象,可以構(gòu)建多路復(fù)用、同步非阻塞 I/O 程序。
JDK 1.7 之后對(duì) NIO 進(jìn)行了進(jìn)一步改進(jìn),引入了異步非阻塞的方式,也被稱為AIO(Asynchronous IO)。可以用生活中的例子來(lái)說(shuō)明:項(xiàng)目經(jīng)理交給手下員工去改一個(gè) bug,那么項(xiàng)目經(jīng)理不會(huì)一直等待員工解決 bug,他肯定在員工解決 bug 的期間給其他手下分配 bug 或者做其他事情,員工解決完 bug 之后再告訴項(xiàng)目經(jīng)理 bug 解決完了。
談?wù)勀阒赖脑O(shè)計(jì)模式
一張思維導(dǎo)圖鎮(zhèn)場(chǎng)
比如全局唯一性可以用單例模式。
可以使用策略模式優(yōu)化過(guò)多的 if...else...
制定標(biāo)準(zhǔn)用模版模式
接手其他人的鍋,但不想改原來(lái)的類(lèi)用適配器模式
使用組合而不是繼承
使用裝飾器可以制作加糖、加奶酪的咖啡
代理可以用于任何中間商......
Comparator 和 Comparable 有什么不同
Comparable 更像是自然排序
Comparator 更像是定制排序
同時(shí)存在時(shí)采用 Comparator(定制排序)的規(guī)則進(jìn)行比較。
對(duì)于一些普通的數(shù)據(jù)類(lèi)型(比如 String, Integer, Double…),它們默認(rèn)實(shí)現(xiàn)了Comparable 接口,實(shí)現(xiàn)了 compareTo 方法,我們可以直接使用。
而對(duì)于一些自定義類(lèi),它們可能在不同情況下需要實(shí)現(xiàn)不同的比較策略,我們可以新創(chuàng)建 Comparator 接口,然后使用特定的 Comparator 實(shí)現(xiàn)進(jìn)行比較。
Object 類(lèi)中一般都有哪些方法
Object 類(lèi)是所有對(duì)象的父類(lèi),它里面包含一些所有對(duì)象都能夠使用的方法
- hashCode():用于計(jì)算對(duì)象的哈希碼
- equals():用于對(duì)象之間比較值是否相等
- toString(): 用于把對(duì)象轉(zhuǎn)換成為字符串
- clone(): 用于對(duì)象之間的拷貝
- wait(): 用于實(shí)現(xiàn)對(duì)象之間的等待
- notify(): 用于通知對(duì)象釋放資源
- notifyAll(): 用于通知所有對(duì)象釋放資源
- finalize(): 用于告知垃圾回收器進(jìn)行垃圾回收
- getClass(): 用于獲得對(duì)象類(lèi)
Java 泛型和類(lèi)型擦除
關(guān)于 Java 泛型和擦除看著一篇就夠了。http://softlab.sdut.edu.cn/blog/subaochen/2017/01/generics-type-erasure/
反射的基本原理,反射創(chuàng)建類(lèi)實(shí)例的三種方式是什么
反射機(jī)制就是使 Java 程序在運(yùn)行時(shí)具有自省(introspect)的能力,通過(guò)反射我們可以直接操作類(lèi)和對(duì)象,比如獲取某個(gè)類(lèi)的定義,獲取類(lèi)的屬性和方法,構(gòu)造方法等。
創(chuàng)建類(lèi)實(shí)例的三種方式是
- 對(duì)象實(shí)例.getClass();
- 通過(guò) Class.forName() 創(chuàng)建
- 對(duì)象實(shí)例.newInstance() 方法創(chuàng)建
強(qiáng)引用、若引用、虛引用和幻象引用的區(qū)別
我們說(shuō)的不同的引用類(lèi)型其實(shí)都是邏輯上的,而對(duì)于虛擬機(jī)來(lái)說(shuō),主要體現(xiàn)的是對(duì)象的不同的可達(dá)性(reachable)狀態(tài)和對(duì)垃圾收集(garbage collector)的影響。
可以通過(guò)下面的流程來(lái)對(duì)對(duì)象的生命周期做一個(gè)總結(jié)
對(duì)象被創(chuàng)建并初始化,對(duì)象在運(yùn)行時(shí)被使用,然后離開(kāi)對(duì)象的作用域,對(duì)象會(huì)變成不可達(dá)并會(huì)被垃圾收集器回收。圖中用紅色標(biāo)明的區(qū)域表示對(duì)象處于強(qiáng)可達(dá)階段。
JDK1.2 介紹了java.lang.ref包,對(duì)象的生命周期有四個(gè)階段:?強(qiáng)可達(dá)?(Strongly Reachable?)、軟可達(dá)(Soft Reachable?)、弱可達(dá)(Weak Reachable?)、幻象可達(dá)(Phantom Reachable?)。
如果只討論符合垃圾回收條件的對(duì)象,那么只有三種:軟可達(dá)、弱可達(dá)和幻象可達(dá)。
軟可達(dá):軟可達(dá)就是?我們只能通過(guò)軟引用?才能訪問(wèn)的狀態(tài),軟可達(dá)的對(duì)象是由SoftReference引用的對(duì)象,并且沒(méi)有強(qiáng)引用的對(duì)象。軟引用是用來(lái)描述一些還有用但是非必須的對(duì)象。垃圾收集器會(huì)盡可能長(zhǎng)時(shí)間的保留軟引用的對(duì)象,但是會(huì)在發(fā)生OutOfMemoryError之前,回收軟引用的對(duì)象。如果回收完軟引用的對(duì)象,內(nèi)存還是不夠分配的話,就會(huì)直接拋出 OutOfMemoryError。
弱可達(dá):弱可達(dá)的對(duì)象是WeakReference引用的對(duì)象。垃圾收集器可以隨時(shí)收集弱引用的對(duì)象,不會(huì)嘗試保留軟引用的對(duì)象。
幻象可達(dá):幻象可達(dá)是由PhantomReference引用的對(duì)象,幻象可達(dá)就是沒(méi)有強(qiáng)、軟、弱引用進(jìn)行關(guān)聯(lián),并且已經(jīng)被 finalize 過(guò)了,只有幻象引用指向這個(gè)對(duì)象的時(shí)候。
除此之外,還有強(qiáng)可達(dá)和不可達(dá)的兩種可達(dá)性判斷條件
- 強(qiáng)可達(dá):就是一個(gè)對(duì)象剛被創(chuàng)建、初始化、使用中的對(duì)象都是處于強(qiáng)可達(dá)的狀態(tài)
- 不可達(dá)(unreachable):處于不可達(dá)的對(duì)象就意味著對(duì)象可以被清除了。
下面是一個(gè)不同可達(dá)性狀態(tài)的轉(zhuǎn)換圖
判斷可達(dá)性條件,也是 JVM 垃圾收集器決定如何處理對(duì)象的一部分考慮因素。
所有的對(duì)象可達(dá)性引用都是java.lang.ref.Reference的子類(lèi),它里面有一個(gè)get()方法,返回引用對(duì)象。如果已通過(guò)程序或垃圾收集器清除了此引用對(duì)象,則此方法返回 null 。也就是說(shuō),除了幻象引用外,軟引用和弱引用都是可以得到對(duì)象的。而且這些對(duì)象可以人為拯救,變?yōu)閺?qiáng)引用,例如把 this 關(guān)鍵字賦值給對(duì)象,只要重新和引用鏈上的任意一個(gè)對(duì)象建立關(guān)聯(lián)即可。
final、finally 和 finalize() 的區(qū)別
這三者可以說(shuō)是沒(méi)有任何關(guān)聯(lián)之處,我們上面談到了,final 可以用來(lái)修飾類(lèi)、變量和方法,可以參考上面 final 的那道面試題。
finally 是一個(gè)關(guān)鍵字,它經(jīng)常和 try 塊一起使用,用于異常處理。使用 try...finally 的代碼塊種,finally 部分的代碼一定會(huì)被執(zhí)行,所以我們經(jīng)常在 finally 方法中用于資源的關(guān)閉操作。
JDK1.7 中,推薦使用try-with-resources優(yōu)雅的關(guān)閉資源,它直接使用 try(){} 進(jìn)行資源的關(guān)閉即可,就不用寫(xiě) finally 關(guān)鍵字了。
finalize 是 Object 對(duì)象中的一個(gè)方法,用于對(duì)象的回收方法,這個(gè)方法我們一般不推薦使用,finalize 是和垃圾回收關(guān)聯(lián)在一起的,在 Java 9 中,將 finalize 標(biāo)記為了deprecated, 如果沒(méi)有特別原因,不要實(shí)現(xiàn) finalize 方法,也不要指望他來(lái)進(jìn)行垃圾回收。
內(nèi)部類(lèi)有哪些分類(lèi),分別解釋一下
在 Java 中,可以將一個(gè)類(lèi)的定義放在另外一個(gè)類(lèi)的定義內(nèi)部,這就是內(nèi)部類(lèi)。內(nèi)部類(lèi)本身就是類(lèi)的一個(gè)屬性,與其他屬性定義方式一致。
內(nèi)部類(lèi)的分類(lèi)一般主要有四種
- 成員內(nèi)部類(lèi)
- 局部?jī)?nèi)部類(lèi)
- 匿名內(nèi)部類(lèi)
- 靜態(tài)內(nèi)部類(lèi)
靜態(tài)內(nèi)部類(lèi)就是定義在類(lèi)內(nèi)部的靜態(tài)類(lèi),靜態(tài)內(nèi)部類(lèi)可以訪問(wèn)外部類(lèi)所有的靜態(tài)變量,而不可訪問(wèn)外部類(lèi)的非靜態(tài)變量;
成員內(nèi)部類(lèi)就是定義在類(lèi)內(nèi)部,成員位置上的非靜態(tài)類(lèi),就是成員內(nèi)部類(lèi)。成員內(nèi)部類(lèi)可以訪問(wèn)外部類(lèi)所有的變量和方法,包括靜態(tài)和非靜態(tài),私有和公有。
定義在方法中的內(nèi)部類(lèi),就是局部?jī)?nèi)部類(lèi)。定義在實(shí)例方法中的局部類(lèi)可以訪問(wèn)外部類(lèi)的所有變量和方法,定義在靜態(tài)方法中的局部類(lèi)只能訪問(wèn)外部類(lèi)的靜態(tài)變量和方法。
匿名內(nèi)部類(lèi)就是沒(méi)有名字的內(nèi)部類(lèi),除了沒(méi)有名字,匿名內(nèi)部類(lèi)還有以下特點(diǎn):
- 匿名內(nèi)部類(lèi)必須繼承一個(gè)抽象類(lèi)或者實(shí)現(xiàn)一個(gè)接口
- 匿名內(nèi)部類(lèi)不能定義任何靜態(tài)成員和靜態(tài)方法。
- 當(dāng)所在的方法的形參需要被匿名內(nèi)部類(lèi)使用時(shí),必須聲明為 final。
- 匿名內(nèi)部類(lèi)不能是抽象的,它必須要實(shí)現(xiàn)繼承的類(lèi)或者實(shí)現(xiàn)的接口的所有抽象方法。
說(shuō)出幾種常用的異常
- NullPointerException: 空指針異常
- NoSuchMethodException:找不到方法
- IllegalArgumentException:不合法的參數(shù)異常
- IndexOutOfBoundException: 數(shù)組下標(biāo)越界異常
- IOException:由于文件未找到、未打開(kāi)或者I/O操作不能進(jìn)行而引起異常
- ClassNotFoundException :找不到文件所拋出的異常
- NumberFormatException:字符的UTF代碼數(shù)據(jù)格式有錯(cuò)引起異常;
- InterruptedException:線程中斷拋出的異常
靜態(tài)綁定和動(dòng)態(tài)綁定的區(qū)別
一個(gè)Java 程序要經(jīng)過(guò)編寫(xiě)、編譯、運(yùn)行三個(gè)步驟,其中編寫(xiě)代碼不在我們討論的范圍之內(nèi),那么我們的重點(diǎn)自然就放在了編譯和運(yùn)行這兩個(gè)階段,由于編譯和運(yùn)行階段過(guò)程相當(dāng)繁瑣,下面就我的理解來(lái)進(jìn)行解釋:
Java 程序從源文件創(chuàng)建到程序運(yùn)行要經(jīng)過(guò)兩大步驟:
1、編譯時(shí)期是由編譯器將源文件編譯成字節(jié)碼的過(guò)程
2、字節(jié)碼文件由Java虛擬機(jī)解釋執(zhí)行
綁定
綁定就是一個(gè)方法的調(diào)用與調(diào)用這個(gè)方法的類(lèi)連接在一起的過(guò)程被稱為綁定。
綁定主要分為兩種:
靜態(tài)綁定 和 動(dòng)態(tài)綁定
綁定的其他叫法
靜態(tài)綁定 ?== 前期綁定 == 編譯時(shí)綁定
動(dòng)態(tài)綁定 ?== 后期綁定 == 運(yùn)行時(shí)綁定
為了方便區(qū)分:下面統(tǒng)一稱呼為靜態(tài)綁定和動(dòng)態(tài)綁定
靜態(tài)綁定
在程序運(yùn)行前,也就是編譯時(shí)期 JVM 就能夠確定方法由誰(shuí)調(diào)用,這種機(jī)制稱為靜態(tài)綁定
識(shí)別靜態(tài)綁定的三個(gè)關(guān)鍵字以及各自的理解
如果一個(gè)方法由 private、static、final 任意一個(gè)關(guān)鍵字所修飾,那么這個(gè)方法是前期綁定的
構(gòu)造方法也是前期綁定
private:private 關(guān)鍵字是私有的意思,如果被 private 修飾的方法是無(wú)法由本類(lèi)之外的其他類(lèi)所調(diào)用的,也就是本類(lèi)所特有的方法,所以也就由編譯器識(shí)別此方法是屬于哪個(gè)類(lèi)的
public?class?Person?{????private?String?talk;
????private?String?canTalk(){
????????return?talk;
????}
}
class?Animal{
????public?static?void?main(String[]?args)?{
????????Person?p?=?new?Person();
????????//?private?修飾的方法是Person類(lèi)獨(dú)有的,所以Animal類(lèi)無(wú)法訪問(wèn)(動(dòng)物本來(lái)就不能說(shuō)話)
//????????p.canTalk();
????}
}
final:final 修飾的方法不能被重寫(xiě),但是可以由子類(lèi)進(jìn)行調(diào)用,如果將方法聲明為 final 可以有效的關(guān)閉動(dòng)態(tài)綁定
public?class?Fruit?{????private?String?fruitName;
????final?String?eatingFruit(String?name){
????????System.out.println("eating?"?+?name);
????????return?fruitName;
????}
}
class?Apple?extends?Fruit{
??????//?不能重寫(xiě)final方法,eatingFruit方法只屬于Fruit類(lèi),Apple類(lèi)無(wú)法調(diào)用
//????String?eatingFruit(String?name){
//????????super.eatingFruit(name);
//????}
????String?eatingApple(String?name){
????????return?super.eatingFruit(name);
????}
}
static:static 修飾的方法比較特殊,不用通過(guò) new 出某個(gè)類(lèi)來(lái)調(diào)用,由類(lèi)名.變量名直接調(diào)用該方法,這個(gè)就很關(guān)鍵了,new 很關(guān)鍵,也可以認(rèn)為是開(kāi)啟多態(tài)的導(dǎo)火索,而由類(lèi)名.變量名直接調(diào)用的話,此時(shí)的類(lèi)名是確定的,并不會(huì)產(chǎn)生多態(tài),如下代碼:
public?class?SuperClass?{????public?static?void?sayHello(){
????????
????????System.out.println("由?superClass?說(shuō)你好");
????}
}
public?class?SubClass?extends?SuperClass{
????public?static?void?sayHello(){
????????System.out.println("由?SubClass?說(shuō)你好");
????}
????public?static?void?main(String[]?args)?{
????????SuperClass.sayHello();
????????SubClass.sayHello();
????}
}
SubClass 繼承 SuperClass 后,在
是無(wú)法重寫(xiě) sayHello 方法的,也就是說(shuō) sayHello() 方法是對(duì)子類(lèi)隱藏的,但是你可以編寫(xiě)自己的 sayHello() 方法,也就是子類(lèi) SubClass 的sayHello() 方法,由此可見(jiàn),方法由 static 關(guān)鍵詞所修飾,也是編譯時(shí)綁定
動(dòng)態(tài)綁定
在運(yùn)行時(shí)根據(jù)具體對(duì)象的類(lèi)型進(jìn)行綁定
除了由 private、final、static 所修飾的方法和構(gòu)造方法外,JVM 在運(yùn)行期間決定方法由哪個(gè)對(duì)象調(diào)用的過(guò)程稱為動(dòng)態(tài)綁定
如果把編譯、運(yùn)行看成一條時(shí)間線的話,在運(yùn)行前必須要進(jìn)行程序的編譯過(guò)程,那么在編譯期進(jìn)行的綁定是前期綁定,在程序運(yùn)行了,發(fā)生的綁定就是后期綁定
public?class?Father?{????void?drinkMilk(){
????????System.out.println("父親喜歡喝牛奶");
????}
}
public?class?Son?extends?Father{
????@Override
????void?drinkMilk()?{
????????System.out.println("兒子喜歡喝牛奶");
????}
????public?static?void?main(String[]?args)?{
????????Father?son?=?new?Son();
????????son.drinkMilk();
????}
}
Son 類(lèi)繼承 Father 類(lèi),并重寫(xiě)了父類(lèi)的 dringMilk() 方法,在輸出結(jié)果得出的是兒子喜歡喝牛奶。那么上面的綁定方式是什么呢?
上面的綁定方式稱之為動(dòng)態(tài)綁定,因?yàn)樵谀憔帉?xiě) Father son = new Son() 的時(shí)候,編譯器并不知道 son 對(duì)象真正引用的是誰(shuí),在程序運(yùn)行時(shí)期才知道,這個(gè) son 是一個(gè) Father 類(lèi)的對(duì)象,但是卻指向了 Son 的引用,這種概念稱之為多態(tài),那么我們就能夠整理出來(lái)多態(tài)的三個(gè)原則:
繼承
重寫(xiě)
父類(lèi)對(duì)象指向子類(lèi)引用
也就是說(shuō),在 Father son = new Son() ,觸發(fā)了動(dòng)態(tài)綁定機(jī)制。
動(dòng)態(tài)綁定的過(guò)程
動(dòng)態(tài)綁定和靜態(tài)綁定的特點(diǎn)
靜態(tài)綁定
靜態(tài)綁定在編譯時(shí)期觸發(fā),那么它的主要特點(diǎn)是
1、編譯期觸發(fā),能夠提早知道代碼錯(cuò)誤
2、提高程序運(yùn)行效率
動(dòng)態(tài)綁定
1、使用動(dòng)態(tài)綁定的前提條件能夠提高代碼的可用性,使代碼更加靈活。
2、多態(tài)是設(shè)計(jì)模式的基礎(chǔ),能夠降低耦合性。
- END -點(diǎn)擊圖片參與活動(dòng),免費(fèi)包郵送書(shū)??????●??人人都欠微軟一個(gè)正版?
●??腳本之家粉絲福利,請(qǐng)查看!
●??2020年開(kāi)發(fā)者生態(tài)報(bào)告:Python超越Java
●?Java 中的 String 有沒(méi)有長(zhǎng)度限制?
●?小白,你要的Java抽象類(lèi),操碎了心!
C、Java:我看著你們,滿懷敬意
總結(jié)
以上是生活随笔為你收集整理的java 多态判断非空_收藏Java 面试题全梳理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 序列化与反序列化的简单认识
- 下一篇: beetl的使用感受