java面試題

android程序員在面試時(shí)都會(huì)被問(wèn)到Java方面的知識(shí),本文整理了部分Java方面的面試題,如下:

0、Java垃圾回收和System.gc的關(guān)系

Java根據(jù)垃圾收集算法,周期性的進(jìn)行垃圾回收,回收哪些無(wú)用的對(duì)象。以下情況會(huì)觸發(fā)GC:

應(yīng)用程序空閑時(shí),即沒(méi)有程序在運(yùn)行,會(huì)GC。因?yàn)镚C線程的優(yōu)先級(jí)較低,CPU較忙時(shí)一般不會(huì)執(zhí)行,以下場(chǎng)景除外:堆內(nèi)存不足。

Java堆內(nèi)存不足時(shí)會(huì)GC,如果一次GC后內(nèi)存還不夠,會(huì)觸發(fā)第二次GC,第二次還不夠就會(huì)觸發(fā)OOM。

System.gc只是呼叫JVM進(jìn)行垃圾回收,但這只是建議而已,不一定立刻執(zhí)行。

1、Java元注解有哪些

元注解指的是對(duì)注解進(jìn)行注解的注解,有以下四種:

@Target :表示該注解用在什么地方,可能值在枚舉類(lèi)ElementType中,包括 ? ElemrntType.CONSTRUCTOR----------構(gòu)造器

ElemrntType.FIELD----------域聲明

ElemrntType.METHOD--------------方法

@Retention:在什么地方保存該注解信息,包括:

RetentionPolicy.SOURCE----------------注解在編譯時(shí)就被丟棄

RetentionPolicy.CLASS----------------注解在編譯時(shí)保留但在VM中丟棄

RetentionPolicy.RUNTIME----------------注解在運(yùn)行時(shí)一直存在,即可以通過(guò)反射進(jìn)行調(diào)研

@Documented:將此注解包含在javadoc 中,即該注解可以被javadoc生成文檔

@Inherited: 允許子類(lèi)繼承父類(lèi)中的注解

2、Synchronized詳解

Java用Synchronized來(lái)實(shí)現(xiàn)線程同步,該關(guān)鍵字可以加在方法上,也可以加在對(duì)象上,如果他的作用的對(duì)象是非靜態(tài)的,則它取得的鎖是對(duì)象;如果作用的對(duì)象是靜態(tài)方法或者類(lèi),則它取得的鎖是類(lèi)對(duì)象(Class對(duì)象)。每個(gè)對(duì)象有一把鎖,誰(shuí)取得這個(gè)鎖,誰(shuí)就可以訪問(wèn)對(duì)象的方法。

可重入性:假設(shè)類(lèi)A有兩個(gè)同步方法a()、b(),

可重入性

線程T在訪問(wèn)A的方法a()時(shí)會(huì)先獲取A的鎖,如果這時(shí)候a()方法調(diào)用了b()方法,由于b()方法也是同步的,所以也需要獲取A的鎖,由于在調(diào)用a()方法時(shí)已經(jīng)獲取到A的對(duì)象鎖了,調(diào)用b()時(shí)就可以直接進(jìn)入方法不會(huì)導(dǎo)致死鎖,這就是可重入性。

不可繼承性:如果類(lèi)A的b()方法是同步的,如果子類(lèi)B的b()方法不是同步的,那么子類(lèi)B的b()方法就不具備同步特性。

對(duì)象鎖和類(lèi)鎖:

Java的所有對(duì)象都含有一個(gè)鎖,這個(gè)鎖由JVM自動(dòng)獲取和釋放,線程進(jìn)入Synchronized代碼塊會(huì)去獲取該對(duì)象的鎖,如果已經(jīng)有線程獲取了對(duì)象鎖,那個(gè)當(dāng)前線程就會(huì)等待。對(duì)象鎖是控制實(shí)例方法的鎖,類(lèi)鎖是用來(lái)控制靜態(tài)代碼的同步的,相當(dāng)于Class對(duì)象的鎖。類(lèi)鎖和對(duì)象鎖不同,一個(gè)是類(lèi)的Class對(duì)象的鎖,一個(gè)是類(lèi)的實(shí)例對(duì)象的鎖,也就是說(shuō)一個(gè)線程訪問(wèn)靜態(tài)的Synchronized時(shí),另外的線程可以訪問(wèn)對(duì)象實(shí)例的Synchronized方法。

3、Java引用類(lèi)型

強(qiáng)引用----------- 垃圾收集器不會(huì)收集它,寧可拋出OOM

軟引用(Soft Reference)-----------當(dāng)垃圾收集器工作時(shí),如果內(nèi)存足夠,就不回收只被軟引用關(guān)聯(lián)的對(duì)象,如果內(nèi)存不夠,則會(huì)進(jìn)行回收。一旦被回收,這個(gè)軟引用就會(huì)被加入到關(guān)聯(lián)的ReferenceQueue中。

弱引用(Weak Reference) -------------- 對(duì)象只能生存到下一次GC,當(dāng)垃圾收集器工作時(shí)無(wú)論內(nèi)存是否足夠都會(huì)收集只被弱引用關(guān)聯(lián)的對(duì)象。一旦一個(gè)弱引用被回收,便會(huì)加入一個(gè)注冊(cè)引用隊(duì)列ReferenceQueue中。

虛引用(Phantom Reference) ------------ 最弱的引用類(lèi)型,get方法返回null。主要用來(lái)跟蹤對(duì)象被垃圾回收的狀態(tài)。虛引用被回收時(shí)會(huì)放入ReferenceQueue,從而達(dá)到跟蹤對(duì)象垃圾回收的作用。

ReferenceQueue-----------是這樣的一個(gè)對(duì)象,當(dāng)一個(gè)obj被gc掉之后,其相應(yīng)的包裝類(lèi),即ref對(duì)象會(huì)被放入queue中。我們可以從queue中獲取到相應(yīng)的對(duì)象信息,同時(shí)進(jìn)行額外的處理。比如反向操作,數(shù)據(jù)清理等。

WeakReference weakReference=new WeakReference(obj,ReferenceQueue)將引用隊(duì)列傳入即可進(jìn)行觀察。

4、垃圾收集算法

可達(dá)性分析算法,通過(guò)一系列作為GCRoot的對(duì)象,虛擬機(jī)從GCRoot沿著對(duì)象傳遞的引用鏈來(lái)尋找,如果找不到,就認(rèn)為對(duì)象不可達(dá),即該對(duì)象可以被回收。

在垃圾收集器對(duì)對(duì)象回收之前首先對(duì)GCroot不可達(dá)的對(duì)象進(jìn)行第一次標(biāo)記,然后進(jìn)行篩選(判斷該對(duì)象是否有必要執(zhí)行finalize()方法,finalize()方法只能被調(diào)用一次。如果有必要執(zhí)行時(shí),就會(huì)放在一個(gè)處理隊(duì)列里等待虛擬機(jī)處理,虛擬機(jī)會(huì)觸發(fā)該對(duì)象的finalize(),但是不承諾會(huì)等待它結(jié)束,防止他進(jìn)入死循環(huán)而阻塞后邊的進(jìn)程,然后在finalize()執(zhí)行完之后,在對(duì)那些仍舊沒(méi)有被引用的對(duì)象進(jìn)行第二次標(biāo)記,然后開(kāi)始進(jìn)行回收,換而言之,該對(duì)象可以在自己的finalize()方法里讓別人引用自己,這樣自己就不會(huì)被回收了,從而拯救自己。

判斷一個(gè)類(lèi)是否可以被回收有以下三個(gè)條件:該類(lèi)的所有實(shí)例已經(jīng)被回收,該類(lèi)的ClassLoader已經(jīng)被回收,該類(lèi)的Class對(duì)象沒(méi)有引用。

垃圾收集算法一般分為:

標(biāo)記-清除算法-----會(huì)產(chǎn)生不聯(lián)系的內(nèi)存碎片,導(dǎo)致以后想分配大內(nèi)存時(shí),明明剩余的總內(nèi)存足夠,確不能分配。

復(fù)制算法----將空間分為兩個(gè)部分AB,當(dāng)A快用完時(shí)將A中存活的對(duì)象復(fù)制到B,然后回收A。

標(biāo)記-整理算法----類(lèi)似于標(biāo)記清除算法,只是在其基礎(chǔ)上將回收完還存活的對(duì)象往一側(cè)移動(dòng),這樣剩余的空間就是連續(xù)的了。

對(duì)應(yīng)Java堆來(lái)說(shuō),使用分代收集,新生代使用復(fù)制算法,老年代使用標(biāo)記清除或者標(biāo)記整理。

可以作為gcroot的對(duì)象如下:虛擬機(jī)棧中本地變量引用的對(duì)象、方法區(qū)中靜態(tài)屬性引用的對(duì)象、方法區(qū)中常量引用的對(duì)象、本地方法棧jni引用的對(duì)象。

5、Java線程池?

Java線程池使用ThreadPoolExecutor類(lèi)實(shí)現(xiàn),構(gòu)造函數(shù)如下:

ThreadPoolExecutor構(gòu)造函數(shù)

corePoolSize:線程池中一直存活的線程最小數(shù)量,也叫核心線程。

maximumPoolSize:線程池能夠容納的最大線程數(shù),當(dāng)提交一個(gè)任務(wù)到線程池,如果處于RUNNING的線程數(shù)量少于corePoolSize,那么即使一些非核心線程處于空閑等待狀態(tài),系統(tǒng)也會(huì)創(chuàng)建一個(gè)新的線程來(lái)處理這個(gè)任務(wù)。如果處于運(yùn)行狀態(tài)的線程數(shù)大于corePoolSize,但又小于maximumPoolSize,系統(tǒng)就會(huì)判斷線程池內(nèi)部阻塞隊(duì)列workQueue是否有空位置,如果有就先存入阻塞隊(duì)列,如果沒(méi)有就創(chuàng)建一個(gè)新線程來(lái)執(zhí)行這個(gè)任務(wù)。如果將corePoolSize和maximumPoolSize設(shè)置為相同,那個(gè)該線程池就是一個(gè)容量固定的線程池。

keepAliveTime:空閑線程處于等待的超時(shí)時(shí)間,當(dāng)總線程數(shù)大于corePoolSize且allowCoreThreadTimeOut為false時(shí),多出來(lái)的空閑等待的線程就會(huì)開(kāi)始計(jì)算各自的等待時(shí)間,等待時(shí)間超過(guò)keepAliveTime該線程就會(huì)停止工作。如果keepAliveTime為true,那么不論核心線程還是非核心線程都會(huì)受到keepAliveTime的制約。

workQueue:是一個(gè)阻塞隊(duì)列,用于保存等待任務(wù)。當(dāng)提交一個(gè)任務(wù)到線程池后,線程池會(huì)根據(jù)當(dāng)前運(yùn)行的線程數(shù)量做出相應(yīng)處理,方式如下:

?????1、如果運(yùn)行的線程數(shù)少于corePoolSize,則直接創(chuàng)建一個(gè)新的core線程來(lái)執(zhí)行任務(wù)

?????2、如果運(yùn)行的線程數(shù)大于等于corePoolSize,那么線程池會(huì)優(yōu)先將該任務(wù)提交到workQueue隊(duì)列中

?????3、基于第2點(diǎn)如果任務(wù)加入到workQueue(隊(duì)列滿了)失敗了,則查看當(dāng)前運(yùn)行的線程數(shù)和maximumPoolSize的大小關(guān)系,如果當(dāng)前運(yùn)行線程數(shù)小于maximumPoolSize,則創(chuàng)建一個(gè)新線程來(lái)執(zhí)行任務(wù);如果大于或等于maximumPoolSize,說(shuō)明線程池滿了,就直接拒絕該任務(wù)。隊(duì)列提交新任務(wù)的三種常見(jiàn)切換策略

?????a、直接切換:常用SynchronousQueue同步隊(duì)列,隊(duì)列內(nèi)部不會(huì)存儲(chǔ)元素,每次插入都會(huì)進(jìn)入阻塞狀態(tài),知道別的線程執(zhí)行了刪除操作;反之一樣。 “直接切換”的意思就是, 處理方式由“將任務(wù)暫時(shí)存入隊(duì)列”直接切換為“新建一個(gè)線程來(lái)處理該任務(wù)”。這種策略適合用來(lái)處理多個(gè)有相互依賴(lài)關(guān)系的任務(wù),因?yàn)樵摬呗钥梢员苊膺@些任務(wù)因一個(gè)沒(méi)有及時(shí)處理而導(dǎo)致依賴(lài)于該任務(wù)的其他任務(wù)也不能及時(shí)處理而造成的鎖定效果。因?yàn)檫@種策略的目的是要讓幾乎每一個(gè)新提交的任務(wù)都能得到立即處理, 所以這種策略通常要求最大線程數(shù) maximumPoolSizes是無(wú)界的(即:Integer.MAX_VALUE)。Executors.newCachedThreadPool() 使用了這個(gè)隊(duì)列。

??????b、無(wú)界隊(duì)列:使用 Integer.MAX_VALUE作為默認(rèn)容量,例如LinkedBlockingQueue,這種情況下maximumPoolSize的設(shè)置其實(shí)沒(méi)有效果,線程池最大的線程數(shù)就是corePoolSize,超過(guò)之后新任務(wù)總能存入隊(duì)列中。Executors.newFixedThreadPool() 使用了這個(gè)隊(duì)列

?????c、有界隊(duì)列:限制隊(duì)列的長(zhǎng)度。例如ArrayBlockingQueue

threadFactory:線程工廠,用于創(chuàng)建線程,可以給線程設(shè)置特定的屬性,例如優(yōu)先級(jí)、名字等。

handler:提交線程到線程池失敗后的處理策略。當(dāng)線程池處于下面任意狀態(tài)時(shí)就會(huì)拒絕服務(wù):

?????1、線程池處于SHUNDOWN狀態(tài),不論線程池和阻塞隊(duì)列是否滿了,都會(huì)拒絕服務(wù)。

?????2、當(dāng)線程池的所有線程都處于運(yùn)行狀態(tài)(corePoolSize和maximumPoolSize都滿了)并且線程池中的阻塞隊(duì)列已經(jīng)滿了

?????線程池默認(rèn)給我們提供了以下處理策略

?????1、 AbortPolicy:直接拋出RejectedExecutionException 異常,默認(rèn)就是這種實(shí)現(xiàn)方式。

?????2、CallerRunsPolicy

?????3、DiscardPolicy:直接不執(zhí)行新提交的任務(wù)。

????? 4、DiscardOldestPolicy:當(dāng)線程池已經(jīng)關(guān)閉,就不執(zhí)行這個(gè)任務(wù)了;當(dāng)線程池沒(méi)有關(guān)閉,會(huì)將阻塞中的隊(duì)首元素一次,然后新提交這個(gè)任務(wù)到隊(duì)尾。

6、HashMap的結(jié)構(gòu)

采用Key-Value形式存儲(chǔ),如果Node數(shù)量小于8,Node的結(jié)構(gòu)就是單鏈表,如果Node的數(shù)量大于8,Node的結(jié)構(gòu)就是TreeNode,紅黑樹(shù)。

7、String、StringBuffer、StringBuilder的區(qū)別

String是字符串常量。Sting的+操作,其實(shí)在編譯時(shí)轉(zhuǎn)化成了StringBuilder操作。

StringBuffer是字符串變量,是線程安全的,內(nèi)部通過(guò)Synchronized實(shí)現(xiàn)線程同步。

StringBuilder是字符串變量,是非線程安全的。

8、數(shù)組和鏈表的區(qū)別

數(shù)組將數(shù)據(jù)在內(nèi)存中連續(xù)存放,每個(gè)元素占用的內(nèi)存相同,可以通過(guò)下標(biāo)迅速訪問(wèn)里面的元素。數(shù)組的插入和刪除需要移動(dòng)大量的元素。如果應(yīng)用需要快速訪問(wèn)元素且很少做插入和刪除操作,就應(yīng)該使用數(shù)組。

鏈表恰巧相反,鏈表中的元素存儲(chǔ)不是有序的,而是通過(guò)指針聯(lián)系在一起,例如上個(gè)元素有個(gè)指針指向下個(gè)元素,以此類(lèi)推,直到最后一個(gè)元素。如果要訪問(wèn)鏈表,需要從頭開(kāi)始根據(jù)指針進(jìn)行查找,但是要想刪除元素,不需要移動(dòng)大量元素,只需要修改指針即可。如果應(yīng)用經(jīng)常做插入和刪除操作,不常訪問(wèn)元素,就使用鏈表。

9、Volatile關(guān)鍵字

http://www.importnew.com/24082.html

Java內(nèi)存模型

Java內(nèi)存模型規(guī)定了所以變量必須存在主內(nèi)存中。每條線程有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了被該線程所使用到的變量(這些變量是從主存拷貝而來(lái)的)。線程對(duì)變量的讀寫(xiě)都在工作內(nèi)存中,不同線程之間無(wú)法之間訪問(wèn)對(duì)方的工作內(nèi)存中的變量,線程將變量的傳遞通過(guò)主內(nèi)存來(lái)完成?;谶@種內(nèi)存模型,便產(chǎn)生了多線程編程中的數(shù)據(jù)“臟讀”問(wèn)題。

例如在執(zhí)行i=10;i=i+1的操作時(shí),執(zhí)行線程首先在自己的工作內(nèi)存對(duì)變量進(jìn)行賦值操作,然后再寫(xiě)入主存,而不是直接寫(xiě)入到主存。當(dāng)兩個(gè)線程同時(shí)執(zhí)行這段代碼的時(shí)候,我們希望執(zhí)行完成后i的值為12,但實(shí)際情況確不一定是這樣,存在了以下場(chǎng)景:

初始時(shí),兩個(gè)線程分別將i=10的值寫(xiě)入到自己的工作內(nèi)存,然后線程1對(duì)i執(zhí)行加1操作,然后線程1將i=11寫(xiě)入到主存;如果當(dāng)線程1將i=11寫(xiě)入之前,線程2執(zhí)行i+1時(shí),由于主存中的i=10,所以線程2取到i=10,然后執(zhí)行i+1,執(zhí)行完成后i的值是11,最后主存中的i=11。

原子性:原子性指的是一個(gè)操作要么全部執(zhí)行,要么全不執(zhí)行。Java中看似很簡(jiǎn)單的x++操作,其實(shí)并不是原子操作,編譯后產(chǎn)生了多條指令。Java內(nèi)存模型只保證基本的讀取和賦值的原子性,其他的操作需要程序員自己保證,可以通過(guò)synchronized、lock等手段。

可見(jiàn)性:可見(jiàn)性指的是多個(gè)線程同時(shí)訪問(wèn)某個(gè)變量時(shí),一個(gè)線程修改這個(gè)變量的值,其余線程應(yīng)該能夠馬上看到修改后的值。Java通過(guò)volatile關(guān)鍵字來(lái)保證可見(jiàn)性,它會(huì)保證修改的值會(huì)被立即更新到主存。

有序性:Java在編譯的時(shí)候,為了提高程序的運(yùn)行效率,會(huì)對(duì)指令進(jìn)行重排,但執(zhí)行結(jié)果不會(huì)影響。

理解Volatile的工作:

1、保證了可見(jiàn)性:一旦變量聲明為volatile,對(duì)變量的修改都在主內(nèi)存,那么一個(gè)線程修改變量后,該修改后的變量對(duì)其他線程立即可見(jiàn)。

線程2通過(guò)設(shè)置stop想要達(dá)到中斷線程1的目的并不一定能夠?qū)崿F(xiàn),線程1將stop變量保存到自己的工作內(nèi)存,線程2設(shè)置stop后線程1還沒(méi)來(lái)得及寫(xiě)入主存就去干其余事了。這種場(chǎng)景下就需要使用volatile變量來(lái)修飾stop變量,保證線程2修改stop后立即將stop變量強(qiáng)制寫(xiě)入主存;并且導(dǎo)致線程1的工作線程中的stop變量的緩存值立即無(wú)效;由于線程1工作內(nèi)存中的stop變量已經(jīng)無(wú)效,所以線程1再次讀取stop變量的時(shí)候會(huì)去主存取數(shù)據(jù)。

2、保證有序性:Volatile關(guān)鍵幀禁止指令重排,所以在一定程度上保證有序。

3、不保證原子性:volatile不能保證原子性。

10、JVM內(nèi)存結(jié)構(gòu)

https://www.cnblogs.com/SaraMoring/p/5713732.html

線程棧(虛擬機(jī)棧):每個(gè)線程都有自己的線程棧【線程棧的大小可以通過(guò)JVM的-Xss參數(shù)進(jìn)行設(shè)置,32位系統(tǒng)在JDK5之后,每個(gè)線程棧的大小為1M】,線程棧中存放的數(shù)據(jù)都是線程私有的。棧里面存放著棧幀,代表一個(gè)函數(shù)調(diào)用,棧幀里面存放著函數(shù)的形參、局部變量、返回地址等。

堆:所有的線程都共享同一個(gè)堆空間,堆里面存放的是對(duì)象數(shù)據(jù)(排除基本類(lèi)型數(shù)據(jù)和引用以外的數(shù)據(jù))。

方法區(qū):線程共享的數(shù)據(jù)區(qū)。

本地方法棧:為Java調(diào)用本地方法(JNI)提供的。

程序計(jì)數(shù)器:在CPU中有個(gè)程序計(jì)數(shù)器,存放了下一條CPU指令的地址。JAVA虛擬機(jī)沒(méi)有使用CPU的程序計(jì)數(shù)器,而是在虛擬機(jī)內(nèi)存中開(kāi)辟了一塊區(qū)域來(lái)模擬CPU的程序計(jì)數(shù)器功能。JVM中,每個(gè)線程都有自己的程序計(jì)數(shù)器,JVM的程序計(jì)數(shù)器里面存放的是當(dāng)前正在執(zhí)行的字節(jié)碼地址,而不是下一條。

Java虛擬機(jī)棧:虛擬機(jī)棧也是線程私有的,描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀,用于保存局部變量表、操作數(shù)棧等。方法的執(zhí)行對(duì)應(yīng)著一個(gè)棧幀從入棧到出棧的過(guò)程。局部變量表中存放著編譯期間可以知道的基本類(lèi)型(int char…)、對(duì)象引用。局部變量表所需要的內(nèi)存在編譯的時(shí)候就已經(jīng)分配好了,當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法在棧幀中需要多少局部變量空間是確定好的,方法運(yùn)行期間不會(huì)改變局部變量表的大小。如果方法遞歸嚴(yán)重,虛擬機(jī)??赡軙?huì)保存StackOverFollowError的異常。

本地方法棧:本地方法棧存放了JNI調(diào)用的棧幀。

Java堆:堆是所有線程共享的區(qū)域,是用來(lái)存放對(duì)象的,幾乎所有的實(shí)例對(duì)象都在里面分配,堆是垃圾收集器主要工作的地方。

方法區(qū):方法區(qū)也叫永久代,是線程共享的區(qū)域,存儲(chǔ)了虛擬機(jī)加載的類(lèi)信息、常量池等。常量池是方法區(qū)的一部分,存放著編譯期間已經(jīng)確定好的常量(字面量,static final類(lèi)型)。

JVM共享的數(shù)據(jù)區(qū)域如下:

分為堆(新生代、老年代)、永久代(方法區(qū)),新生代可以分為Eden區(qū)(存放新生對(duì)象)、Survivor幸存區(qū)(存放垃圾回收后幸存的對(duì)象);永久代(方法區(qū))管理類(lèi)信息、常量、靜態(tài)對(duì)象等。

Jvm的堆的垃圾收集采用分代收集策略,新生代采用復(fù)制算法,老年代采用標(biāo)記整理算法。

復(fù)制算法:將內(nèi)存分為A/B部分:

1. 新生對(duì)象被分配到A塊中未使用的內(nèi)存當(dāng)中。當(dāng)A塊的內(nèi)存用完了,?把A塊的存活對(duì)象對(duì)象復(fù)制到B塊。

2.清理A塊所有對(duì)象。

3.新生對(duì)象被分配的B塊中未使用的內(nèi)存當(dāng)中。當(dāng)B塊的內(nèi)存用完了,?把B塊的存活對(duì)象對(duì)象復(fù)制到A塊。

4.清理B塊所有對(duì)象。

5. goto 1

11、深拷貝和淺拷貝

淺拷貝:創(chuàng)建一個(gè)新的實(shí)例,將舊實(shí)例的成員逐個(gè)復(fù)制給新的實(shí)例,類(lèi)似于復(fù)寫(xiě)clone方法,引用變量沒(méi)有重新創(chuàng)建。

深拷貝:實(shí)現(xiàn)類(lèi)的拷貝構(gòu)造函數(shù)時(shí),不僅復(fù)制所有的非引用變量,還要為引用變量創(chuàng)建新的實(shí)例。

12、泛型的extend和super的區(qū)別

假設(shè)有這樣一個(gè)盤(pán)子,實(shí)現(xiàn)如下。

現(xiàn)在定義一個(gè)水果盤(pán)子,理論上可以如下實(shí)現(xiàn):

邏輯上裝水果的盤(pán)子肯定可以裝蘋(píng)果,但實(shí)際上java編譯器卻不允許,編譯器認(rèn)為蘋(píng)果是一種水果,但裝蘋(píng)果的盤(pán)子并不一定是裝水果的盤(pán)子。于是就有了extend和super的概念。

extends:上界通配符,如下所示,可以存放Fruit的所有子類(lèi):

Plate p = new Plate();//存放蘋(píng)果

Plate p = new Plate();//存放橘子

Note:使用extends通配符后,Plate的set方法不能使用,試想一下,Plate <? extends Fruit> p可能是Apple類(lèi)型的,也可能是Origin類(lèi)型的,如果p指向Apple類(lèi)型,而set進(jìn)去一個(gè)Origin類(lèi)型,在運(yùn)行時(shí)就會(huì)報(bào)錯(cuò),所以不能set。Get方法是可以使用的,因?yàn)間et出來(lái)的肯定是一種Fruit。

super:下界通配符,存放Fruit的父類(lèi)。

Plate p = new Plate();

Plate p = new Plate();

Note:使用super通配符后,Plate的get方法不能使用,試想一下,Plate <? super Fruit> p可能是Food類(lèi)型的,也可能是Object類(lèi)型的,如果p指向Food類(lèi)型,而get的時(shí)候使用Fruit f = p.get(),F(xiàn)ood并不一定是Fruit在運(yùn)行時(shí)就會(huì)報(bào)錯(cuò),所以不能get。set方法是可以使用的,因?yàn)閟et進(jìn)去Fruit,肯定是Fruit的父類(lèi)。

類(lèi)型擦除:Java的泛型是在編譯層實(shí)現(xiàn)的,編譯完成后class里面會(huì)使用原始類(lèi)型。例如List、List在編譯完成后,都變成了List,只能存放Object類(lèi)型。如果類(lèi)型變量有限定的話,那么原始類(lèi)型就用第一個(gè)邊界類(lèi)型變量來(lái)替換,例如Plate<?extends Fruit>就被擦除成Plate<Fruit>。

橋接模式

13、HashMap和HashTable的區(qū)別

HashTable是基于陳舊的Dictionary類(lèi),完成Map接口;HashMap繼承與AbstractMap。

HashTable的方法是同步的,通過(guò)Synchronized實(shí)現(xiàn)線程的安全;HashMap是非同步的。

HashTable不允許null類(lèi)型的key和value進(jìn)行存儲(chǔ);HashMap允許null類(lèi)型的key或者value。

HashTable使用的是Enumeration遍歷,HashMap使用的是Iterator。

14、HashMap和HashSet的區(qū)別

HashSet實(shí)現(xiàn)了Set接口,不允許有重復(fù)的值,HashSet內(nèi)部也是使用HashMap來(lái)實(shí)現(xiàn)的,只是使用插入進(jìn)去的值作為Key,使用固定的PRESENT作為value。

HashMap的put方法如下:

如果HashSet插入進(jìn)去的值的hashCode已經(jīng)在map中存在,所以會(huì)直接更新Node的值。

HashMap判斷重復(fù)的過(guò)程:根據(jù)Key計(jì)算hashCode,根據(jù)hashCode查詢(xún)Node列表,如果找到Node列表就說(shuō)明可能存在重復(fù)。接著比較Node的Key和put傳入的Key是否完全一致(引用一致、equals返回true、hashCode一致),HashSet在插入元素的時(shí)候,如果插入相同的對(duì)象,這邊就完全一致。如果完全一致,說(shuō)明插入的元素存在重復(fù),直接更新舊value即可。如果Key不完全一致,就查詢(xún)Node列表,在列表里面查詢(xún)Key完全一致的元素,如果找到就更新Value,找不到就插入新的Node。

也就是說(shuō)存在重復(fù)的條件是Key的引用一致、equals方法一致、hashCode一致。

HashSet使用add的對(duì)象作為Key,如果是同一個(gè)對(duì)象,就說(shuō)明存在重復(fù)。

15、Java異常體系

16、修改equals方法的簽名

正確的equals方法如下:

正確寫(xiě)法

錯(cuò)誤的寫(xiě)法如下:

錯(cuò)誤寫(xiě)法

錯(cuò)誤的寫(xiě)法雖然在調(diào)用foodA.equals(foodB)的時(shí)候能夠正常運(yùn)行,但當(dāng)將foodA存到集合的時(shí)候就有問(wèn)題。例如:List.add(foodA)?? List.contains(foodA)卻返回了false。

17、ThreadLocal如何保證local

ThreadLocal

ThreadLocal的set和get方法如上圖所示,它保證了每隔線程有自己的變量副本,假設(shè)有如下ThreadLocal:

?????????????? ThreadLocal tl =new ThreadLocal();

當(dāng)線程A和線程B同時(shí)訪問(wèn)tl時(shí),每個(gè)線程獲取到的都是屬于自己Thread的本地變量,互相不影響。原理就在于ThreadLocal的數(shù)據(jù)結(jié)構(gòu)。

每個(gè)Thread都有一個(gè)ThreadLocalMap類(lèi)型的threadLocals變量,當(dāng)調(diào)用ThreadLocal.set的時(shí)候,map里面存儲(chǔ)了當(dāng)前ThreadLoacal的引用和value值對(duì)象,假設(shè)線程A訪問(wèn)了tl,線程A的threadLocals的map中就有Key=tl,value=v的鍵值對(duì),當(dāng)調(diào)用get的時(shí)候,會(huì)先獲取線程A的threadLocals對(duì)象,入得threadLoacal對(duì)應(yīng)的value,每個(gè)線程都是取的自己ThreadLocalMap中的ThreadLocal對(duì)應(yīng)的Value,互相不影響。

18、線程的狀態(tài)

新建:新創(chuàng)建一個(gè)線程對(duì)象?

就緒:線程對(duì)象創(chuàng)建后,調(diào)用了Thread.start方法,等待被調(diào)度

運(yùn)行:可運(yùn)行狀態(tài)的線程獲得cpu的時(shí)間片,執(zhí)行程序代碼?

阻塞:由于某種原因而放棄cpu的使用權(quán),暫時(shí)停止運(yùn)行。直到進(jìn)入可運(yùn)行狀態(tài)才有機(jī)會(huì)再次獲得cpu的時(shí)間片。?

結(jié)束:run方法執(zhí)行結(jié)束?

線程狀態(tài)

19、類(lèi)的初始化時(shí)機(jī)

遇到new、getstatic、putstatic、invokestatic,如果類(lèi)還沒(méi)有初始化,則先觸發(fā)類(lèi)的初始化。常見(jiàn)場(chǎng)景:new一個(gè)對(duì)象、調(diào)用或者設(shè)置類(lèi)的靜態(tài)變量、調(diào)用類(lèi)的靜態(tài)方法。

使用java.lang.reflect反射類(lèi)時(shí),如果類(lèi)還沒(méi)創(chuàng)建,則觸發(fā)初始化。

當(dāng)初始化一個(gè)類(lèi)時(shí),如果父類(lèi)還沒(méi)初始化,則先觸發(fā)其父類(lèi)初始化。

當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)虛擬機(jī)執(zhí)行的類(lèi)(main函數(shù)),虛擬機(jī)會(huì)先觸發(fā)這個(gè)類(lèi)的初始化。

總結(jié):一個(gè)類(lèi)對(duì)另一個(gè)類(lèi)進(jìn)行主動(dòng)引用,將會(huì)觸發(fā)類(lèi)的初始化,否則不會(huì)。

20、類(lèi)加載機(jī)制

加載:將類(lèi)文件的二進(jìn)制流加載到內(nèi)存;將二進(jìn)制流的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu);在內(nèi)存中生成代表這個(gè)類(lèi)的java.lang.class對(duì)象、作為方法區(qū)這個(gè)類(lèi)各種對(duì)象的訪問(wèn)入口。

驗(yàn)證:驗(yàn)證Class文件的字節(jié)流是否符合虛擬機(jī)的要求,包括文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證、符號(hào)引用驗(yàn)證。

準(zhǔn)備:為類(lèi)變量(static變量)分配內(nèi)存并設(shè)置初始值。

解析:將常量池的符號(hào)引用轉(zhuǎn)化為直接引用。

初始化:開(kāi)始執(zhí)行類(lèi)中定義的java程序,執(zhí)行類(lèi)構(gòu)造器<cinit>,cinit是由編譯器自動(dòng)收集所有類(lèi)變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊中的語(yǔ)句合并產(chǎn)生的,編譯器收集的順序由語(yǔ)句在代碼中出現(xiàn)的順序決定,靜態(tài)語(yǔ)句塊中只能訪問(wèn)定義在它之前的靜態(tài)變量,定義在之后變量,前面只能賦值,不能訪問(wèn)。<cinit>方法與類(lèi)的構(gòu)造器(<init>)不同,它不需要顯式地調(diào)用父類(lèi)構(gòu)造器,虛擬機(jī)會(huì)保證子類(lèi)的<cinit>執(zhí)行之前,父類(lèi)的<cinit>已經(jīng)執(zhí)行完畢。因此虛擬機(jī)中第一個(gè)執(zhí)行的<cinit>方法是java.lang.Object的<cinit>方法。由于父類(lèi)的<cinit>先執(zhí)行,所以父類(lèi)的靜態(tài)語(yǔ)句塊優(yōu)先于子類(lèi)執(zhí)行。

<cinit>對(duì)于類(lèi)來(lái)說(shuō)不是必須的,如果類(lèi)沒(méi)有靜態(tài)語(yǔ)句也沒(méi)有賦值操作,可以不生成<cinit>。

接口中不能有靜態(tài)語(yǔ)句塊,但同樣有賦值操作,所以接口也會(huì)生成cinit方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • (一)Java部分 1、列舉出JAVA中6個(gè)比較常用的包【天威誠(chéng)信面試題】 【參考答案】 java.lang;ja...
    獨(dú)云閱讀 7,275評(píng)論 0 62
  • Java SE 基礎(chǔ): 封裝、繼承、多態(tài) 封裝: 概念:就是把對(duì)象的屬性和操作(或服務(wù))結(jié)合為一個(gè)獨(dú)立的整體,并盡...
    Jayden_Cao閱讀 2,259評(píng)論 0 8
  • 相關(guān)概念 面向?qū)ο蟮娜齻€(gè)特征 封裝,繼承,多態(tài).這個(gè)應(yīng)該是人人皆知.有時(shí)候也會(huì)加上抽象. 多態(tài)的好處 允許不同類(lèi)對(duì)...
    東經(jīng)315度閱讀 2,211評(píng)論 0 8
  • 有一種誓約,叫《海誓山盟亦會(huì)分開(kāi)》。 有一種年齡,叫《未成年》。 有一種風(fēng)景,叫《你在看孤獨(dú)的風(fēng)景》。 有一種相守...
    柒弒閱讀 736評(píng)論 0 0
  • 看過(guò)一個(gè)短片,幾位媽媽給各自寶寶們打分,滿分是10分,最差是1分。 媽媽們給寶寶打分,都得...
    紫軒暢婉閱讀 924評(píng)論 0 2

友情鏈接更多精彩內(nèi)容