注:都是在百度搜索整理的答案,如有侵權(quán)和錯誤,希告知更改。
一、哪些情況下的對象會被垃圾回收機制處理掉
?當對象對當前使用這個對象的應(yīng)用程序變得不可觸及(不可達)的時候,這個對象就可以被回收了。
java垃圾回收是有jvm自動執(zhí)行的,不是人為操作的,所以當不存在對某對象的任何引用時,該對象就處于被jvm回收的狀態(tài),并不是馬上予以銷毀。
可達性(可達性算法(GC Roots Tracing):從GC Roots作為起點開始搜索,那么整個連通圖中的對象便都是活對象,對于GC Roots無法到達的對象便成了垃圾回收的對象,隨時可被GC回收。)
從強到弱,不同級別的可達性反應(yīng)對象的生命周期,定義如下:
如果一個對象可以被一些線程直接使用而不用通過其他引用對象,那么它就是強可達。一個新創(chuàng)建的對象對創(chuàng)建它的線程來講就是強可達的。
如果一個對象沒有強可達性,但是它可以通過一個軟引用(soft reference.)來使用,那么它就具有軟可達性。
如果一個對象既沒有強可達性,也沒有軟可達性,但是它可以通過一個弱引用(weak reference)來使用,那么他就具有弱可達性。當弱引用指向的弱可達對象沒有其他的引用,那么這個對象就會被回收。
如果一個對象既沒有強可達性,也沒有軟可達性、弱可達性,他已經(jīng)被finalized,并且有一些虛引用(phantom reference)指向它,那么它就具有虛可達性。
當一個對象不能通過以上的方式指向,那么這個對象就變得不可達,并因此適合被回收。
強可達(Strongly reachable)
被new出來的對象都是強可達的,他們的引用就是強引用。任何通過強引用所使用的對象都不會被GC回收。
軟可達(Softly reachable)
只有當系統(tǒng)需要更多內(nèi)存時,GC才會回收具有軟可達性的對象。在內(nèi)存不足前,GC保證一定回收軟可達的對象。
關(guān)于軟引用(SoftReference)何時應(yīng)該被回收的算法依賴于不同的JVM發(fā)行版本。它往往是一個跟引用(reference)的使用頻率和使用間隔有關(guān)的函數(shù)。
弱可達(Weakly reachable
它也是用來描述非需對象的,但它的強度比軟引用更弱些,被弱引用關(guān)聯(lián)的對象只能生存島下一次垃圾收集發(fā)生之前。當垃圾收集器工作時,無論當前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象。
虛可達(Phantom reachable)
最弱的一種引用關(guān)系,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用來取得一個對象實例。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的是希望能在這個對象被收集器回收時收到一個系統(tǒng)通知。
二、講一下常見編碼方式?
ASCII 碼
學(xué)過計算機的人都知道 ASCII 碼,總共有 128 個,用一個字節(jié)的低 7 位表示,0~31 是控制字符如換行回車刪除等;32~126 是打印字符,可以通過鍵盤輸入并且能夠顯示出來。
ISO-8859-1
128 個字符顯然是不夠用的,于是 ISO 組織在 ASCII 碼基礎(chǔ)上又制定了一些列標準用來擴展 ASCII 編碼,它們是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵蓋了大多數(shù)西歐語言字符,所有應(yīng)用的最廣泛。ISO-8859-1 仍然是單字節(jié)編碼,它總共能表示 256 個字符。
GBK
全稱叫《漢字內(nèi)碼擴展規(guī)范》,是國家技術(shù)監(jiān)督局為 windows95 所制定的新的漢字內(nèi)碼規(guī)范,它的出現(xiàn)是為了擴展 GB2312,加入更多的漢字,它的編碼范圍是 8140~FEFE(去掉 XX7F)總共有 23940 個碼位,它能表示 21003 個漢字,它的編碼是和 GB2312 兼容的,也就是說用 GB2312 編碼的漢字可以用 GBK 來解碼,并且不會有亂碼。
UTF-16
說到 UTF 必須要提到 Unicode(Universal Code 統(tǒng)一碼),ISO 試圖想創(chuàng)建一個全新的超語言字典,世界上所有的語言都可以通過這本字典來相互翻譯??上攵@個字典是多么的復(fù)雜,關(guān)于 Unicode 的詳細規(guī)范可以參考相應(yīng)文檔。Unicode 是 Java 和 XML 的基礎(chǔ),下面詳細介紹 Unicode 在計算機中的存儲形式。
UTF-8
UTF-16 統(tǒng)一采用兩個字節(jié)表示一個字符,雖然在表示上非常簡單方便,但是也有其缺點,有很大一部分字符用一個字節(jié)就可以表示的現(xiàn)在要兩個字節(jié)表示,存儲空間放大了一倍,在現(xiàn)在的網(wǎng)絡(luò)帶寬還非常有限的今天,這樣會增大網(wǎng)絡(luò)傳輸?shù)牧髁?,而且也沒必要。而 UTF-8 采用了一種變長技術(shù),每個編碼區(qū)域有不同的字碼長度。不同類型的字符可以是由 1~6 個字節(jié)組成。
三、utf-8編碼中的中文占幾個字節(jié);int型幾個字節(jié)?
一般是3個字節(jié),int為四個
四、靜態(tài)代理和動態(tài)代理的區(qū)別,什么場景使用?
代理模式的定義:給某一個對象提供一個代理,并由代理對象控制對原對象的引用。
代理模式包含如下角色:
ISubject:抽象主題角色,是一個接口。該接口是對象和它的代理共用的接口。
RealSubject:真實主題角色,是實現(xiàn)抽象主題接口的類。
Proxy:代理角色,內(nèi)部含有對真實對象RealSubject的引用,從而可以操作真實對象。代理對象提供與真實對象相同的接口,以便在任何時刻都能代替真實對象。同時,代理對象可以在執(zhí)行真實對象操作時,附加其他的操作,相當于對真實對象進行封裝。
靜態(tài)代理類:由程序員創(chuàng)建或由特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經(jīng)存在了。
動態(tài)代理類:在程序運行時,運用反射機制動態(tài)創(chuàng)建而成。
靜態(tài)代理通常只代理一個類,動態(tài)代理是代理一個接口下的多個實現(xiàn)類。
靜態(tài)代理事先知道要代理的是什么,而動態(tài)代理不知道要代理什么東西,只有在運行時才知道。
動態(tài)代理是實現(xiàn)JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的業(yè)務(wù)類必須要實現(xiàn)接口,通過Proxy里的newProxyInstance得到代理對象。
一個關(guān)于靜態(tài)代理的例子:
警匪片大家一定都不會陌生,一些有錢的人看那個不順眼,就想著找黑幫的幫忙殺人,黑幫就幫他們做一些壞事。這里的老板就變成了RealSubject,黑幫就變成了(Proxy),這里的real和proxy只是針對殺人是誰指使的(即幕后黑手是那個)
首先定義一個共同的接口,使得RealSubject出現(xiàn)的地方Proxy都可以出現(xiàn)
/*
* 抽象接口,對應(yīng)類圖中的Subject
*/
public interface Subject {
public void SujectShow();
}
然后定義一個RealSubject,真正的幕后黑手
public class RealSubject implements Subject {
@Override
public void SujectShow() {
// TODO Auto-generated method stub
System.out.println("殺人是我指使的,我是幕后黑手!By---" + getClass());
}
}
然后定義一個代理類,黑幫,拿錢辦事,但不是幕后黑手
public class ProxySubject implements Subject {
private Subject realSubject;//代理類中有 老板的引用。
public Subject TakeCall() //通過電話聯(lián)系
{
return new RealSubject();
}
public void Before() {
System.out.println("我只是一個代理類,在做事情之前我先聲明,接下來的事情跟我無關(guān),我只是受人指使!By---" + getClass());
}
public void After() {
System.out.println("正如事情還沒有發(fā)生之前講的一樣,我只是個路人,上面做的事情跟我無關(guān),我是受人指使的! By---" + getClass());
}
@Override
public void SujectShow() {
// TODO Auto-generated method stub
Object o = TakeCall();//代理類接到了一個電話
if (checked(o)) //檢查這個電話是不是老板打過來的
{
Before();
this.realSubject = (Subject) o;
realSubject.SujectShow();
After();
} else {
System.out.println("不好意思,你權(quán)限不夠,我?guī)筒涣四悖?);
}
}
boolean checked(Object o) //權(quán)限檢查,這年頭不是誰都可以冒充老板的
{
if (o instanceof RealSubject)
return true;
return false;
}
}
測試
public class ProxyTest {
public static void main(String[] args) {
ProxySubject proxy = new ProxySubject();
proxy.SujectShow();
}
}
執(zhí)行結(jié)果:
我只是一個代理類,在做事情之前我先聲明,接下來的事情跟我無關(guān),我只是受人指使!By---class ProxyMode.ProxySubject
殺人是我指使的,我是幕后黑手!By---class ProxyMode.RealSubject
正如事情還沒有發(fā)生之前講的一樣,我只是個路人,上面做的事情跟我無關(guān),我是受人指使的! By---class ProxyMode.ProxySubject
動態(tài)代理實現(xiàn)過程
具體有如下四步驟: 通過實現(xiàn) InvocationHandler 接口創(chuàng)建自己的調(diào)用處理器;通過為 Proxy 類指定 ClassLoader 對象和一組 interface 來創(chuàng)建動態(tài)代理類;通過反射機制獲得動態(tài)代理類的構(gòu)造函數(shù),其唯一參數(shù)類型是調(diào)用處理器接口類型;通過構(gòu)造函數(shù)創(chuàng)建動態(tài)代理類實例,構(gòu)造時調(diào)用處理器對象作為參數(shù)被傳入。
Subjec:
/*
* 抽象接口,對應(yīng)類圖中的Subject
*/
public interface Subject {
public void SujectShow();
}
RealSubject :
public class RealSubject implements Subject {
@Override
public void SujectShow() {
// TODO Auto-generated method stub
System.out.println("殺人是我指使的,我是幕后黑手!By---" + getClass());
}
}
建立InvocationHandler用來響應(yīng)代理的任何調(diào)用
public class ProxyHandler implements InvocationHandler {
private Object proxied;
public ProxyHandler(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("準備工作之前:");
//轉(zhuǎn)調(diào)具體目標對象的方法
Object object = method.invoke(proxied, args);
System.out.println("工作已經(jīng)做完了!");
return object;
}
}
動態(tài)代理類測試,這個代理類中再也不用實現(xiàn)Subject接口,可以動態(tài)的獲得RealSubject接口中的方法
public class DynamicProxy {
public static void main(String args[]) {
RealSubject real = new RealSubject();
Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
new Class[]{Subject.class},
new ProxyHandler(real));
proxySubject.SujectShow();
;
}
}
測試結(jié)果
準備工作之前:
殺人是我指使的,我是幕后黑手!By---class ProxyMode.RealSubject
工作已經(jīng)做完了!
五、Java的異常體系
Java把異常當作對象來處理,并定義一個基類java.lang.Throwable作為所有異常的超類。 在Java API中已經(jīng)定義了許多異常類,這些異常類分為兩大類,錯誤Error和異常Exception。Java異常體系結(jié)構(gòu)呈樹狀,其層次結(jié)構(gòu)圖如圖

Thorwable類所有異常和錯誤的超類,有兩個子類Error和Exception,分別表示錯誤和異常。其中異常類Exception又分為運行時異常(RuntimeException)和非運行時異常,這兩種異常有很大的區(qū)別,也稱之為不檢查異常(Unchecked Exception)和檢查異常(Checked Exception)。
|——Throwable 實現(xiàn)類描述java的錯誤和異常 一般交由硬件處理
?|——Error(錯誤)一般不通過代碼去處理,一般由硬件保護
?|——Exception(異常)
|——RuntimeException(運行時異常)
|——非運行時異常
?多個try-catch語句聯(lián)用時的順序
1、順序執(zhí)行,從上到下,有一個catch子句匹配之后,后面的自動不在執(zhí)行
2、如果多個cach內(nèi)的異常有父子類的關(guān)系
一定要,子類異常在上,父類異常在下
自定義異常類型
一般都是提供兩個構(gòu)造參數(shù),一個無參一個有參數(shù),有參數(shù)的一般是調(diào)用父類的有參構(gòu)造函數(shù),調(diào)用形式super(message)
運行時異常
RuntimeException
|——ClassCastException多態(tài)中可以使用instanceof 進行規(guī)避
|——ArithmeticException進行if判斷,吐過除數(shù)為0進行return
|——NullPointerException進行if判斷是否為null
|——ArrayIndexOutBondsExcetion使用數(shù)組length屬性以避免數(shù)組越界。
在后面我們異常處理的時候,經(jīng)常把捕獲的一場裝華為運行時異常拋出,尤其是寫一些函數(shù)框架時。throw new RuntimeException(e);
非運行時異(受檢異常) 這些異常必須做出try-catch不然編譯器無法通過注意事項
1、子類覆蓋父類的方法時,父類方法拋出異常,子類的覆蓋方法可以不拋出異?;蛘邟伋龈割惙椒ㄏ嗤漠惓#蛘邟伋龈割惙椒ó惓5淖宇悺?br>
2、父類方法拋出了多個異常,子類覆蓋方法時,只能拋出父類異常的子集
3、父類沒有拋出異常,子類不能拋出異常。子類發(fā)生非運行時異常時,需要進行try-catch處理
4、子類不能比父類拋出更多的異常。
凡事應(yīng)當向父類看齊,父類已有就應(yīng)當向分類看齊。
finally塊一般用于釋放資源 無論程序正常與否都執(zhí)行finally塊
1.只有一種情況,jvm退出了System.exit(0)這時候不會執(zhí)行finally的內(nèi)容
2、return語句也無法阻止finally的執(zhí)行
throw關(guān)鍵字是用于方法體內(nèi)部,用來拋出一個Throwable類型的異常。如果拋出了檢查異常,則還應(yīng)該在方法頭部聲明方法可能拋出的異常類型。該方法的調(diào)用者也必須檢查處理拋出的異常。如果所有方法都層層上拋獲取的異常,最終JVM會進行處理,處理也很簡單,就是打印異常消息和堆棧信息
六、談?wù)勀銓馕雠c分派的認識。
例如,對于People p = new Man();這句代碼,我們把People叫做靜態(tài)類型,Man叫做動態(tài)類型。靜態(tài)類型在編譯期可知,實際類型在運行期才可以確定下來。
解析
所有方法調(diào)用中的目標方法在Class文件里面都是一個常量池中的符號引用,在類加載的解析階段,會將其中一部分符號引用轉(zhuǎn)化為直接引用,這種解析能成立的前提是:方法在程序真正運行之前就有一個可確定的調(diào)用版本,并且運行期不可變。
java虛擬機中提供了5條方法調(diào)用字節(jié)碼指令,分別如下:
- invokestatic:調(diào)用靜態(tài)方法
- invokespecial:調(diào)用實力構(gòu)造器方法,私有方法和父類方法
- invokevirtual:調(diào)用所有的虛方法
- invokeinterface:調(diào)用接口方法,會在運行時再確定一個實現(xiàn)此接口的對象
- invokedynamic:先在運行時動態(tài)解析出調(diào)用點限定符所引用的方法,然后再執(zhí)行該方法,在此之前的四條調(diào)用指令,分派邏輯是固化在java虛擬機內(nèi)部的,而invokedynamic指令的分派邏輯是由用戶所設(shè)定的引導(dǎo)方法決定的
只要能被invokestatic和invokespecial指令調(diào)用的方法,都可以在解析階段確定唯一的調(diào)用版本,符合這個條件的靜態(tài)方法,私有方法,實例構(gòu)造器,父類方法四類,它們在類加載的時候就會把符號引用解析為該方法的直接引用。這些方法可以稱為非虛方法,與之相反,其他方法稱為虛方法(除去final方法)。final雖然使用invokevirtual調(diào)用,但是仍然是非虛方法。非虛方法是不可以被重寫的。
解析調(diào)用一定是個靜態(tài)的過程,在編譯期間就完全確定。而分派調(diào)用可能是靜態(tài)可能是動態(tài)的。
分派
1、靜態(tài)分派
靜態(tài)分派的典型應(yīng)用是重載,重載根據(jù)參數(shù)的靜態(tài)類型而不是實際類型作為判定依據(jù)。而靜態(tài)類型在編譯期可知,所以靜態(tài)分派發(fā)生在編譯階段。
另外雖然編譯器能確定方法的重載版本,但是在很多情況下重載版本不唯一,往往只能確定一個更加合適的版本。主要原因是字面量作為參數(shù)傳入是沒有顯式的靜態(tài)類型的。只能選擇一個最貼近該字面型類型的方法。實際工作中要避免出現(xiàn)。
關(guān)于分派與解析的關(guān)系:并不是排他關(guān)系,而是在不同層次上去篩選,確定目標方法的過程。前面說過,靜態(tài)方法會在類加載時就進行解析,而靜態(tài)方法顯然也是可以擁有重載版本的,選擇重載版本也是通過靜態(tài)分派來完成的。
2、動態(tài)分派
動態(tài)分派的典型應(yīng)用是重寫,運行期根據(jù)方法接收者的實際類型來選擇方法。invokevirtual指令的工作過程是,優(yōu)先尋找當前類中是否有該方法,如有直接選擇該方法,若沒有找到,則在父類中尋找,直到找到為止。
七、修改對象A的equals方法的簽名,那么使用HashMap存放這個對象實例的時候,會調(diào)用哪個equals方法?
修改后的equal:
陷阱1:定義錯誤equals方法簽名(signature)
考慮為下面這個簡單類Point增加一個等價性方法:
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
看上去非常明顯,但是按照這種方式來定義equals就是錯誤的。
// An utterly wrong definition of equals
public boolean equals(Point other){
return(this.getX()==other.getX()&&this.getY()==other.getY());
}
這個方法有什么問題呢?初看起來,它工作的非常完美:
Point p1=new Point(1,2);
Point p2=new Point(1,2);
Point q=new Point(2,3);
System.out.println(p1.equals(p2)); // prints true
System.out.println(p1.equals(q)); // prints false
然而,當我們一旦把這個Point類的實例放入到一個容器中問題就出現(xiàn)了:
HashSet coll = new HashSet();
coll.add(p1);
System.out.println(coll.contains(p2)); // prints false
為什么coll中沒有包含p2呢?甚至是p1也被加到集合里面,p1和p2是是等價的對象嗎?在下面的程序中,我們可以找到其中的一些原因,定義p2a是一個指向p2的對象,但是p2a的類型是Object而非Point類型:
Object p2a = p2;現(xiàn)在我們重復(fù)第一個比較,但是不再使用p2而是p2a,我們將會得到如下的結(jié)果:
System.out.println(p1.equals(p2a)); // prints false
到底是那里出了了問題?事實上,之前所給出的equals版本并沒有覆蓋Object類的equals方法,因為他的類型不同。下面是Object的equals方法的定義
public boolean equals(Object other)
因為Point類中的equals方法使用的是以Point類而非Object類做為參數(shù),因此它并沒有覆蓋Object中的equals方法。而是一種變化了的重載。在Java中重載被解析為靜態(tài)的參數(shù)類型而非運行期的類型,因此當靜態(tài)參數(shù)類型是Point,Point的equals方法就被調(diào)用。然而當靜態(tài)參數(shù)類型是Object時,Object類的equals就被調(diào)用。因為這個方法并沒有被覆蓋,因此它仍然是實現(xiàn)成比較對象標示。這就是為什么雖然p1和p2a具有同樣的x,y值,p1.equals(p2a)仍然返回了false。這也是會什么HashSet的contains方法返回false的原因,因為這個方法操作的是泛型,他調(diào)用的是一般化的Object上equals方法而非Point類上變化了的重載方法equals
八、Java中實現(xiàn)多態(tài)的機制是什么?
靠的是父類或接口定義的引用變量可以指向子類或具體實現(xiàn)類的實例對象,而程序調(diào)用的方法在運行期才動態(tài)綁定,就是引用變量所指向的具體實例對象的方法,也就是內(nèi)存里正在運行的那個對象的方法,而不是引用變量的類型中定義的方法。
多態(tài)可分為:
1.編譯多態(tài):主要是體現(xiàn)在重載,系統(tǒng)在編譯時就能確定調(diào)用重載函數(shù)的哪個版本。
2.運行多態(tài):主要體現(xiàn)在OO設(shè)計的繼承性上,子類的對象也是父類的對象,即上溯造型,所以子類對象可以作為父類對象使用,父類的對象變量可以指向子類對象。因此通過一個父類發(fā)出的方法調(diào)用可能執(zhí)行的是方法在父類中的實現(xiàn),也可能是某個子類中的實現(xiàn),它是由運行時刻具體的對象類型決定的。
方法的重寫Overriding和重載Overloading是Java多態(tài)性的不同表現(xiàn).
重寫是父類與子類之間多態(tài)性的一種表現(xiàn),重載是一類中多態(tài)性的表現(xiàn)
九、如何將一個Java對象序列化到文件里?
寫入序列化數(shù)據(jù)到文件中,主要是兩個對象,一個對象是FileOutputStream 對象,一個是ObjectOutputStream 對象,ObjectOutputStream 負責(zé)向指定的流中寫入序列化的對象。當從文件中讀取序列化數(shù)據(jù)時,主要需要兩個對象,一個是FileInputStream ,一個是ObjectInputStream 對象,ObjectInputStream 負責(zé)從指定流中讀取序列化數(shù)據(jù)并還原成序列化前得對象。另外,序列化的讀取數(shù)據(jù)與寫入的順序相同,比如我們序列化時先寫入數(shù)據(jù)A ,再寫入B ,最后寫入C ;那么我們再讀取數(shù)據(jù)的時候,讀取到的第一個數(shù)據(jù)為A ,讀取到的第二個數(shù)據(jù)為B ,最后讀取到的數(shù)據(jù)為C ,即:先寫入先讀取的原則。
在序列化一個對象的時候,這個對象必須實現(xiàn)java.io.Serializable 接口, Serializable 接口中不含任何方法,這個可以理解為聲明該對象是可以序列化的方法吧。當我們在序列化一個對象時,有些屬性我們不想序列化(可以減少數(shù)據(jù)量),那么我們可以聲明該屬性為瞬間態(tài)(用transient 關(guān)鍵字聲明)。另外,靜態(tài)字段也是不會被序列化的。
當我們在序列化一個對象時,如果該對象沒有覆寫writeObject 或者readObject 方法,那么系統(tǒng)將采用默認的方法進行序列化,如果序列化對象中有這兩個方法,則采用對象中的這兩個方法進行序列化。至于如何找到的這兩個方法,通過代碼我們可以跟蹤到是根據(jù)反射的方式進行判斷對象是否覆寫這兩個方法。
import java.io.*;
public class Cat implements Serializable {
private String name;
public Cat() {
this.name = "new cat";
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
Cat cat = new Cat();
try {
//FileOutputStream獲得一個文件流,ObjectOutputStream鏈接對象和文件流,
// 通過writeObject方法把對象寫到硬盤上面,從內(nèi)存中的對象寫到硬盤,故是OUT流
FileOutputStream fos = new FileOutputStream("catDemo.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
System.out.println(" 1> " + cat.getName());
cat.setName("My Cat");
oos.writeObject(cat);
oos.close();
} catch (Exception ex) {
ex.printStackTrace();
}
try {
//把硬盤文件中的對象寫到內(nèi)存,故是IN流
FileInputStream fis = new FileInputStream("catDemo.out");
ObjectInputStream ois = new ObjectInputStream(fis);
cat = (Cat) ois.readObject();
System.out.println(" 2> " + cat.getName());
ois.close();
} catch (Exception ex) {
ex.printStackTrace();
}
//writeObject和readObject本身就是線程安全的,傳輸過程中是不允許被并發(fā)訪問的。所以對象能一個一個接連不斷的傳過來
}
}
十、說說你對Java反射的理解?
JAVA反射機制是在運行狀態(tài)中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調(diào)用它的任意一個方法和屬性;這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機制。
反射就是把java類中的各種成分映射成一個個的Java對象
如圖是類的正常加載過程:反射的原理在與class對象。
熟悉一下加載的時候:Class對象的由來是將class文件讀入內(nèi)存,并為之創(chuàng)建一個Class對象

1、獲取Class對象的三種方式
1.1 Object ——> getClass()
1.2 任何數(shù)據(jù)類型(包括基本數(shù)據(jù)類型)都有一個“靜態(tài)”的class屬性
1.3 通過Class類的靜態(tài)方法:forName(String className)(常用)
public class Fanshe {
public static void main(String[] args) {
//第一種方式獲取Class對象
Student stu1 = new Student();//這一new 產(chǎn)生一個Student對象,一個Class對象。
Class stuClass = stu1.getClass();//獲取Class對象
System.out.println(stuClass.getName());
//第二種方式獲取Class對象
Class stuClass2 = Student.class;
System.out.println(stuClass == stuClass2);//判斷第一種方式獲取的Class對象和第二種方式獲取的是否是同一個
//第三種方式獲取Class對象
try {
Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必須是真實路徑,就是帶包名的類路徑,包名.類名
System.out.println(stuClass3 == stuClass2);//判斷三種方式是否獲取的是同一個Class對象
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
2、獲取所有構(gòu)造方法:getDeclaredConstructors();
3、獲取成員變量并調(diào)用:
*1.批量的
*1).Field[]getFields():獲取所有的"公有字段"
*2).Field[]getDeclaredFields():獲取所有字段,包括:私有、受保護、默認、公有;
*2.獲取單個的:
*1).public Field getField(String fieldName):獲取某個"公有的"字段;
*2).public Field getDeclaredField(String fieldName):獲取某個字段(可以是私有的)
*設(shè)置字段的值:
*Field-->public void set(Object obj,Object value):
*參數(shù)說明:
*1.obj:要設(shè)置的字段所在的對象;
*2.value:要為字段設(shè)置的值;
4、獲取成員方法并調(diào)用:
*1.批量的:
*public Method[]getMethods():獲取所有"公有方法";(包含了父類的方法也包含Object類)
*public Method[]getDeclaredMethods():獲取所有的成員方法,包括私有的(不包括繼承的)
*2.獲取單個的:
*public Method getMethod(String name,Class...parameterTypes):
*參數(shù):
*name:方法名;
*Class...:形參的Class類型對象
*public Method getDeclaredMethod(String name,Class...parameterTypes)
*
*調(diào)用方法:
*Method-->public Object invoke(Object obj,Object...args):
*參數(shù)說明:
*obj:要調(diào)用方法的對象;
*args:調(diào)用方式時所傳遞的實參;
十一、說說你對Java注解的理解?
注解:對程序代碼本身的描述-代碼元數(shù)據(jù),一種約定的規(guī)范,包括格式、意義、作用域等。
1、Annotation的工作原理:
JDK5.0中提供了注解的功能,允許開發(fā)者定義和使用自己的注解類型。該功能由一個定義注解類型的語法和描述一個注解聲明的語法,讀取注解的API,一個使用注解修飾的class文件和一個注解處理工具組成。
Annotation并不直接影響代碼的語義,但是他可以被看做是程序的工具或者類庫。它會反過來對正在運行的程序語義有所影響。
Annotation可以沖源文件、class文件或者在運行時通過反射機制多種方式被讀取。
2、@Override注解:
package java.lang;
import java.lang.annotation.*;
/**
* Indicates that a method declaration is intended to override a
* method declaration in a supertype. If a method is annotated with
* this annotation type compilers are required to generate an error
* message unless at least one of the following conditions hold:
*
* <ul><li>
* The method does override or implement a method declared in a
* supertype.
* </li><li>
* The method has a signature that is override-equivalent to that of
* any public method declared in {@linkplain Object}.
* </li></ul>
*
* @author Peter von der Ahé
* @author Joshua Bloch
* @jls 9.6.1.4 @Override
* @since 1.5
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
表示一個方法聲明打算重寫超類中的另一個方法聲明。如果方法利用此注釋類型進行注解但沒有重寫超類方法,則編譯器會生成一條錯誤消息。
@Override注解表示子類要重寫父類的對應(yīng)方法。
Override是一個Marker annotation,用于標識的Annotation,Annotation名稱本身表示了要給工具程序的信息。
下面是一個使用@Override注解的例子:
class A {
private String id;
A(String id){
this.id = id;
}
@Override
public String toString() {
return id;
}
}
3、@Deprecated注解:
package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
用 @Deprecated 注釋的程序元素,不鼓勵程序員使用這樣的元素,通常是因為它很危險或存在更好的選擇。在使用不被贊成的程序元素或在不被贊成的代碼中執(zhí)行重寫時,編譯器會發(fā)出警告。
@Deprecated注解表示方法是不被建議使用的。
Deprecated是一個Marker annotation。
下面是一個使用@Deprecated注解的例子:
class A {
private String id;
A(String id) {
this.id = id;
}
@Deprecated
public void execute() {
System.out.println(id);
}
public static void main(String[] args) {
A a = new A("a123");
a.execute();
}
}
4、@SuppressWarnings注解:
package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
指示應(yīng)該在注釋元素(以及包含在該注釋元素中的所有程序元素)中取消顯示指定的編譯器警告。注意,在給定元素中取消顯示的警告集是所有包含元素中取消顯示的警告的超集。例如,如果注釋一個類來取消顯示某個警告,同時注釋一個方法來取消顯示另一個警告,那么將在此方法中同時取消顯示這兩個警告。
根據(jù)風(fēng)格不同,程序員應(yīng)該始終在最里層的嵌套元素上使用此注釋,在那里使用才有效。如果要在特定的方法中取消顯示某個警告,則應(yīng)該注釋該方法而不是注釋它的類。
@SuppressWarnings注解表示抑制警告。
下面是一個使用@SuppressWarnings注解的例子:
@SuppressWarnings("unchecked")
public static void main(String[] args) {
List list = new ArrayList();
list.add("abc");
}
5、自定義注解:
使用@interface自定義注解時,自動繼承了java.lang.annotation.Annotation接口,由編譯程序自動完成其他細節(jié)。在定義注解時,不能繼承其他的注解或接口。
自定義最簡單的注解:
public @interface MyAnnotation {
}
使用自定義注解:
public class AnnotationTest2 {
@MyAnnotation
public void execute(){
System.out.println("method");
}
}
5.1、添加變量:
public @interface MyAnnotation {
String value1();
}
使用自定義注解:
public class AnnotationTest2 {
@MyAnnotation(value1="abc")
public void execute(){
System.out.println("method");
}
}
當注解中使用的屬性名為value時,對其賦值時可以不指定屬性的名稱而直接寫上屬性值接口;除了value意外的變量名都需要使用name=value的方式賦值。
5.2、添加默認值:
public @interface MyAnnotation {
String value1() default "abc";
}
5.3、多變量使用枚舉:
public @interface MyAnnotation {
String value1() default "abc";
MyEnum value2() default MyEnum.Sunny;
}
enum MyEnum{
Sunny,Rainy
}
使用自定義注解:
public class AnnotationTest2 {
@MyAnnotation(value1="a", value2=MyEnum.Sunny)
public void execute(){
System.out.println("method");
}
}
5.4、數(shù)組變量:
public @interface MyAnnotation {
String[] value1() default "abc";
}
使用自定義注解:
public class AnnotationTest2 {
@MyAnnotation(value1={"a","b"})
public void execute(){
System.out.println("method");
}
}
6、設(shè)置注解的作用范圍:
@Documented
@Retention(value=RUNTIME)
@Target(value=ANNOTATION_TYPE)
public @interface Retention
指示注釋類型的注釋要保留多久。如果注釋類型聲明中不存在 Retention 注釋,則保留策略默認為 RetentionPolicy.CLASS。
只有元注釋類型直接用于注釋時,Target 元注釋才有效。如果元注釋類型用作另一種注釋類型的成員,則無效。
public enum RetentionPolicy
extends Enum<RetentionPolicy>
注釋保留策略。此枚舉類型的常量描述保留注釋的不同策略。它們與 Retention 元注釋類型一起使用,以指定保留多長的注釋。
@Retention(RetentionPolicy.SOURCE) //注解僅存在于源碼中,在class字節(jié)碼文件中不包含
@Retention(RetentionPolicy.CLASS) // 默認的保留策略,注解會在class字節(jié)碼文件中存在,但運行時無法獲得,
@Retention(RetentionPolicy.RUNTIME) // 注解會在class字節(jié)碼文件中存在,在運行時可以通過反射獲取到
@Retention注解可以在定義注解時為編譯程序提供注解的保留策略。
屬于CLASS保留策略的注解有@SuppressWarnings,該注解信息不會存儲于.class文件。
6.1、在自定義注解中的使用例子:
@Retention(RetentionPolicy.CLASS)
public @interface MyAnnotation {
String[] value1() default "abc";
}
7、使用反射讀取RUNTIME保留策略的Annotation信息的例子:
java.lang.reflect
接口 AnnotatedElement
所有已知實現(xiàn)類:
AccessibleObject, Class, Constructor, Field, Method, Package
表示目前正在此 VM 中運行的程序的一個已注釋元素。該接口允許反射性地讀取注釋。由此接口中的方法返回的所有注釋都是不可變并且可序列化的。調(diào)用者可以修改已賦值數(shù)組枚舉成員的訪問器返回的數(shù)組;這不會對其他調(diào)用者返回的數(shù)組產(chǎn)生任何影響。
如果此接口中的方法返回的注釋(直接或間接地)包含一個已賦值的 Class 成員,該成員引用了一個在此 VM 中不可訪問的類,則試圖通過在返回的注釋上調(diào)用相關(guān)的類返回的方法來讀取該類,將導(dǎo)致一個 TypeNotPresentException。
isAnnotationPresent
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
如果指定類型的注釋存在于此元素上,則返回 true,否則返回 false。此方法主要是為了便于訪問標記注釋而設(shè)計的。
參數(shù):
annotationClass - 對應(yīng)于注釋類型的 Class 對象
返回:
如果指定注釋類型的注釋存在于此對象上,則返回 true,否則返回 false
拋出:
NullPointerException - 如果給定的注釋類為 null
從以下版本開始:
1.5
getAnnotation
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
如果存在該元素的指定類型的注釋,則返回這些注釋,否則返回 null。
參數(shù):
annotationClass - 對應(yīng)于注釋類型的 Class 對象
返回:
如果該元素的指定注釋類型的注釋存在于此對象上,則返回這些注釋,否則返回 null
拋出:
NullPointerException - 如果給定的注釋類為 null
從以下版本開始:
1.5
getAnnotations
Annotation[] getAnnotations()
返回此元素上存在的所有注釋。(如果此元素沒有注釋,則返回長度為零的數(shù)組。)該方法的調(diào)用者可以隨意修改返回的數(shù)組;這不會對其他調(diào)用者返回的數(shù)組產(chǎn)生任何影響。
返回:
此元素上存在的所有注釋
從以下版本開始:
1.5
getDeclaredAnnotations
Annotation[] getDeclaredAnnotations()
返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長度為零的一個數(shù)組。)該方法的調(diào)用者可以隨意修改返回的數(shù)組;這不會對其他調(diào)用者返回的數(shù)組產(chǎn)生任何影響。
返回:
直接存在于此元素上的所有注釋
從以下版本開始:
1.5
下面是使用反射讀取RUNTIME保留策略的Annotation信息的例子:
自定義注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String[] value1() default "abc";
}
使用自定義注解:
public class AnnotationTest2 {
@MyAnnotation(value1={"a","b"})
@Deprecated
public void execute(){
System.out.println("method");
}
}
讀取注解中的信息:
public static void main(String[]args)throws SecurityException,NoSuchMethodException,IllegalArgumentException,IllegalAccessException,InvocationTargetException{
AnnotationTest2 annotationTest2=new AnnotationTest2();
//獲取AnnotationTest2的Class實例
Class<AnnotationTest2> c=AnnotationTest2.class;
//獲取需要處理的方法Method實例
Method method=c.getMethod("execute",new Class[]{});
//判斷該方法是否包含MyAnnotation注解
if(method.isAnnotationPresent(MyAnnotation.class)){
//獲取該方法的MyAnnotation注解實例
MyAnnotation myAnnotation=method.getAnnotation(MyAnnotation.class);
//執(zhí)行該方法
method.invoke(annotationTest2,new Object[]{});
//獲取myAnnotation
String[]value1=myAnnotation.value1();
System.out.println(value1[0]);
}
//獲取方法上的所有注解
Annotation[]annotations=method.getAnnotations();
for(Annotation annotation:annotations){
System.out.println(annotation);
}
}
8、限定注解的使用:
限定注解使用@Target。
@Documented
@Retention(value=RUNTIME)
@Target(value=ANNOTATION_TYPE)
public @interface Target
指示注釋類型所適用的程序元素的種類。如果注釋類型聲明中不存在 Target 元注釋,則聲明的類型可以用在任一程序元素上。如果存在這樣的元注釋,則編譯器強制實施指定的使用限制。 例如,此元注釋指示該聲明類型是其自身,即元注釋類型。它只能用在注釋類型聲明上:
@Target(ElementType.ANNOTATION_TYPE)
public @interface MetaAnnotationType {
...
}
此元注釋指示該聲明類型只可作為復(fù)雜注釋類型聲明中的成員類型使用。它不能直接用于注釋:
@Target({})
public @interface MemberType {
...
}
這是一個編譯時錯誤,它表明一個 ElementType 常量在 Target 注釋中出現(xiàn)了不只一次。例如,以下元注釋是非法的:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
public @interface Bogus {
...
}
public enum ElementType
extends Enum<ElementType>
程序元素類型。此枚舉類型的常量提供了 Java 程序中聲明的元素的簡單分類。
這些常量與 Target 元注釋類型一起使用,以指定在什么情況下使用注釋類型是合法的。
ANNOTATION_TYPE
注釋類型聲明CONSTRUCTOR
構(gòu)造方法聲明FIELD
字段聲明(包括枚舉常量)LOCAL_VARIABLE
局部變量聲明METHOD
方法聲明PACKAGE
包聲明PARAMETER
參數(shù)聲明TYPE
類、接口(包括注釋類型)或枚舉聲明
注解的使用限定的例子:
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String[] value1() default "abc";
}
9、在幫助文檔中加入注解:
要想在制作JavaDoc文件的同時將注解信息加入到API文件中,可以使用java.lang.annotation.Documented。
在自定義注解中聲明構(gòu)建注解文檔:
@Documented
public @interface MyAnnotation {
String[] value1() default "abc";
}
使用自定義注解:
public class AnnotationTest2 {
@MyAnnotation(value1={"a","b"})
public void execute(){
System.out.println("method");
}
}
10、在注解中使用繼承:
默認情況下注解并不會被繼承到子類中,可以在自定義注解時加上java.lang.annotation.Inherited注解聲明使用繼承。
@Documented
@Retention(value=RUNTIME)
@Target(value=ANNOTATION_TYPE)
public @interface Inherited
指示注釋類型被自動繼承。如果在注釋類型聲明中存在 Inherited 元注釋,并且用戶在某一類聲明中查詢該注釋類型,同時該類聲明中沒有此類型的注釋,則將在該類的超類中自動查詢該注釋類型。此過程會重復(fù)進行,直到找到此類型的注釋或到達了該類層次結(jié)構(gòu)的頂層 (Object) 為止。如果沒有超類具有該類型的注釋,則查詢將指示當前類沒有這樣的注釋。
注意,如果使用注釋類型注釋類以外的任何事物,此元注釋類型都是無效的。還要注意,此元注釋僅促成從超類繼承注釋;對已實現(xiàn)接口的注釋無效。
十二、說說你對依賴注入的理解?
假設(shè)你編寫了兩個類,一個是人(Person),一個是手機(Mobile)。
人有時候需要用手機打電話,需要用到手機的callUp方法。
傳統(tǒng)方法中,類Person的makeCall方法對Mobile類具有依賴,必須手動生成一個新的實例new Mobile()才可以進行之后的工作。依賴注入的思想是這樣,當一個類(Person)對另一個類(Mobile)有依賴時,不再該類(Person)內(nèi)部對依賴的類(Moblile)進行實例化,而是之前配置一個beans.xml,告訴容器所依賴的類(Mobile),在實例化該類(Person)時,容器自動注入一個所依賴的類(Mobile)的實例。
十三、說一下泛型原理,并舉例說明?
Java從1.5之后支持泛型,泛型的本質(zhì)是參數(shù)化類型,也就是說所操作的數(shù)據(jù)類型被指定為一個參數(shù)。這種參數(shù)類型可以用在類、接口和方法的創(chuàng)建中,分別稱為泛型類、泛型接口、泛型方法。
如不支持泛型,則表現(xiàn)為支持Object,不是特定的泛型。
泛型是對 Java 語言的類型系統(tǒng)的一種擴展,以支持創(chuàng)建可以按類型進行參數(shù)化的類。可以把類型參數(shù)看作是使用參數(shù)化類型時指定的類型的一個占位符,就像方法的形式參數(shù)是運行時傳遞的值的占位符一樣。
可以在集合框架中看到泛型的動機。例如,List類允許您向一個 List添加任意類的對象,即使最常見的情況List.add()。
因為 list.get() 被定義為返回 Object,所以一般必須將 list.get() 的結(jié)果強制類型轉(zhuǎn)換為期望的類型,如下面的代碼所示:
List list = new ArrayList();
list .add("obj");
String s = (String) list.get(0);
要讓程序通過編譯,必須將 get() 的結(jié)果強制類型轉(zhuǎn)換為 String,并且希望結(jié)果真的是一個 String。但是有可能某人已經(jīng)在該映射中保存了不是 String 的東西,這樣的話,上面的代碼將會拋出 ClassCastException。
理想情況下,您可能會得出這樣一個觀點,即 list 是一個 List,它將 String 鍵映射到 String 值。
泛型可以消除代碼中的強制類型轉(zhuǎn)換,同時獲得一個附加的類型檢查層,該檢查層可以防止有人將錯誤類型的鍵或值保存在集合中。這就是泛型所做的工作。
十四、Java中String的了解?
1)String類是final類,也即意味著String類不能被繼承,并且它的成員方法都默認為final方法。在Java中,被final修飾的類是不允許被繼承的,并且該類中的成員方法都默認為final方法。
2)String類其實是通過char數(shù)組來保存字符串的。
無論是sub操、concat還是replace操作都不是在原有的字符串上進行的,而是重新生成了一個新的字符串對象。也就是說進行這些操作后,最原始的字符串并沒有被改變。
在這里要永遠記住一點:“String對象一旦被創(chuàng)建就是固定不變的了,對String對象的任何改變都不影響到原對象,相關(guān)的任何change操作都會生成新的對象”。
我們知道字符串的分配和其他對象分配一樣,是需要消耗高昂的時間和空間的,而且字符串我們使用的非常多。JVM為了提高性能和減少內(nèi)存的開銷,在實例化字符串的時候進行了一些優(yōu)化:使用字符串常量池。每當我們創(chuàng)建字符串常量時,JVM會首先檢查字符串常量池,如果該字符串已經(jīng)存在常量池中,那么就直接返回常量池中的實例引用。如果字符串不存在常量池中,就會實例化該字符串并且將其放到常量池中。由于String字符串的不可變性我們可以十分肯定常量池中一定不存在兩個相同的字符串(這點對理解上面至關(guān)重要)。
Java中的常量池,實際上分為兩種形態(tài):靜態(tài)常量池和運行時常量池。
所謂靜態(tài)常量池,即*.class文件中的常量池,class文件中的常量池不僅僅包含字符串(數(shù)字)字面量,還包含類、方法的信息,占用class文件絕大部分空間。
而運行時常量池,則是jvm虛擬機在完成類裝載操作后,將class文件中的常量池載入到內(nèi)存中,并保存在方法區(qū)中,我們常說的常量池,就是指方法區(qū)中的運行時常量池。
來看下面的程序:
String a = "chenssy";
String b = "chenssy";
a、b和字面上的chenssy都是指向JVM字符串常量池中的"chenssy"對象,他們指向同一個對象。
String c =newString("chenssy");
new關(guān)鍵字一定會產(chǎn)生一個對象chenssy(注意這個chenssy和上面的chenssy不同),同時這個對象是存儲在堆中。所以上面應(yīng)該產(chǎn)生了兩個對象:保存在棧中的c和保存堆中chenssy。但是在Java中根本就不存在兩個完全一模一樣的字符串對象。故堆中的chenssy應(yīng)該是引用字符串常量池中chenssy。所以c、chenssy、池chenssy的關(guān)系應(yīng)該是:c--->chenssy--->池chenssy。
總結(jié):
1.String類初始化后是不可變的(immutable)
String使用private final char value[]來實現(xiàn)字符串的存儲,也就是說String對象創(chuàng)建之后,就不能再修改此對象中存儲的字符串內(nèi)容,就是因為如此,才說String類型是不可變的(immutable)。程序員不能對已有的不可變對象進行修改。我們自己也可以創(chuàng)建不可變對象,只要在接口中不提供修改數(shù)據(jù)的方法就可以。
然而,String類對象確實有編輯字符串的功能,比如replace()。這些編輯功能是通過創(chuàng)建一個新的對象來實現(xiàn)的,而不是對原有對象進行修改。比如:
s = s.replace("World", "Universe");
上面對s.replace()的調(diào)用將創(chuàng)建一個新的字符串"Hello Universe!",并返回該對象的引用。通過賦值,引用s將指向該新的字符串。如果沒有其他引用指向原有字符串"Hello World!",原字符串對象將被垃圾回收。2.引用變量與對象
A aa;
這個語句聲明一個類A的引用變量aa[我們常常稱之為句柄],而對象一般通過new創(chuàng)建。所以aa僅僅是一個引用變量,它不是對象。3.創(chuàng)建字符串的方式
創(chuàng)建字符串的方式歸納起來有兩類:
(1)使用""引號創(chuàng)建字符串;
(2)使用new關(guān)鍵字創(chuàng)建字符串。
結(jié)合上面例子,總結(jié)如下:
(1)單獨使用""引號創(chuàng)建的字符串都是常量,編譯期就已經(jīng)確定存儲到String Pool中;
(2)使用new String("")創(chuàng)建的對象會存儲到heap中,是運行期新創(chuàng)建的;
new創(chuàng)建字符串時首先查看池中是否有相同值的字符串,如果有,則拷貝一份到堆中,然后返回堆中的地址;如果池中沒有,則在堆中創(chuàng)建一份,然后返回堆中的地址(注意,此時不需要從堆中復(fù)制到池中,否則,將使得堆中的字符串永遠是池中的子集,導(dǎo)致浪費池的空間)!
(3)使用只包含常量的字符串連接符如"aa" + "aa"創(chuàng)建的也是常量,編譯期就能確定,已經(jīng)確定存儲到String Pool中;
(4)使用包含變量的字符串連接符如"aa" + s1創(chuàng)建的對象是運行期才創(chuàng)建的,存儲在heap中;4.使用String不一定創(chuàng)建對象
在執(zhí)行到雙引號包含字符串的語句時,如String a = "123",JVM會先到常量池里查找,如果有的話返回常量池里的這個實例的引用,否則的話創(chuàng)建一個新實例并置入常量池里。所以,當我們在使用諸如String str = "abc";的格式定義對象時,總是想當然地認為,創(chuàng)建了String類的對象str。擔心陷阱!對象可能并沒有被創(chuàng)建!而可能只是指向一個先前已經(jīng)創(chuàng)建的對象。只有通過new()方法才能保證每次都創(chuàng)建一個新的對象。5.使用new String,一定創(chuàng)建對象
在執(zhí)行String a = new String("123")的時候,首先走常量池的路線取到一個實例的引用,然后在堆上創(chuàng)建一個新的String實例,走以下構(gòu)造函數(shù)給value屬性賦值,然后把實例引用賦值給a:6.關(guān)于String.intern()
intern方法使用:一個初始為空的字符串池,它由類String獨自維護。當調(diào)用
intern方法時,如果池已經(jīng)包含一個等于此String對象的字符串(用equals(oject)方法確定),則返回池中的字符串。否則,將此String對象添加到池中,并返回此String對象的引用。7.關(guān)于equals和==
(1)對于==,如果作用于基本數(shù)據(jù)類型的變量(byte,short,char,int,long,float,double,boolean
),則直接比較其存儲的"值"是否相等;如果作用于引用類型的變量(String),則比較的是所指向的對象的地址(即是否指向同一個對象)。
(2)equals方法是基類Object中的方法,因此對于所有的繼承于Object的類都會有該方法。在Object類中,equals方法是用來比較兩個對象的引用是否相等,即是否指向同一個對象。
(3)對于equals方法,注意:equals方法不能作用于基本數(shù)據(jù)類型的變量。如果沒有對equals方法進行重寫,則比較的是引用類型的變量所指向的對象的地址;而String類對equals方法進行了重寫,用來比較指向的字符串對象所存儲的字符串是否相等。其他的一些類諸如Double,Date,Integer等,都對equals方法進行了重寫用來比較指向的對象所存儲的內(nèi)容是否相等。8.當使用+進行多個字符串連接時,實際上是產(chǎn)生了一個StringBuilder對象和一個String對象。
9.String的不可變性導(dǎo)致字符串變量使用+號的代價:
String s = "a" + "b" + "c";
String s1 = "a";
String s2 = "b";
String s3 = "c";
String s4 = s1 + s2 + s3;
分析:變量s的創(chuàng)建等價于 String s = "abc"; 由上面例子可知編譯器進行了優(yōu)化,這里只創(chuàng)建了一個對象。由上面的例子也可以知道s4不能在編譯期進行優(yōu)化,其對象創(chuàng)建相當于:
StringBuilder temp =new StringBuilder();
temp.append(a).append(b).append(c);
String s = temp.toString();
不難推斷出String 采用連接運算符(+)效率低下原因。10.關(guān)于String str = new String("abc")創(chuàng)建了多少個對象?
new只調(diào)用了一次,也就是說只創(chuàng)建了一個對象。而這道題目讓人混淆的地方就是這里,這段代碼在運行期間確實只創(chuàng)建了一個對象,即在堆上創(chuàng)建了"abc"對象。而為什么大家都在說是2個對象呢,這里面要澄清一個概念,該段代碼執(zhí)行過程和類的加載過程是有區(qū)別的。在類加載的過程中,確實在運行時常量池中創(chuàng)建了一個"abc"對象,而在代碼執(zhí)行過程中確實只創(chuàng)建了一個String對象。
因此,這個問題如果換成 String str = new String("abc")涉及到幾個String對象?合理的解釋是2個。11.字符串池的優(yōu)缺點:
字符串池的優(yōu)點就是避免了相同內(nèi)容的字符串的創(chuàng)建,節(jié)省了內(nèi)存,省去了創(chuàng)建相同字符串的時間,同時提升了性能;另一方面,字符串池的缺點就是犧牲了JVM在常量池中遍歷對象所需要的時間,不過其時間成本相比而言比較低。
十五、String為什么要設(shè)計成不可變的?
1. 字符串常量池的需要
字符串常量池(String pool,String
intern pool, String保留池) 是Java堆內(nèi)存中一個特殊的存儲區(qū)域, 當創(chuàng)建一個String對象時,假如此字符串值已經(jīng)存在于常量池中,則不會創(chuàng)建一個新的對象,而是引用已經(jīng)存在的對象。
2. 允許String對象緩存HashCode
Java中String對象的哈希碼被頻繁地使用, 比如在hashMap 等容器中。
字符串不變性保證了hash碼的唯一性,因此可以放心地進行緩存.這也是一種性能優(yōu)化手段,意味著不必每次都去計算新的哈希碼. 在String類的定義中有如下代碼:
3. 安全性
String被許多的Java類(庫)用來當做參數(shù),例如 網(wǎng)絡(luò)連接地址URL,文件路徑path,還有反射機制所需要的String參數(shù)等, 假若String不是固定不變的,將會引起各種安全隱患。
十六、Object類的equal和hashCode方法重寫,為什么?
hashCode是編譯器為不同對象產(chǎn)生的不同整數(shù),根據(jù)equal方法的定義:如果兩個對象是相等(equal)的,那么兩個對象調(diào)用hashCode必須產(chǎn)生相同的整數(shù)結(jié)果,即:equal為true,hashCode必須為true,equal為false,hashCode也必須為false,所以必須重寫hashCode來保證與equal同步。
class Student {
int num;
String name;
Student(int num, String name) {
this.num = num;
this.name = name;
}
public int hashCode() {
return num * name.hashCode();
}
public boolean equals(Object o) {
Student s = (Student) o;
return num == s.num && name.equals(s.name);
}
public String toString() {
return num + ":" + name;
}
}
在java的集合中,判斷兩個對象是否相等的規(guī)則是:
1,判斷兩個對象的hashCode是否相等
如果不相等,認為兩個對象也不相等,完畢
如果相等,轉(zhuǎn)入2
2,判斷兩個對象用equals運算是否相等
如果不相等,認為兩個對象也不相等
如果相等,認為兩個對象相等
為什么equals()相等,hashCode就一定要相等,而hashCode相等,卻不要求equals相等?
答案:
1、因為是按照hashCode來訪問小內(nèi)存塊,所以hashCode必須相等。
2、HashMap獲取一個對象是比較key的hashCode相等和equal為true。
之所以hashCode相等,卻可以equal不等,就比如ObjectA和ObjectB他們都有屬性name,那么hashCode都以name計算,所以hashCode一樣,但是兩個對象屬于不同類型,所以equal為false。
為什么需要hashCode?
1、 通過hashCode可以很快的查到小內(nèi)存塊。
2、 通過hashCode比較比equal方法快,當get時先比較hashCode,如果hashCode不同,直接返回false。