Java經(jīng)典面試題最全匯總208道(一)
前言
短時間提升自己最快的手段就是背面試題,最近總結(jié)了Java常用的面試題,分享給大家,希望大家都能圓夢大廠,加油,我命由我不由天。
1、JDK 和 JRE 有什么區(qū)別?
JDK(Java Development Kit),Java開發(fā)工具包
JRE(Java Runtime Environment),Java運行環(huán)境
JDK中包含JRE,JDK中有一個名為jre的目錄,里面包含兩個文件夾bin和lib,bin就是JVM,lib就是JVM工作所需要的類庫。
2、== 和 equals 的區(qū)別是什么?
- 對于基本類型,==比較的是值;
- 對于引用類型,==比較的是地址;
- equals不能用于基本類型的比較;
- 如果沒有重寫equals,equals就相當于==;
- 如果重寫了equals方法,equals比較的是對象的內(nèi)容;
3、final 在 java 中有什么作用?
(1)用來修飾一個引用
- 如果引用為基本數(shù)據(jù)類型,則該引用為常量,該值無法修改;
- 如果引用為引用數(shù)據(jù)類型,比如對象、數(shù)組,則該對象、數(shù)組本身可以修改,但指向該對象或數(shù)組的地址的引用不能修改。
- 如果引用時類的成員變量,則必須當場賦值,否則編譯會報錯。
(2)用來修飾一個方法
當使用final修飾方法時,這個方法將成為最終方法,無法被子類重寫。
但是,該方法仍然可以被繼承。
(3)用來修飾類
當用final修改類時,該類成為最終類,無法被繼承。
比如常用的String類就是最終類。
4、java 中的 Math.round(-1.5) 等于多少?
Math提供了三個與取整有關(guān)的方法:ceil、floor、round
(1)ceil:向上取整;
Math.ceil(11.3) = 12;
Math.ceil(-11.3) = 11;
(2)floor:向下取整;
Math.floor(11.3) = 11;
Math.floor(-11.3) = -12;
(3)round:四舍五入;
加0.5然后向下取整。
Math.round(11.3) = 11;
Math.round(11.8) = 12;
Math.round(-11.3) = -11;
Math.round(-11.8) = -12;
5、String 屬于基礎(chǔ)的數(shù)據(jù)類型嗎?
不屬于。
八種基本數(shù)據(jù)類型:byte、short、char、int、long、double、float、boolean。
6、String str="i"與 String str=new String(“i”)一樣嗎?
String str="i"會將起分配到常量池中,常量池中沒有重復的元素,如果常量池中存中i,就將i的地址賦給變量,如果沒有就創(chuàng)建一個再賦給變量。
String str=new String(“i”)會將對象分配到堆中,即使內(nèi)存一樣,還是會重新創(chuàng)建一個新的對象。
7、如何將字符串反轉(zhuǎn)?
將對象封裝到stringBuilder中,調(diào)用reverse方法反轉(zhuǎn)。
8、String 類的常用方法都有那些?
(1)常見String類的獲取功能
length:獲取字符串長度;
charAt(int index):獲取指定索引位置的字符;
indexOf(int ch):返回指定字符在此字符串中第一次出現(xiàn)處的索引;
substring(int start):從指定位置開始截取字符串,默認到末尾;
substring(int start,int end):從指定位置開始到指定位置結(jié)束截取字符串;
(2)常見String類的判斷功能
equals(Object obj): 比較字符串的內(nèi)容是否相同,區(qū)分大小寫;
contains(String str): 判斷字符串中是否包含傳遞進來的字符串;
startsWith(String str): 判斷字符串是否以傳遞進來的字符串開頭;
endsWith(String str): 判斷字符串是否以傳遞進來的字符串結(jié)尾;
isEmpty(): 判斷字符串的內(nèi)容是否為空串"";
(3)常見String類的轉(zhuǎn)換功能
byte[] getBytes(): 把字符串轉(zhuǎn)換為字節(jié)數(shù)組;
char[] toCharArray(): 把字符串轉(zhuǎn)換為字符數(shù)組;
String valueOf(char[] chs): 把字符數(shù)組轉(zhuǎn)成字符串。
valueOf可以將任意類型轉(zhuǎn)為字符串;
toLowerCase(): 把字符串轉(zhuǎn)成小寫;
toUpperCase(): 把字符串轉(zhuǎn)成大寫;
concat(String str): 把字符串拼接;
(4)常見String類的其他常用功能
replace(char old,char new) 將指定字符進行互換
replace(String old,String new) 將指定字符串進行互換
trim() 去除兩端空格
int compareTo(String str) 會對照ASCII 碼表 從第一個字母進行減法運算 返回的就是這個減法的結(jié)果。
如果前面幾個字母一樣會根據(jù)兩個字符串的長度進行減法運算返回的就是這個減法的結(jié)果,如果連個字符串一摸一樣 返回的就是0。
9、new String("a") + new String("b") 會創(chuàng)建幾個對象?
對象1:new StringBuilder()
對象2:new String("a")
對象3:常量池中的"a"
對象4:new String("b")
對象5:常量池中的"b"
深入剖析:StringBuilder中的toString():
對象6:new String("ab")
強調(diào)一下,toString()的調(diào)用,在字符串常量池中,沒有生成"ab"
附加題
String s1 = new String("1") + new String("1");//s1變量記錄的地址為:new String
s1.intern();//在字符串常量池中生成"11"。如何理解:jdk6:創(chuàng)建了一個新的對象"11",也就有新的地址;jdk7:此時常量池中并沒有創(chuàng)建"11",而是創(chuàng)建了一個指向堆空間中new String("11")的地址;
String s2 = "11";
System.out.println(s1 == s2);//jdk6:false;jdk7:true
10、如何將字符串反轉(zhuǎn)?
添加到StringBuilder中,然后調(diào)用reverse()。
11、String 類的常用方法都有那些?
equals、length、contains、replace、split、hashcode、indexof、substring、trim、toUpperCase、toLowerCase、isEmpty等等。
12、普通類和抽象類有哪些區(qū)別?
抽象類不能被實例化;
抽象類可以有抽象方法,只需申明,無須實現(xiàn);
有抽象方法的類一定是抽象類;
抽象類的子類必須實現(xiàn)抽象類中的所有抽象方法,否則子類仍然是抽象類;
抽象方法不能聲明為靜態(tài)、不能被static、final修飾。
13、接口和抽象類有什么區(qū)別?
(1)接口
接口使用interface修飾;
接口不能實例化;
類可以實現(xiàn)多個接口;
①java8之前,接口中的方法都是抽象方法,省略了public abstract。
②java8之后;接口中可以定義靜態(tài)方法,靜態(tài)方法必須有方法體,普通方法沒有方法體,需要被實現(xiàn);
(2)抽象類
抽象類使用abstract修飾;
抽象類不能被實例化;
抽象類只能單繼承;
抽象類中可以包含抽象方法和非抽象方法,非抽象方法需要有方法體;
如果一個類繼承了抽象類,
①如果實現(xiàn)了所有的抽象方法,子類可以不是抽象類;
②如果沒有實現(xiàn)所有的抽象方法,子類仍然是抽象類。
14、java 中 IO 流分為幾種?
(1)按流劃分,可以分為輸入流和輸出流;
(2)按單位劃分,可以分為字節(jié)流和字符流;
字節(jié)流:inputStream、outputStream;
字符流:reader、writer;
15、BIO、NIO、AIO 有什么區(qū)別?
(1)同步阻塞BIO
一個連接一個線程。
JDK1.4之前,建立網(wǎng)絡(luò)連接的時候采用BIO模式,先在啟動服務(wù)端socket,然后啟動客戶端socket,對服務(wù)端通信,客戶端發(fā)送請求后,先判斷服務(wù)端是否有線程響應。
如果沒有則會一直等待或者遭到拒絕請求,如果有的話會等待請求結(jié)束后才繼續(xù)執(zhí)行。
(2)同步非阻塞NIO
NIO主要是想解決BIO的大并發(fā)問題,BIO是每一個請求分配一個線程,當請求過多時,每個線程占用一定的內(nèi)存空間,服務(wù)器癱瘓了。
JDK1.4開始支持NIO,適用于連接數(shù)目多且連接比較短的架構(gòu),比如聊天 服務(wù)器,并發(fā)局限于應用中。
一個請求一個線程。
(3)異步非阻塞AIO
一個有效請求一個線程。
JDK1.7開始支持AIO,適用于連接數(shù)目多且連接比較長的結(jié)構(gòu),比如相冊服務(wù)器,充分調(diào)用OS參與并發(fā)操作。
16、Files的常用方法都有哪些?
exist
createFile
createDirectory
write
read
copy
size
delete
move
17、什么是反射?
所謂反射,是java在運行時進行自我觀察的能力,通過class、constructor、field、method四個方法獲取一個類的各個組成部分。
在Java運行時環(huán)境中,對任意一個類,可以知道類有哪些屬性和方法。
這種動態(tài)獲取類的信息以及動態(tài)調(diào)用對象的方法的功能來自于反射機制。
18、什么是 java 序列化?什么情況下需要序列化?
序列化就是一種用來處理對象流的機制。
將對象的內(nèi)容流化,將流化后的對象傳輸于網(wǎng)絡(luò)之間。
序列化是通過實現(xiàn)serializable接口,該接口沒有需要實現(xiàn)的方法,implement Serializable只是為了標注該對象是可被序列化的,使用一個輸出流(FileOutputStream)來構(gòu)造一個ObjectOutputStream對象,接著使用ObjectOutputStream對象的writeObejct(Object object)方法就可以將參數(shù)的obj對象到磁盤,需要恢復的時候使用輸入流。
序列化是將對象轉(zhuǎn)換為容易傳輸?shù)母袷降倪^程。
例如,可以序列化一個對象,然后通過HTTP通過Internet在客戶端和服務(wù)器之間傳輸該對象。
在另一端,反序列化將從流中心構(gòu)造成對象。
一般程序在運行時,產(chǎn)生對象,這些對象隨著程序的停止而消失,但我們想將某些對象保存下來。
這時,我們就可以通過序列化將對象保存在磁盤,需要使用的時候通過反序列化獲取到。
對象序列化的最主要目的就是傳遞和保存對象,保存對象的完整性和可傳遞性。
譬如通過網(wǎng)絡(luò)傳輸或者把一個對象保存成本地一個文件的時候,需要使用序列化。
19、為什么要使用克?。咳绾螌崿F(xiàn)對象克???深拷貝和淺拷貝區(qū)別是什么?
(1)什么要使用克?。?/p>
想對一個對象進行復制,又想保留原有的對象進行接下來的操作,這個時候就需要克隆了。
(2)如何實現(xiàn)對象克隆?
實現(xiàn)Cloneable接口,重寫clone方法;
實現(xiàn)Serializable接口,通過對象的序列化和反序列化實現(xiàn)克隆,可以實現(xiàn)真正的深克隆。
BeanUtils,apache和Spring都提供了bean工具,只是這都是淺克隆。
(3)深拷貝和淺拷貝區(qū)別是什么?
淺拷貝:僅僅克隆基本類型變量,不克隆引用類型變量;
深克?。杭瓤寺』绢愋妥兞浚挚寺∫妙愋妥兞?;
(4)代碼實例
20、throw 和 throws 的區(qū)別?
(1)throw
作用在方法內(nèi),表示拋出具體異常,由方法體內(nèi)的語句處理;
一定拋出了異常;
(2)throws
作用在方法的聲明上,表示拋出異常,由調(diào)用者來進行異常處理;
可能出現(xiàn)異常,不一定會發(fā)生異常;
21、final、finally、finalize 有什么區(qū)別?
final可以修飾類,變量,方法,修飾的類不能被繼承,修飾的變量不能重新賦值,修飾的方法不能被重寫
finally用于拋異常,finally代碼塊內(nèi)語句無論是否發(fā)生異常,都會在執(zhí)行finally,常用于一些流的關(guān)閉。
finalize方法用于垃圾回收。
一般情況下不需要我們實現(xiàn)finalize,當對象被回收的時候需要釋放一些資源,比如socket鏈接,在對象初始化時創(chuàng)建,整個生命周期內(nèi)有效,那么需要實現(xiàn)finalize方法,關(guān)閉這個鏈接。
但是當調(diào)用finalize方法后,并不意味著gc會立即回收該對象,所以有可能真正調(diào)用的時候,對象又不需要回收了,然后到了真正要回收的時候。
因為之前調(diào)用過一次,這次又不會調(diào)用了,產(chǎn)生問題。
所以,不推薦使用finalize方法。
22、try-catch-finally 中,如果 catch 中 return 了,finally 還會執(zhí)行嗎?
23、常見的異常類有哪些?
- NullPointerException:空指針異常;
- SQLException:數(shù)據(jù)庫相關(guān)的異常;
- IndexOutOfBoundsException:數(shù)組下角標越界異常;
- FileNotFoundException:打開文件失敗時拋出;
- IOException:當發(fā)生某種IO異常時拋出;
- ClassCastException:當試圖將對象強制轉(zhuǎn)換為不是實例的子類時,拋出此異常;
- NoSuchMethodException:無法找到某一方法時,拋出;
- ArrayStoreException:試圖將錯誤類型的對象存儲到一個對象數(shù)組時拋出的異常;
- NumberFormatException:當試圖將字符串轉(zhuǎn)換成數(shù)字時,失敗了,拋出;
- IllegalArgumentException 拋出的異常表明向方法傳遞了一個不合法或不正確的參數(shù)。
- ArithmeticException當出現(xiàn)異常的運算條件時,拋出此異常。例如,一個整數(shù)“除以零”時,拋出此類的一個實例。
24、hashcode是什么?有什么作用?
Java中Object有一個方法:
public native int hashcode();
(1)hashcode()方法的作用
hashcode()方法主要配合基于散列的集合一起使用,比如HashSet、HashMap、HashTable。
當集合需要添加新的對象時,先調(diào)用這個對象的hashcode()方法,得到對應的hashcode值,實際上hashmap中會有一個table保存已經(jīng)存進去的對象的hashcode值
如果table中沒有改hashcode值,則直接存入,如果有,就調(diào)用equals方法與新元素進行比較,相同就不存了,不同就存入。
(2)equals和hashcode的關(guān)系
如果equals為true,hashcode一定相等;
如果equals為false,hashcode不一定不相等;
如果hashcode值相等,equals不一定相等;
如果hashcode值不等,equals一定不等;
(3)重寫equals方法時,一定要重寫hashcode方法
(4)百度百科
hashcode方法返回該對象的哈希碼值。支持該方法是為哈希表提供一些優(yōu)點,例如,java.util.Hashtable 提供的哈希表。
hashCode 的常規(guī)協(xié)定是:
在 Java 應用程序執(zhí)行期間,在同一對象上多次調(diào)用 hashCode 方法時,必須一致地返回相同的整數(shù),前提是對象上 equals 比較中所用的信息沒有被修改。從某一應用程序的一次執(zhí)行到同一應用程序的另
一次執(zhí)行,該整數(shù)無需保持一致。
如果根據(jù) equals(Object) 方法,兩個對象是相等的,那么在兩個對象中的每個對象上調(diào)用 hashCode 方法都必須生成相同的整數(shù)結(jié)果。
以下情況不 是必需的:如果根據(jù) equals(java.lang.Object) 方法,兩個對象不相等,那么在兩個對象中的任一對象上調(diào)用 hashCode 方法必定會生成不同的整數(shù)結(jié)果。
但是,程序員應該知道,為不相等的對象生成不同整數(shù)結(jié)果可以提高哈希表的性能。
實際上,由 Object 類定義的 hashCode 方法確實會針對不同的對象返回不同的整數(shù)。(這一般是通過將該對象的內(nèi)部地址轉(zhuǎn)換成一個整數(shù)來實現(xiàn)的,但是 JavaTM 編程語言不需要這種實現(xiàn)技巧。)
當equals方法被重寫時,通常有必要重寫 hashCode 方法,以維護 hashCode 方法的常規(guī)協(xié)定,該協(xié)定聲明相等對象必須具有相等的哈希碼。
(5)小白解釋
1.hashcode是用來查找的,如果你學過數(shù)據(jù)結(jié)構(gòu)就應該知道,在查找和排序這一章有
例如內(nèi)存中有這樣的位置
0 1 2 3 4 5 6 7
而我有個類,這個類有個字段叫ID,我要把這個類存放在以上8個位置之一,如果不用hashcode而任意存放,那么當查找時就需要到這八個位置里挨個去找,或者用二分法一類的算法。
但如果用hashcode那就會使效率提高很多。
我們這個類中有個字段叫ID,那么我們就定義我們的hashcode為ID%8,然后把我們的類存放在取得得余數(shù)那個位置。比如我們的ID為9,9除8的余數(shù)為1,那么我們就把該類存在1這個位置
如果ID是13,求得的余數(shù)是5,那么我們就把該類放在5這個位置。這樣,以后在查找該類時就可以通過ID除 8求余數(shù)直接找到存放的位置了。
2.但是如果兩個類有相同的hashcode怎么辦那(我們假設(shè)上面的類的ID不是唯一的),例如9除以8和17除以8的余數(shù)都是1,那么這是不是合法的,回答是:可以這樣。那么如何判斷呢?在這個時候就需
要定義 equals了。
也就是說,我們先通過 hashcode來判斷兩個類是否存放某個桶里,但這個桶里可能有很多類,那么我們就需要再通過 equals 來在這個桶里找到我們要的類。
那么。重寫了equals(),為什么還要重寫hashCode()呢?
想想,你要在一個桶里找東西,你必須先要找到這個桶啊,你不通過重寫hashcode()來找到桶,光重寫equals()有什么用啊。
25、java 中操作字符串都有哪些類?它們之間有什么區(qū)別?
(1)String
String是不可變對象,每次對String類型的改變時都會生成一個新的對象。
(2)StringBuilder
線程不安全,效率高,多用于單線程。
(3)StringBuffer
線程安全,由于加鎖的原因,效率不如StringBuilder,多用于多線程。
不頻繁的字符串操作使用String,操作頻繁的情況不建議使用String。
StringBuilder > StringBuffer > String。
26、java 中都有哪些引用類型?
(1)強引用
Java中默認聲明的就是強引用,比如:
Object obj = new Object(); obj = null;
只要強引用存在,垃圾回收器將永遠不會回收被引用的對象。如果想被回收,可以將對象置為null;
(2)軟引用(SoftReference)
在內(nèi)存足夠的時候,軟引用不會被回收,只有在內(nèi)存不足時,系統(tǒng)才會回收軟引用對象,如果回收了軟引用對象之后仍然沒有足夠的內(nèi)存,才會跑出內(nèi)存溢出異常。
byte[] buff = new byte[1024 * 1024];
SoftReference<byte[]> sr = new SoftReference<>(buff);
(3)弱引用(WeakReference)
進行垃圾回收時,弱引用就會被回收。
(4)虛引用(PhantomReference)
(5)引用隊列(ReferenceQueue)
引用隊列可以與軟引用、弱引用、虛引用一起配合使用。
當垃圾回收器準備回收一個對象時,如果發(fā)現(xiàn)它還有引用,就會在回收對象之前,把這個引用加入到引用隊列中。
程序可以通過判斷引用隊列中是否加入了引用,來判斷被引用的對象是否將要被垃圾回收,這樣可以在對象被回收之前采取一些必要的措施。
27、在 Java 中,為什么不允許從靜態(tài)方法中訪問非靜態(tài)變量?
- 靜態(tài)變量屬于類本身,在類加載的時候就會分配內(nèi)存,可以通過類名直接訪問;
- 非靜態(tài)變量屬于類的對象,只有在類的對象產(chǎn)生時,才會分配內(nèi)存,通過類的實例去訪問;
- 靜態(tài)方法也屬于類本身,但是此時沒有類的實例,內(nèi)存中沒有非靜態(tài)變量,所以無法調(diào)用。
28、說說Java Bean的命名規(guī)范
1、JavaBean 類必須是一個公共類,并將其訪問屬性設(shè)置為 public
2、JavaBean 類必須有一個空的構(gòu)造函數(shù):類中必須有一個不帶參數(shù)的公用構(gòu)造器,此構(gòu)造器也應該通過調(diào)用各個特性的設(shè)置方法來設(shè)置特性的缺省值。
3、一個javaBean類不應有公共實例變量,類變量都為private
4、持有值應該通過一組存取方法(getXxx 和 setXxx)來訪問:對于每個特性,應該有一個帶匹配公用 getter 和 setter 方法的專用實例變量。
屬性為布爾類型,可以使用 isXxx() 方法代替 getXxx() 方法。
通常屬性名是要和 包名、類名、方法名、字段名、常量名作出區(qū)別的:
首先:必須用英文,不要用漢語拼音
(1)包(package)
用于將完成不同功能的類分門別類,放在不同的目錄(包)下,包的命名規(guī)則:將公司域名反轉(zhuǎn)作為包名。
比如www.sohu.com 對于包名:每個字母都需要小寫。比如:com.sohu.test;該包下的Test類的全名是:com.sohu.Test.Java 。
如果定義類的時候沒有使用package,那么java就認為我們所定義的類位于默認包里面(default package)。
(2)類
首字母大寫,如果一個類由多個單詞構(gòu)成,那么每個單詞的首字母都大寫,而且中間不使用任何的連接符。盡量使用英文。如ConnectionFactory
(3)方法
首單詞全部小寫,如果一個方法由多個單詞構(gòu)成,那么從第二個單詞開始首字母大寫,不使用連接符。addPerson
(4)字段
與方法相同。如ageOfPerson
(5)常量
所有單詞的字母都是大寫,如果有多個單詞,那么使用下劃線鏈接即可。
如:public static final int AGE_OF_PERSON = 20; //通常加上static
29、Java Bean 屬性命名規(guī)范問題分析
public class User { private String busName; private String pCount; private Boolean isRunning; //正確的命名方式,駝峰式的 public String getBusName() { return busName; } public void setBusName(String busName) { this.busName = busName; } //這是什么? public String getpCount() { return pCount; } public void setpCount(String pCount) { this.pCount = pCount; } //這個也是不允許的 public Boolean getIsRunning() { return isRunning; } public void setIsRunning(Boolean isRunning) { this.isRunning = isRunning; } }
1. javabean屬性命名盡量使用常規(guī)的駝峰式命名規(guī)則
2. 屬性名第一個單詞盡量避免使用一個字母:如eBook, eMail。
3. boolean屬性名避免使用 “is” 開頭的名稱
4. 隨著jdk, eclipse, spring 等軟件版本的不斷提高, 底版本的出現(xiàn)的問題可能在高版本中解決了, 低版本原來正常的代碼可能在高版本環(huán)境下不再支持。
30、什么是 Java 的內(nèi)存模型?
在了解什么是 Java 內(nèi)存模型之前,先了解一下為什么要提出 Java 內(nèi)存模型。
之前提到過并發(fā)編程有三大問題
CPU 緩存,在多核 CPU 的情況下,帶來了可見性問題
操作系統(tǒng)對當前執(zhí)行線程的切換,帶來了原子性問題
譯器指令重排優(yōu)化,帶來了有序性問題
為了解決并發(fā)編程的三大問題,提出了 JSR-133,新的 Java 內(nèi)存模型,JDK 5 開始使用。
簡單總結(jié)下
- Java 內(nèi)存模型是 JVM 的一種規(guī)范
- 定義了共享內(nèi)存在多線程程序中讀寫操作行為的規(guī)范
- 屏蔽了各種硬件和操作系統(tǒng)的訪問差異,保證了 Java 程序在各種平臺下對內(nèi)存的訪問效果一致
- 解決并發(fā)問題采用的方式:限制處理器優(yōu)化和使用內(nèi)存屏障
- 增強了三個同步原語(synchronized、volatile、final)的內(nèi)存語義
- 定義了 happens-before 規(guī)則
31、在 Java 中,什么時候用重載,什么時候用重寫?
(1)重載是多態(tài)的集中體現(xiàn),在類中,要以統(tǒng)一的方式處理不同類型數(shù)據(jù)的時候,可以用重載。
(2)重寫的使用是建立在繼承關(guān)系上的,子類在繼承父類的基礎(chǔ)上,增加新的功能,可以用重寫。
(3)簡單總結(jié):
- 重載是多樣性,重寫是增強劑;
- 目的是提高程序的多樣性和健壯性,以適配不同場景使用時,使用重載進行擴展;
- 目的是在不修改原方法及源代碼的基礎(chǔ)上對方法進行擴展或增強時,使用重寫;
生活例子:
你想吃一碗面,我給你提供了拉面,炒面,刀削面,擔擔面供你選擇,這是重載;
你想吃一碗面,我不但給你端來了面,還給你加了青菜,加了雞蛋,這個是重寫;
設(shè)計模式:
cglib實現(xiàn)動態(tài)代理,核心原理用的就是方法的重寫;
詳細解答:
Java的重載(overload) 最重要的應用場景就是構(gòu)造器的重載,構(gòu)造器重載后,提供多種形參形式的構(gòu)造器,可以應對不同的業(yè)務(wù)需求,加強程序的健壯性和可擴展性
比如我們最近學習的Spring源碼中的ClassPathXmlApplicationContext,它的構(gòu)造函數(shù)使用重載一共提供了10個構(gòu)造函數(shù),這樣就為業(yè)務(wù)的選擇提供了多選擇性。
在應用到方法中時,主要是為了增強方法的健壯性和可擴展性,比如我們在開發(fā)中常用的各種工具類
比如我目前工作中的短信工具類SMSUtil, 發(fā)短信的方法就會使用重載,針對不同業(yè)務(wù)場景下的不同形參,提供短信發(fā)送方法,這樣提高了工具類的擴展性和健壯性。
總結(jié):重載必須要修改方法(構(gòu)造器)的形參列表,可以修改方法的返回值類型,也可以修改方法的異常信息即訪問權(quán)限;
使用范圍是在同一個類中,目的是對方法(構(gòu)造器)進行功能擴展,以應對多業(yè)務(wù)場景的不同使用需求。提高程序的健壯性和擴展性。
java的重寫(override) 只要用于子類對父類方法的擴展或修改,但是在我們開發(fā)中,為了避免程序混亂,重寫一般都是為了方法的擴展,比如在cglib方式實現(xiàn)的動態(tài)代理中,代理類就是繼承了目標類,對目標類的方法進行重寫,同時在方法前后進行切面織入。
- 總結(jié):方法重寫時,參數(shù)列表,返回值得類型是一定不能修改的,異??梢詼p少或者刪除,但是不能拋出新的異?;蛘吒鼜V的異常,方法的訪問權(quán)限可以降低限制,但是不能做更嚴格的限制。
(4)在里氏替換原則中,子類對父類的方法盡量不要重寫和重載。(我們可以采用final的手段強制來遵循)
32、舉例說明什么情況下會更傾向于使用抽象類而不是接口?
接口和抽象類都遵循”面向接口而不是實現(xiàn)編碼”設(shè)計原則,它可以增加代碼的靈活性,可以適應不斷變化的需求。
下面有幾個點可以幫助你回答這個問題:在 Java 中,你只能繼承一個類,但可以實現(xiàn)多個接口。
所以一旦你繼承了一個類,你就失去了繼承其他類的機會了。
接口通常被用來表示附屬描述或行為如: Runnable 、 Clonable 、 Serializable 等等,因此當你使用抽象類來表示行為時,你的類就不能同時是 Runnable 和 Clonable
( 注:這里的意思是指如果把 Runnable 等實現(xiàn)為抽象類的情況 )
因為在 Java 中你不能繼承兩個類,但當你使用接口時,你的類就可以同時擁有多個不同的行為。
在一些對時間要求比較高的應用中,傾向于使用抽象類,它會比接口稍快一點。
如果希望把一系列行為都規(guī)范在類繼承層次內(nèi),并且可以更好地在同一個地方進行編碼,那么抽象類是一個更好的選擇。
有時,接口和抽象類可以一起使用,接口中定義函數(shù),而在抽象類中定義默認的實現(xiàn)。
33、實例化對象有哪幾種方式
- new
- clone()
- 通過反射機制創(chuàng)建
//用 Class.forName方法獲取類,在調(diào)用類的newinstance()方法 Class<?> cls = Class.forName("com.dao.User"); User u = (User)cls.newInstance();
- 序列化反序列化
//將一個對象實例化后,進行序列化,再反序列化,也可以獲得一個對象(遠程通信的場景下使用) ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream("D:/data.txt")); //序列化對象 out.writeObject(user1); out.close(); //反序列化對象 ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/data.txt")); User user2 = (User) in.readObject(); System.out.println("反序列化user:" + user2); in.close();
34、byte類型127+1等于多少
byte的范圍是-128~127。
字節(jié)長度為8位,最左邊的是符號位,而127的二進制為01111111,所以執(zhí)行+1操作時,01111111變?yōu)?0000000。
大家知道,計算機中存儲負數(shù),存的是補碼的興衰。左邊第一位為符號位。
那么負數(shù)的補碼轉(zhuǎn)換成十進制如下:
一個數(shù)如果為正,則它的原碼、反碼、補碼相同;一個正數(shù)的補碼,將其轉(zhuǎn)化為十進制,可以直接轉(zhuǎn)換。
已知一個負數(shù)的補碼,將其轉(zhuǎn)換為十進制數(shù),步驟如下:
- 先對各位取反;
- 將其轉(zhuǎn)換為十進制數(shù);
- 加上負號,再減去1;
例如10000000,最高位是1,是負數(shù),①對各位取反得01111111,轉(zhuǎn)換為十進制就是127,加上負號得-127,再減去1得-128;
35、Java 容器都有哪些?
(1)Collection
① set
HashSet、TreeSet
② list
ArrayList、LinkedList、Vector
(2)Map
HashMap、HashTable、TreeMap
36、Collection 和 Collections 有什么區(qū)別?
(1)Collection是最基本的集合接口,Collection派生了兩個子接口list和set,分別定義了兩種不同的存儲方式。
(2)Collections是一個包裝類,它包含各種有關(guān)集合操作的靜態(tài)方法(對集合的搜索、排序、線程安全化等)。
此類不能實例化,就像一個工具類,服務(wù)于Collection框架。
37、list與Set區(qū)別
(1)List簡介
實際上有兩種List:
一種是基本的ArrayList,其優(yōu)點在于隨機訪問元素,另一種是LinkedList,它并不是為快速隨機訪問設(shè)計的,而是快速的插入或刪除。
ArrayList:由數(shù)組實現(xiàn)的List。允許對元素進行快速隨機訪問,但是向List中間插入與移除元素的速度很慢。
LinkedList :對順序訪問進行了優(yōu)化,向List中間插入與刪除的開銷并不大。隨機訪問則相對較慢。
還具有下列方 法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 這些方法 (沒有在任何接口或基類中定義過)
使得LinkedList可以當作堆棧、隊列和雙向隊列使用。
(2)Set簡介
Set具有與Collection完全一樣的接口,因此沒有任何額外的功能。
實際上Set就是Collection,只是行為不同。
這是繼承與多態(tài)思想的典型應用:表現(xiàn)不同的行為。
Set不保存重復的元素(至于如何判斷元素相同則較為負責)
Set : 存入Set的每個元素都必須是唯一的,因為Set不保存重復元素。
加入Set的元素必須定義equals()方法以確保對象的唯一性。
Set與Collection有完全一樣的接口。Set接口不保證維護元素的次序。
HashSet:為快速查找設(shè)計的Set。存入HashSet的對象必須定義hashCode()。
TreeSet: 保存次序的Set, 底層為樹結(jié)構(gòu)。使用它可以從Set中提取有序的序列。
(3)list與Set區(qū)別
① List,Set都是繼承自Collection接口
② List特點:元素有放入順序,元素可重復
Set特點:元素無放入順序,元素不可重復,重復元素會覆蓋掉,(元素雖然無放入順序,但是元素在set中的位置是有該元素的HashCode決定的,其位置其實是固定的,加入Set 的Object必須定義equals()方法 ,另外list支持for循環(huán),也就是通過下標來遍歷,也可以用迭代器,但是set只能用迭代,因為他無序,無法用下標來取得想要的值。)
③ Set和List對比:
Set:檢索元素效率低下,刪除和插入效率高,插入和刪除不會引起元素位置改變。
List:和數(shù)組類似,List可以動態(tài)增長,查找元素效率高,插入刪除元素效率低,因為會引起其他元素位置改變。
38、HashMap 和 Hashtable 有什么區(qū)別?
- HashMap是線程不安全的,HashTable是線程安全的;
- HashMap中允許鍵和值為null,HashTable不允許;
- HashMap的默認容器是16,為2倍擴容,HashTable默認是11,為2倍+1擴容;
39、說一下 HashMap 的實現(xiàn)原理?
(1)簡介
HashMap基于map接口,元素以鍵值對方式存儲,允許有null值,HashMap是線程不安全的。
(2)基本屬性
初始化大小,默認16,2倍擴容;
負載因子0.75;
初始化的默認數(shù)組;
size
threshold。判斷是否需要調(diào)整hashmap容量
(3)HashMap的存儲結(jié)構(gòu)
JDK1.7中采用數(shù)組+鏈表的存儲形式。
HashMap采取Entry數(shù)組來存儲key-value,每一個鍵值對組成了一個Entry實體,Entry類時機上是一個單向的鏈表結(jié)構(gòu)
它具有next指針,指向下一個Entry實體,以此來解決Hash沖突的問題。
HashMap實現(xiàn)一個內(nèi)部類Entry,重要的屬性有hash、key、value、next。
JDK1.8中采用數(shù)據(jù)+鏈表+紅黑樹的存儲形式。當鏈表長度超過閾值(8)時,將鏈表轉(zhuǎn)換為紅黑樹。在性能上進一步得到提升。
40、set有哪些實現(xiàn)類?
(1)HashSet
HashSet是set接口的實現(xiàn)類,set下面最主要的實現(xiàn)類就是HashSet(也就是用的最多的),此外還有LinkedHashSet和TreeSet。
HashSet是無序的、不可重復的。通過對象的hashCode和equals方法保證對象的唯一性。
HashSet內(nèi)部的存儲結(jié)構(gòu)是哈希表,是線程不安全的。
(2)TreeSet
TreeSet對元素進行排序的方式:
元素自身具備比較功能,需要實現(xiàn)Comparable接口,并覆蓋compareTo方法。
元素自身不具備比較功能,需要實現(xiàn)Comparator接口,并覆蓋compare方法。
(3)LinkedHashSet
LinkedHashSet是一種有序的Set集合,即其元素的存入和輸出的順序是相同的。
41、說一下 HashSet 的實現(xiàn)原理?
HashSet實際上是一個HashMap實例,數(shù)據(jù)存儲結(jié)構(gòu)都是數(shù)組+鏈表。
HashSet是基于HashMap實現(xiàn)的,HashSet中的元素都存放在HashMap的key上面,而value都是一個統(tǒng)一的對象PRESENT。
private static final Object PRESENT = new Object();
HashSet中add方法調(diào)用的是底層HashMap中的put方法,put方法要判斷插入值是否存在
而HashSet的add方法,首先判斷元素是否存在,如果存在則插入,如果不存在則不插入,這樣就保證了HashSet中不存在重復值。
通過對象的hashCode和equals方法保證對象的唯一性。
42、ArrayList 和 LinkedList 的區(qū)別是什么?
ArrayList是動態(tài)數(shù)組的數(shù)據(jù)結(jié)構(gòu)實現(xiàn),查找和遍歷的效率較高;
LinkedList 是雙向鏈表的數(shù)據(jù)結(jié)構(gòu),增加和刪除的效率較高;
43、如何實現(xiàn)數(shù)組和 List 之間的轉(zhuǎn)換?
String[] arr = {"zs","ls","ww"}; List<String> list = Arrays.asList(arr); System.out.println(list); ArrayList<String> list1 = new ArrayList<String>(); list1.add("張三"); list1.add("李四"); list1.add("王五"); String[] arr1 = list1.toArray(new String[list1.size()]); System.out.println(arr1); for(int i = 0; i < arr1.length; i++){ System.out.println(arr1[i]); }
44、在 Queue 中 poll()和 remove()有什么區(qū)別?
(1)offer()和add()區(qū)別:
增加新項時,如果隊列滿了,add會拋出異常,offer返回false。
(2)poll()和remove()區(qū)別:
poll()和remove()都是從隊列中刪除第一個元素,remove拋出異常,poll返回null。
(3)peek()和element()區(qū)別:
peek()和element()用于查詢隊列頭部元素,為空時element拋出異常,peek返回null。
45、哪些集合類是線程安全的
Vector:就比Arraylist多了個同步化機制(線程安全)。
Stack:棧,也是線程安全的,繼承于Vector。
Hashtable:就比Hashmap多了個線程安全。
ConcurrentHashMap:是一種高效但是線程安全的集合。
46、迭代器 Iterator 是什么?
為了方便的處理集合中的元素,Java中出現(xiàn)了一個對象,該對象提供了一些方法專門處理集合中的元素.例如刪除和獲取集合中的元素.該對象就叫做迭代器(Iterator)。
47、Iterator 怎么使用?有什么特點?
Iterator 接口源碼中的方法:
- java.lang.Iterable 接口被 java.util.Collection 接口繼承,java.util.Collection 接口的 iterator() 方法返回一個 Iterator 對象
- next() 方法獲得集合中的下一個元素
- hasNext() 檢查集合中是否還有元素
- remove() 方法將迭代器新返回的元素刪除
48、Iterator 和 ListIterator 有什么區(qū)別?
(1)ListIterator 繼承 Iterator
(2)ListIterator 比 Iterator多方法
- add(E e) 將指定的元素插入列表,插入位置為迭代器當前位置之前
- set(E e) 迭代器返回的最后一個元素替換參數(shù)
- ehasPrevious() 迭代器當前位置,反向遍歷集合是否含有元素
- previous() 迭代器當前位置,反向遍歷集合,下一個元素
- previousIndex() 迭代器當前位置,反向遍歷集合,返回下一個元素的下標
- nextIndex() 迭代器當前位置,返回下一個元素的下標
(3)使用范圍不同,Iterator可以迭代所有集合;ListIterator 只能用于List及其子類
- ListIterator 有 add 方法,可以向 List 中添加對象;
- Iterator 不能ListIterator 有 hasPrevious() 和 previous() 方法,可以實現(xiàn)逆向遍歷;
- Iterator不可以ListIterator 有 nextIndex() 和previousIndex() 方法,可定位當前索引的位置;
- Iterator不可以ListIterator 有 set()方法,可以實現(xiàn)對 List 的修改;Iterator 僅能遍歷,不能修改。
49、怎么確保一個集合不能被修改?
我們很容易想到用final關(guān)鍵字進行修飾,我們都知道
final關(guān)鍵字可以修飾類,方法,成員變量,final修飾的類不能被繼承,final修飾的方法不能被重寫,final修飾的成員變量必須初始化值
如果這個成員變量是基本數(shù)據(jù)類型,表示這個變量的值是不可改變的
如果說這個成員變量是引用類型,則表示這個引用的地址值是不能改變的,但是這個引用所指向的對象里面的內(nèi)容還是可以改變的。
那么,我們怎么確保一個集合不能被修改?首先我們要清楚,集合(map,set,list…)都是引用類型,所以我們?nèi)绻胒inal修飾的話,集合里面的內(nèi)容還是可以修改的。
我們可以做一個實驗:
可以看到:我們用final關(guān)鍵字定義了一個map集合,這時候我們往集合里面?zhèn)髦?,第一個鍵值對1,1;我們再修改后,可以把鍵為1的值改為100,說明我們是可以修改map集合的值的。
那我們應該怎么做才能確保集合不被修改呢?
我們可以采用Collections包下的unmodifiableMap方法,通過這個方法返回的map,是不可以修改的。
他會報 java.lang.UnsupportedOperationException錯。
同理:Collections包也提供了對list和set集合的方法。
Collections.unmodifiableList(List)
Collections.unmodifiableSet(Set)
50、隊列和棧是什么?有什么區(qū)別?
(1)隊列先進先出,棧先進后出。
(2)遍歷數(shù)據(jù)速度不同。
棧只能從頭部取數(shù)據(jù) 也就最先放入的需要遍歷整個棧最后才能取出來,而且在遍歷數(shù)據(jù)的時候還得為數(shù)據(jù)開辟臨時空間,保持數(shù)據(jù)在遍歷前的一致性;
隊列則不同,他基于地址指針進行遍歷,而且可以從頭或尾部開始遍歷,但不能同時遍歷,無需開辟臨時空間,因為在遍歷的過程中不影像數(shù)據(jù)結(jié)構(gòu),速度要快的多。
51、Java8開始ConcurrentHashMap,為什么舍棄分段鎖?
ConcurrentHashMap的原理是引用了內(nèi)部的 Segment ( ReentrantLock ) 分段鎖,保證在操作不同段 map 的時候, 可以并發(fā)執(zhí)行, 操作同段 map 的時候,進行鎖的競爭和等待。
從而達到線程安全, 且效率大于 synchronized。
但是在 Java 8 之后, JDK 卻棄用了這個策略,重新使用了 synchronized+CAS。
棄用原因
通過 JDK 的源碼和官方文檔看來, 他們認為的棄用分段鎖的原因由以下幾點:
加入多個分段鎖浪費內(nèi)存空間。
生產(chǎn)環(huán)境中, map 在放入時競爭同一個鎖的概率非常小,分段鎖反而會造成更新等操作的長時間等待。
為了提高 GC 的效率
新的同步方案
既然棄用了分段鎖, 那么一定由新的線程安全方案, 我們來看看源碼是怎么解決線程安全的呢?(源碼保留了segment 代碼, 但并沒有使用)。
52、ConcurrentHashMap(JDK1.8)為什么要使用synchronized而不是如ReentranLock這樣的可重入鎖?
我想從下面幾個角度討論這個問題:
(1)鎖的粒度
首先鎖的粒度并沒有變粗,甚至變得更細了。每當擴容一次,ConcurrentHashMap的并發(fā)度就擴大一倍。
(2)Hash沖突
JDK1.7中,ConcurrentHashMap從過二次hash的方式(Segment -> HashEntry)能夠快速的找到查找的元素。
在1.8中通過鏈表加紅黑樹的形式彌補了put、get時的性能差距。
JDK1.8中,在ConcurrentHashmap進行擴容時,其他線程可以通過檢測數(shù)組中的節(jié)點決定是否對這條鏈表(紅黑樹)進行擴容,減小了擴容的粒度,提高了擴容的效率。
下面是我對面試中的那個問題的一下看法。
為什么是synchronized,而不是ReentranLock
(1)減少內(nèi)存開銷
假設(shè)使用可重入鎖來獲得同步支持,那么每個節(jié)點都需要通過繼承AQS來獲得同步支持。
但并不是每個節(jié)點都需要獲得同步支持的,只有鏈表的頭節(jié)點(紅黑樹的根節(jié)點)需要同步,這無疑帶來了巨大內(nèi)存浪費。
(2)獲得JVM的支持
可重入鎖畢竟是API這個級別的,后續(xù)的性能優(yōu)化空間很小。
synchronized則是JVM直接支持的,JVM能夠在運行時作出相應的優(yōu)化措施:鎖粗化、鎖消除、鎖自旋等等。
這就使得synchronized能夠隨著JDK版本的升級而不改動代碼的前提下獲得性能上的提升。
到此這篇關(guān)于Java經(jīng)典面試題最全匯總208道(一)的文章就介紹到這了,更多相關(guān)Java面試題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決OkHttp接收gzip壓縮數(shù)據(jù)返回亂碼問題
這篇文章主要介紹了解決OkHttp接收gzip壓縮數(shù)據(jù)返回亂碼問題,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06SpringBoot實現(xiàn)熱部署Community的示例代碼
本文主要介紹了SpringBoot實現(xiàn)熱部署Community的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-06-06如何使用SpringBootCondition更自由地定義條件化配置
這篇文章主要介紹了如何使用SpringBootCondition更自由地定義條件化配置,幫助大家更好的理解和學習使用springboot框架,感興趣的朋友可以了解下2021-04-04spring-boot報錯java: 程序包javax.servlet.http不存在
當springboot項目從2.7.x的升級到3.0.x的時候,會遇到一個問題java: 程序包javax.servlet.http不存在,下面就來具體介紹一下,感興趣的可以了解一下2024-08-08