Java-Basic
談?wù)刦inal、 finally、 finalize有什么不同?
典型回答:
final可以用來(lái)修飾類、方法、變量,分別有不同的意義, final修飾的class代表不可以繼承擴(kuò)展, final的變量是不可以修改的,而final的方法也是不可以重寫的( override)。
finally則是Java保證重點(diǎn)代碼一定要被執(zhí)行的一種機(jī)制。我們可以使用try-finally或者try-catch-finally來(lái)進(jìn)行類似關(guān)閉JDBC連接、保證unlock鎖等動(dòng)作。
finalize是基礎(chǔ)類java.lang.Object的一個(gè)方法,它的設(shè)計(jì)目的是保證對(duì)象在被垃圾收集前完成特定資源的回收。 finalize機(jī)制現(xiàn)在已經(jīng)不推薦使用,并且在JDK 9開始被標(biāo)記
為deprecated。
強(qiáng)引用、軟引用、弱引用、幻象引用有什么區(qū)別?具體使用場(chǎng)景是什么?
不同的引用類型,主要體現(xiàn)的是對(duì)象不同的可達(dá)性( reachable)狀態(tài)和對(duì)垃圾收集的影響。
所謂強(qiáng)引用( "Strong" Reference),就是我們最常見的普通對(duì)象引用,只要還有強(qiáng)引用指向一個(gè)對(duì)象,就能表明對(duì)象還“活著”,垃圾收集器不會(huì)碰這種對(duì)象。對(duì)于一個(gè)普通的對(duì)
象,如果沒有其他的引用關(guān)系,只要超過了引用的作用域或者顯式地將相應(yīng)(強(qiáng))引用賦值為null,就是可以被垃圾收集的了,當(dāng)然具體回收時(shí)機(jī)還是要看垃圾收集策略。
軟引用( SoftReference),是一種相對(duì)強(qiáng)引用弱化一些的引用,可以讓對(duì)象豁免一些垃圾收集,只有當(dāng)JVM認(rèn)為內(nèi)存不足時(shí),才會(huì)去試圖回收軟引用指向的對(duì)象。 JVM會(huì)確保在拋
出OutOfMemoryError之前,清理軟引用指向的對(duì)象。軟引用通常用來(lái)實(shí)現(xiàn)內(nèi)存敏感的緩存,如果還有空閑內(nèi)存,就可以暫時(shí)保留緩存,當(dāng)內(nèi)存不足時(shí)清理掉,這樣就保證了使用緩
存的同時(shí),不會(huì)耗盡內(nèi)存。
andriod中的圖片緩存是軟引用的例子.
弱引用( WeakReference)并不能使對(duì)象豁免垃圾收集,僅僅是提供一種訪問在弱引用狀態(tài)下對(duì)象的途徑。這就可以用來(lái)構(gòu)建一種沒有特定約束的關(guān)系,比如,維護(hù)一種非強(qiáng)制性
的映射關(guān)系,如果試圖獲取時(shí)對(duì)象還在,就使用它,否則重現(xiàn)實(shí)例化。它同樣是很多緩存實(shí)現(xiàn)的選擇。
ThreadLocal中entry的Key是弱引用的例子.
對(duì)于幻象引用,有時(shí)候也翻譯成虛引用,你不能通過它訪問對(duì)象。幻象引用僅僅是提供了一種確保對(duì)象被fnalize以后,做某些事情的機(jī)制,比如,通常用來(lái)做所謂的PostMortem清理機(jī)制,我在專欄上一講中介紹的Java平臺(tái)自身Cleaner機(jī)制等,也有人利用幻象引用監(jiān)控對(duì)象的創(chuàng)建和銷毀。
理解Java的字符串, String、 StringBufer、 StringBuilder有什么區(qū)別?
String是Java語(yǔ)言非?;A(chǔ)和重要的類,提供了構(gòu)造和管理字符串的各種基本邏輯。它是典型的Immutable類,被聲明成為fnal class,所有屬性也都是fnal的。也由于它的不可
變性,類似拼接、裁剪字符串等動(dòng)作,都會(huì)產(chǎn)生新的String對(duì)象。由于字符串操作的普遍性,所以相關(guān)操作的效率往往對(duì)應(yīng)用性能有明顯影響。
StringBufer是為解決上面提到拼接產(chǎn)生太多中間對(duì)象的問題而提供的一個(gè)類,我們可以用append或者add方法,把字符串添加到已有序列的末尾或者指定位置。 StringBufer本
質(zhì)是一個(gè)線程安全的可修改字符序列,它保證了線程安全,也隨之帶來(lái)了額外的性能開銷,所以除非有線程安全的需要,不然還是推薦使用它的后繼者,也就是StringBuilder。
StringBuilder是Java 1.5中新增的,在能力上和StringBufer沒有本質(zhì)區(qū)別,但是它去掉了線程安全的部分,有效減小了開銷,是絕大部分情況下進(jìn)行字符串拼接的首選。
String是Immutable類的典型實(shí)現(xiàn),原生的保證了基礎(chǔ)線程安全,因?yàn)槟銦o(wú)法對(duì)它內(nèi)部數(shù)據(jù)進(jìn)行任何修改,這種便利甚至體現(xiàn)在拷貝構(gòu)造函數(shù)中,由于不可
變, Immutable對(duì)象在拷貝時(shí)不需要額外復(fù)制數(shù)據(jù)。
為了實(shí)現(xiàn)修改字符序列的目的, StringBufer和StringBuilder底層都是利用可修改的( char, JDK 9以后是byte)數(shù)組,二者都繼承了AbstractStringBuilder,里面包含了基本
操作,區(qū)別僅在于最終的方法是否加了synchronized。
談?wù)凧ava反射機(jī)制,動(dòng)態(tài)代理是基于什么原理?
典型回答:
反射機(jī)制是Java語(yǔ)言提供的一種基礎(chǔ)功能,賦予程序在運(yùn)行時(shí)自省( introspect,官方用語(yǔ))的能力。通過反射我們可以直接操作類或者對(duì)象,比如獲取某個(gè)對(duì)象的類定義,獲取類
聲明的屬性和方法,調(diào)用方法或者構(gòu)造對(duì)象,甚至可以運(yùn)行時(shí)修改類定義。
動(dòng)態(tài)代理是一種方便運(yùn)行時(shí)動(dòng)態(tài)構(gòu)建代理、動(dòng)態(tài)處理代理方法調(diào)用的機(jī)制,很多場(chǎng)景都是利用類似機(jī)制做到的,比如用來(lái)包裝RPC調(diào)用、面向切面的編程( AOP)。
實(shí)現(xiàn)動(dòng)態(tài)代理的方式很多,比如JDK自身提供的動(dòng)態(tài)代理,就是主要利用了上面提到的反射機(jī)制。還有其他的實(shí)現(xiàn)方式,比如利用傳說中更高性能的字節(jié)碼操作機(jī)制,類
似ASM、 cglib(基于ASM)、 Javassist等。
我們知道Spring AOP支持兩種模式的動(dòng)態(tài)代理, JDK Proxy或者cglib,如果我們選擇cglib方式,你會(huì)發(fā)現(xiàn)對(duì)接口的依賴被克服了。
cglib動(dòng)態(tài)代理采取的是創(chuàng)建目標(biāo)類的子類的方式,因?yàn)槭亲宇惢?,我們可以達(dá)到近似使用被調(diào)用者本身的效果。
int和Integer有什么區(qū)別?談?wù)処nteger的值緩存范圍。
典型回答:
int是我們常說的整形數(shù)字,是Java的8個(gè)原始數(shù)據(jù)類型( Primitive Types, boolean、 byte 、 short、 char、 int、 foat、 double、 long)之一。 Java語(yǔ)言雖然號(hào)稱一切都是對(duì)象,
但原始數(shù)據(jù)類型是例外。
Integer是int對(duì)應(yīng)的包裝類,它有一個(gè)int類型的字段存儲(chǔ)數(shù)據(jù),并且提供了基本操作,比如數(shù)學(xué)運(yùn)算、 int和字符串之間轉(zhuǎn)換等。在Java 5中,引入了自動(dòng)裝箱和自動(dòng)拆箱功能
( boxing/unboxing), Java可以根據(jù)上下文,自動(dòng)進(jìn)行轉(zhuǎn)換,極大地簡(jiǎn)化了相關(guān)編程。
關(guān)于Integer的值緩存,這涉及Java 5中另一個(gè)改進(jìn)。構(gòu)建Integer對(duì)象的傳統(tǒng)方式是直接調(diào)用構(gòu)造器,直接new一個(gè)對(duì)象。但是根據(jù)實(shí)踐,我們發(fā)現(xiàn)大部分?jǐn)?shù)據(jù)操作都是集中在有
限的、較小的數(shù)值范圍,因而,在Java 5中新增了靜態(tài)工廠方法valueOf,在調(diào)用它的時(shí)候會(huì)利用一個(gè)緩存機(jī)制,帶來(lái)了明顯的性能改進(jìn)。按照J(rèn)avadoc, 這個(gè)值默認(rèn)緩存
是-128到127之間。
這種緩存機(jī)制并不是只有Integer才有,同樣存在于其他的一些包裝類,比如:
- Boolean,緩存了true/false對(duì)應(yīng)實(shí)例,確切說,只會(huì)返回兩個(gè)常量實(shí)例Boolean.TRUE/FALSE。
- Short,同樣是緩存了-128到127之間的數(shù)值。
- Byte,數(shù)值有限,所以全部都被緩存。
- Character,緩存范圍'\u0000' 到 '\u007F'。
注意事項(xiàng):
[1] 基本類型均具有取值范圍,在大數(shù)*大數(shù)的時(shí)候,有可能會(huì)出現(xiàn)越界的情況。
[2] 基本類型轉(zhuǎn)換時(shí),使用聲明的方式。例: int result= 1234567890 * 24 * 365;結(jié)果值一定不會(huì)是你所期望的那個(gè)值,因?yàn)?234567890 * 24已經(jīng)超過了int的范圍,如果修改為: long result= 1234567890L * 24 * 365;就正常了。
[3] 慎用基本類型處理貨幣存儲(chǔ)。如采用double常會(huì)帶來(lái)差距,常采用BigDecimal、整型(如果要精確表示分,可將值擴(kuò)大100倍轉(zhuǎn)化為整型)解決該問題。
[4] 優(yōu)先使用基本類型。原則上,建議避免無(wú)意中的裝箱、拆箱行為,尤其是在性能敏感的場(chǎng)合,
[5] 如果有線程安全的計(jì)算需要,建議考慮使用類型AtomicInteger、 AtomicLong 這樣的線程安全類。部分比較寬的基本數(shù)據(jù)類型,比如 foat、 double,甚至不能保證更新操作的原子性,
可能出現(xiàn)程序讀取到只更新了一半數(shù)據(jù)位的數(shù)值。
[4].原則上, 建議避免無(wú)意中的裝箱、拆箱行為,尤其是在性能敏感的場(chǎng)合,創(chuàng)建10萬(wàn)個(gè)Java對(duì)象和10萬(wàn)個(gè)整數(shù)的開銷可不是一個(gè)數(shù)量級(jí)的,不管是內(nèi)存使用還是處理速度,光是對(duì)象頭
的空間占用就已經(jīng)是數(shù)量級(jí)的差距了。
以我們經(jīng)常會(huì)使用到的計(jì)數(shù)器實(shí)現(xiàn)為例,下面是一個(gè)常見的線程安全計(jì)數(shù)器實(shí)現(xiàn)。
class Counter {
private fnal AtomicLong counter = new AtomicLong();
public void increase() {
counter.incrementAndGet();
}
}
如果利用原始數(shù)據(jù)類型,可以將其修改為
class CompactCounter {
private volatile long counter;
private satic fnal AtomicLongFieldUpdater<CompactCounter> updater = AtomicLongFieldUpdater.newUpdater(CompactCounter.class, "counter");
public void increase() {
updater.incrementAndGet(this);
}
}
Java原始數(shù)據(jù)類型和引用類型局限性:
前面我談了非常多的技術(shù)細(xì)節(jié),最后再?gòu)腏ava平臺(tái)發(fā)展的角度來(lái)看看,原始數(shù)據(jù)類型、對(duì)象的局限性和演進(jìn)。
對(duì)于Java應(yīng)用開發(fā)者,設(shè)計(jì)復(fù)雜而靈活的類型系統(tǒng)似乎已經(jīng)習(xí)以為常了。但是坦白說,畢竟這種類型系統(tǒng)的設(shè)計(jì)是源于很多年前的技術(shù)決定,現(xiàn)在已經(jīng)逐漸暴露出了一些副作用,例
如:
原始數(shù)據(jù)類型和Java泛型并不能配合使用
這是因?yàn)镴ava的泛型某種程度上可以算作偽泛型,它完全是一種編譯期的技巧, Java編譯期會(huì)自動(dòng)將類型轉(zhuǎn)換為對(duì)應(yīng)的特定類型,這就決定了使用泛型,必須保證相應(yīng)類型可以轉(zhuǎn)換
為Object。無(wú)法高效地表達(dá)數(shù)據(jù),也不便于表達(dá)復(fù)雜的數(shù)據(jù)結(jié)構(gòu),比如vector和tuple我們知道Java的對(duì)象都是引用類型,如果是一個(gè)原始數(shù)據(jù)類型數(shù)組,它在內(nèi)存里是一段連續(xù)的內(nèi)存,而對(duì)象數(shù)組則不然,數(shù)據(jù)存儲(chǔ)的是引用,對(duì)象往往是分散地存儲(chǔ)在堆的不同位
置。這種設(shè)計(jì)雖然帶來(lái)了極大靈活性,但是也導(dǎo)致了數(shù)據(jù)操作的低效,尤其是無(wú)法充分利用現(xiàn)代CPU緩存機(jī)制。
Vector、 ArrayList、 LinkedList有何區(qū)別?
典型回答:
Vector是Java早期提供的線程安全的動(dòng)態(tài)數(shù)組,如果不需要線程安全,并不建議選擇,畢竟同步是有額外開銷的。 Vector內(nèi)部是使用對(duì)象數(shù)組來(lái)保存數(shù)據(jù),可以根據(jù)需要自動(dòng)的增加
容量,當(dāng)數(shù)組已滿時(shí),會(huì)創(chuàng)建新的數(shù)組,并拷貝原有數(shù)組數(shù)據(jù)。
ArrayList是應(yīng)用更加廣泛的動(dòng)態(tài)數(shù)組實(shí)現(xiàn),它本身不是線程安全的,所以性能要好很多。與Vector近似, ArrayList也是可以根據(jù)需要調(diào)整容量,不過兩者的調(diào)整邏輯有所區(qū)
別, Vector在擴(kuò)容時(shí)會(huì)提高1倍,而ArrayList則是增加50%。
LinkedList顧名思義是Java提供的雙向鏈表,所以它不需要像上面兩種那樣調(diào)整容量,它也不是線程安全的。

我們可以看到Java的集合框架, Collection接口是所有集合的根,然后擴(kuò)展開提供了三大類集合,分別是:
- List,也就是我們前面介紹最多的有序集合,它提供了方便的訪問、插入、刪除等操作。
- Set, Set是不允許重復(fù)元素的,這是和List最明顯的區(qū)別,也就是不存在兩個(gè)對(duì)象equals返回true。我們?cè)谌粘i_發(fā)中有很多需要保證元素唯一性的場(chǎng)合。
- Queue/Deque,則是Java提供的標(biāo)準(zhǔn)隊(duì)列結(jié)構(gòu)的實(shí)現(xiàn),除了集合的基本功能,它還支持類似先入先出( FIFO, First-in-First-Out)或者后入先出( LIFO, Last-In-FirstOut)等特定行為。這里不包括BlockingQueue,因?yàn)橥ǔJ遣l(fā)編程場(chǎng)合,所以被放置在并發(fā)包里。
今天介紹的這些集合類,都不是線程安全的,對(duì)于java.util.concurrent里面的線程安全容器,我在專欄后面會(huì)去介紹。但是,并不代表這些集合完全不能支持并發(fā)編程的場(chǎng)景,
在Collections工具類中,提供了一系列的synchronized方法,比如
static <T> List<T> synchronizedList(List<T> list)
我們完全可以利用類似方法來(lái)實(shí)現(xiàn)基本的線程安全集合:
List list = Collections.synchronizedList(new ArrayList());
它的實(shí)現(xiàn),基本就是將每個(gè)基本方法,比如get、 set、 add之類,都通過synchronizd添加基本的同步支持,非常簡(jiǎn)單粗暴,但也非常實(shí)用。注意這些方法創(chuàng)建的線程安全集合,都
符合迭代時(shí)fail-fast行為,當(dāng)發(fā)生意外的并發(fā)修改時(shí),盡早拋出ConcurrentModifcationException異常,以避免不可預(yù)計(jì)的行為。
另外一個(gè)經(jīng)常會(huì)被考察到的問題,就是理解Java提供的默認(rèn)排序算法,具體是什么排序方式以及設(shè)計(jì)思路等。
這個(gè)問題本身就是有點(diǎn)陷阱的意味,因?yàn)樾枰獏^(qū)分是Arrays.sort()還是Collections.sort() (底層是調(diào)用Arrays.sort());什么數(shù)據(jù)類型;多大的數(shù)據(jù)集(太小的數(shù)據(jù)集,復(fù)雜排
序是沒必要的, Java會(huì)直接進(jìn)行二分插入排序)等。
對(duì)于原始數(shù)據(jù)類型,目前使用的是所謂雙軸快速排序( Dual-Pivot QuickSort),是一種改進(jìn)的快速排序算法,早期版本是相對(duì)傳統(tǒng)的快速排序,你可以閱讀源碼。
而對(duì)于對(duì)象數(shù)據(jù)類型,目前則是使用TimSort,思想上也是一種歸并和二分插入排序( binarySort)結(jié)合的優(yōu)化排序算法。 TimSort并不是Java的獨(dú)創(chuàng),簡(jiǎn)單說它的思路是查找
數(shù)據(jù)集中已經(jīng)排好序的分區(qū)(這里叫run),然后合并這些分區(qū)來(lái)達(dá)到排序的目的。
另外, Java 8引入了并行排序算法(直接使用parallelSort方法),這是為了充分利用現(xiàn)代多核處理器的計(jì)算能力,底層實(shí)現(xiàn)基于fork-join框架,當(dāng)處理的數(shù)據(jù)集比較小的時(shí)候,差距不明顯,甚至還表現(xiàn)差一點(diǎn);但是,當(dāng)數(shù)據(jù)集增長(zhǎng)到數(shù)萬(wàn)或百萬(wàn)以上時(shí),提高就非常大了,具體還是取決于處理器和系統(tǒng)環(huán)境。