本大全每個(gè)月會(huì)定期更新,索取網(wǎng)址:http://www. Java核心技術(shù)部分Java核心技術(shù)部分的面試題,可能覆蓋Java基本語法、面向?qū)ο螅ò惗x、方法、構(gòu)造器、遞歸、繼承、抽象類、接口、枚舉以及final、static等關(guān)鍵字)、Java常用API、Java集合框架(需要重點(diǎn)掌握)、注解(Annotation)、泛型、輸入/輸出、多線程、網(wǎng)絡(luò)通信、反射、內(nèi)存管理等相關(guān)內(nèi)容,這些知識(shí)基本都可通過《瘋狂Java講義》一書找到詳細(xì)解答。 這部分面試題大部分從網(wǎng)絡(luò)收集、整理,也有部分題目來自瘋狂軟件學(xué)員面試之后的反饋。 1、面向?qū)ο蟮奶卣饔心男?/h3>面向?qū)ο蟮娜筇卣鳎?/p> 繼承:通過繼承允許復(fù)用已有的類,繼承關(guān)系是一種“一般到特殊”的關(guān)系,比如蘋果類繼承水果類,這個(gè)過程稱為類繼承。 派生出來的新類稱為原有類的子類(派生類),而原有類稱為新類的父類(基類)。 子類可以從父類那里繼承得到方法和成員變量,而且子類類可以修改或增加新的方法使之適合子類的需要。 封裝:封裝是把對(duì)象的狀態(tài)數(shù)據(jù)隱藏起來,再通過暴露合適的方法來允許外部程序修改對(duì)象的狀態(tài)數(shù)據(jù)。Java的封裝主要通過private、protected、public等訪問控制符來實(shí)現(xiàn)。 多態(tài):多態(tài)指的是當(dāng)同一個(gè)類型的引用類型的變量在執(zhí)行相同的方法時(shí),實(shí)際上會(huì)呈現(xiàn)出多種不同的行為特征。比如程序有Animal a1 = new Animal (); Animal a2 = new Wolf();雖然a1、a2兩個(gè)引用變量的類型都是Animal,但當(dāng)它們調(diào)用同一個(gè)run()方法時(shí),如果Wolf()類重寫過Animal的run()方法,這就會(huì)導(dǎo)致a1、a2兩個(gè)變量執(zhí)行run()方法時(shí)呈現(xiàn)出不同的行為特征,這就是多態(tài)。多態(tài)增加了編程的靈活性,實(shí)際上大量設(shè)計(jì)模式都是基于多態(tài)類實(shí)現(xiàn)的。 除此之外,抽象也是一個(gè)重要的特征,抽象就是忽略與當(dāng)前目標(biāo)無關(guān)的相關(guān)方面,以便更充分地突出與當(dāng)前目標(biāo)有關(guān)的方面。抽象并不打算了解全部問題,而只是選擇其中的一部分,暫時(shí)不用部分細(xì)節(jié)。抽象包括兩個(gè)方面,一是過程抽象,二是數(shù)據(jù)抽象。 2、Java中實(shí)現(xiàn)多態(tài)的機(jī)制是什么?Java允許父類或接口定義的引用變量指向子類或具體實(shí)現(xiàn)類的實(shí)例對(duì)象,而程序調(diào)用的方法在運(yùn)行時(shí)才動(dòng)態(tài)綁定,就是引用變量所指向的具體實(shí)例對(duì)象的方法,也就是內(nèi)存里正在運(yùn)行的那個(gè)對(duì)象的方法,而不是引用變量的類型中定義的方法。 正是由于這種機(jī)制,兩個(gè)相同類型的引用變量,但由于它們實(shí)際引用了不同的對(duì)象,因此它們運(yùn)行時(shí)可能呈現(xiàn)出多種不同的行為特征,這就被稱多態(tài)。 3、一個(gè)".java"源文件中是否可以包括多個(gè)類(不是內(nèi)部類)?有什么限制?可以有多個(gè)類,但只能有一個(gè)public的類,并且public的類名必須與文件的主文件名相同。 包含多個(gè)類的Java源文件編譯之后會(huì)生成多個(gè).class文件,每個(gè)類(包括外部類、內(nèi)部類)都會(huì)生成一個(gè)對(duì)應(yīng)的.class文件。 4、String是基本數(shù)據(jù)類型嗎?基本數(shù)據(jù)類型包括byte、short、int、long、char、float、double和boolean。String不是基本類型。String是引用類型。 java.lang.String類是final的,因此無法通過String類派生子類。 String也是一個(gè)不可變類(它所包含的字符序列是不可改變),因此如果程序需要使用的字符串所包含的字符序列需要經(jīng)常改變,建議使用StringBuffer(線程安全、性能略差)類或StringBuilder類。 5、int 和 Integer 有什么區(qū)別Java 提供兩種不同的類型:引用類型和基本數(shù)據(jù)類型。 int是基本數(shù)據(jù)類型,Integer是Java為int提供的包裝類。 Java為所有的基本類型提供了對(duì)應(yīng)的包裝類。 byte Byte short Short int Integer long Long char Character float Float double Double boolean Boolean 基本類型的變量只能當(dāng)成簡單的直接量、參與表達(dá)式運(yùn)算,不具備面向?qū)?duì)象的特征,基本類型的變量不能被賦為null;但包裝類的變量則完全可以當(dāng)成對(duì)象使用,它具有面向?qū)ο蟮奶卣鳎b類的變量可以被賦為null。 因?yàn)镮nteger具有面向?qū)ο蟮奶卣?,因此Integer可以區(qū)分出未賦值和值為0的區(qū)別,int則無法表達(dá)出未賦值的情況,例如,要想表達(dá)出沒有參加考試和考試成績?yōu)?的區(qū)別,則只能使用Integer。在JSP開發(fā)中,Integer的默認(rèn)為null,所以用EL輸出為null的Integer時(shí),將會(huì)顯示為空白字符串,而int默認(rèn)的默認(rèn)值為0,用EL輸出為將顯示0。所以,int不適合作為Web層的表單數(shù)據(jù)的類型。 從Java 5開始,Java提供了自動(dòng)裝箱、自動(dòng)拆箱功能,因此包裝類也可以直接參與表達(dá)式運(yùn)算,因此使用起來十分方便。 另外,Integer提供了多個(gè)與整數(shù)相關(guān)的操作方法,例如,將一個(gè)字符串轉(zhuǎn)換成整數(shù),Integer中還定義了表示整數(shù)的最大值和最小值的常量。 6、Java有沒有g(shù)oto?goto是Java中的保留字,Java程序的標(biāo)識(shí)符不允許使用goto。但Java也不支持使用goto進(jìn)行跳轉(zhuǎn)。 7、String 和StringBuffer、StringBuilder的區(qū)別Java提供了:String、StringBuffer和StringBuilder,它們都是CharSequence的實(shí)現(xiàn)類,都可以作為字符串使用。 String代表了字符序列不可變的字符串;而StringBuffer、StringBuilder都代表了字符序列可變的字符串。 StringBuffer、StringBuilder的區(qū)別是StringBuffer是線程安全的、性能略低,而StringBuilder是線程不安全的,適合單線程環(huán)境使用,性能較好。 8、Collection 和 Collections的區(qū)別。Collection是集合類(List、Set、Queue)的根接口。 Collections是針對(duì)集合類的一個(gè)工具類,它提供一系列靜態(tài)方法實(shí)現(xiàn)對(duì)各種集合的搜索、排序、線程安全化等操作。 9、說說&和&&的區(qū)別。&和&&都可以用作邏輯與的運(yùn)算符,當(dāng)運(yùn)算符兩邊的表達(dá)式的結(jié)果都為true時(shí),整個(gè)運(yùn)算結(jié)果才為true,否則,只要有一方為false,則結(jié)果為false。 &&還具有短路的功能,即如果第一個(gè)表達(dá)式為false,則不再計(jì)算第二個(gè)表達(dá)式,例如,對(duì)于if(a >8 && b > 5),當(dāng)a小于等于8時(shí),由于&&之前的表達(dá)式已經(jīng)為false了,因此&&之后的表達(dá)式根本不會(huì)執(zhí)行; 再例如if(str != null && !str.equals(""))表達(dá)式,當(dāng)str為null時(shí),后面的表達(dá)式不會(huì)執(zhí)行,因此不會(huì)出現(xiàn)NullPointerException如果將&&改為&,則可能拋出NullPointerException異常。 再例如if(x > 8 & ++y)與if(x > 8 && ++y ),當(dāng)a小于等于8時(shí),前一個(gè)表達(dá)式中y的值會(huì)增長;后一個(gè)表達(dá)式中y的值不會(huì)增加。 除此之外,&還可以用作位運(yùn)算符,當(dāng)&操作符兩邊的表達(dá)式不是boolean類型時(shí),&表示按位與操作,通常使用0x0f來與一個(gè)整數(shù)進(jìn)行&運(yùn)算,來獲取該整數(shù)的最低4個(gè)bit位,例如,0x31 & 0x0f的結(jié)果為0x01。 10、Overload和Override的區(qū)別。Overloaded的方法是否可以改變返回值的類型?Overload是方法的重載 Override是方法的重寫,也叫覆蓋。 Overload要求兩個(gè)方法具有方法名相同、形參列表不同的要求,返回值類型不能作為重載的條件。 Override要求子類方法與父類方法具有“兩同兩小一大”的要求。兩同指:即父類方法、子類方法的方法名相同、形參列表相同;兩小指:子類方法返回值類型要么是父類方法返回值類型的子類、要么與父類方法返回值類型相同;子類方法聲明拋出的異常類型要么是父類方法聲明拋出的異常類型的子類、要么與父類聲明拋出的異常類型相同;一大指:子類方法的訪問權(quán)限要么與父類方法的訪問權(quán)限相同,要么比父類方法的訪問權(quán)限更大。 Overloaded的方法是可以改變返回值的類型。 11、Java如何跳出當(dāng)前的多重嵌套循環(huán)?在Java中,要想跳出多重循環(huán),可以在外面的循環(huán)語句前定義一個(gè)標(biāo)號(hào),然后在里層循環(huán)體的代碼中使用帶有標(biāo)號(hào)的break語句,即可跳出外層循環(huán)。例如, outer: for(int i=0;i<10;i++) { for(int j=0;j<10;j++) { System.out.println(“i=” + i + “,j=” + j); if(j == 5) break ouer; } } 12、switch語句能否作用在byte上,能否作用在long上,能否作用在String上?在Java 7以前,在switch(expr1)中,expr1只能是一個(gè)整數(shù)表達(dá)式(但不包括long和Long)或者枚舉常量,整數(shù)表達(dá)式可以是int基本類型或Integer包裝類型,byte、short、char都可以自動(dòng)轉(zhuǎn)換為int,它們都可作為switch表達(dá)式。 從Java 7開始,switch表達(dá)式的可以使用String。 13、String s = new String("xyz");創(chuàng)建了幾個(gè)String Object?兩個(gè)。一個(gè)是直接量的"xyz"字符串對(duì)象,該字符串將會(huì)被緩存在字符串常量池中,以便以后復(fù)用這個(gè)字符串;另一個(gè)是通過new Sting()構(gòu)造器創(chuàng)建出來的String對(duì)象,這個(gè)String對(duì)象保存在堆內(nèi)存中。 通常來說,應(yīng)該盡量使用直接量的String對(duì)象,這樣具有更好的性能。 14、數(shù)組有沒有l(wèi)ength()這個(gè)方法? String有沒有l(wèi)ength()這個(gè)方法?數(shù)組沒有l(wèi)ength()這個(gè)方法,有l(wèi)ength屬性。String有l(wèi)ength()方法。 15、short s1 = 1; s1 = s1 + 1;有什么錯(cuò)? short s1 = 1; s1 += 1;有什么錯(cuò)?對(duì)于short s1 = 1; s1 = s1 + 1; 由于s1+1運(yùn)算時(shí)會(huì)自動(dòng)提升表達(dá)式的類型,所以結(jié)果是int型,再賦值給short類型s1時(shí),編譯器將報(bào)告需要強(qiáng)制轉(zhuǎn)換類型的錯(cuò)誤。 對(duì)于short s1 = 1; s1 += 1;由于 +=運(yùn)算符里已經(jīng)包括了一個(gè)隱式的強(qiáng)制類型轉(zhuǎn)換,因此Java會(huì)把s1+=1計(jì)算后的結(jié)果進(jìn)行隱式的強(qiáng)制類型轉(zhuǎn)換,因此它不會(huì)有任何錯(cuò)誤。 16、char型變量中能不能存儲(chǔ)一個(gè)中文字符?為什么?char型變量是用來存儲(chǔ)Unicode編碼的字符的,Unicode編碼字符集中包含了漢字,因此char型變量中可以存儲(chǔ)漢字。不過,如果某個(gè)特殊的漢字沒有被包含在Unicode編碼字符集中,那么,這個(gè)char型變量中就不能存儲(chǔ)這個(gè)特殊漢字。 char類型的變量占兩個(gè)字節(jié),而Unicode編碼中每個(gè)字符也占兩個(gè)字節(jié),因此char類型類型的變量可以存儲(chǔ)任何一個(gè)Unicode字符。 17、用最有效率的方法算出2乘以8等于幾?2 << 3 因?yàn)閷⒁粋€(gè)數(shù)左移n位,就相當(dāng)于乘以了2的n次方,那么,一個(gè)數(shù)乘以8只要將其左移3位即可,而位運(yùn)算CPU直接支持的,效率最高,所以,2乘以8等于幾的最效率的方法是2 << 3。 但需要注意的是,如果這個(gè)數(shù)字本身已經(jīng)很大,比如本身已經(jīng)是2的30次方了,此時(shí)再用這種位移運(yùn)算就可能導(dǎo)致“溢出”,這樣就得不到正確結(jié)果了。 18、使用final關(guān)鍵字修飾一個(gè)變量時(shí),是引用不能變,還是引用的對(duì)象不能變?使用final關(guān)鍵字修飾一個(gè)變量時(shí),是指引用變量不能變,引用變量所指向的對(duì)象中的內(nèi)容還是可以改變的。例如,對(duì)于如下語句: final StringBuilder a = new StringBuilder ("immutable"); 執(zhí)行如下語句將報(bào)告編譯錯(cuò)誤: a = new StringBuilder (""); 但如下語句則是完全正確的 a.append("fkjava.org"); 有人希望在定義方法的形參時(shí),通過final修飾符來阻止方法內(nèi)部修改傳進(jìn)來的實(shí)參: public void method(final StringBuilder param) { } 實(shí)際上這沒有用,在該方法內(nèi)部仍然可以增加如下代碼來修改實(shí)參對(duì)象: param.append("fkjava.org"); 19、"=="和equals方法究竟有什么區(qū)別?==操作符的作用有兩種: A.如果==的兩邊都是基本類型變量、包裝類對(duì)象所組成的表達(dá)式,==用于比較兩邊的表達(dá)式的值是否相等——只要兩邊的表達(dá)式的值相等,即使數(shù)據(jù)類不同,該運(yùn)算符也會(huì)返回true。例如'a' == 97.0,將會(huì)返回true。 B.如果==的兩邊是引用類型的變量,==用于判斷這兩個(gè)引用類型的變量是否引用同一塊內(nèi)存,只有當(dāng)它們引用同一塊內(nèi)存時(shí),==才會(huì)返回true。 而equals()則是一個(gè)java.lang.Object類的一個(gè)方法,因此任何Java對(duì)象都可調(diào)用該方法與其他對(duì)象進(jìn)行比較。java.lang.Object類的equals方法的實(shí)現(xiàn)代碼如下: boolean equals(Object o) { return this==o; } 從上面代碼可以看出,如果一個(gè)類沒有重寫java.lang.Object的equals()方法時(shí),此時(shí)equals()方法的比較結(jié)果與==的比較結(jié)果是相同的。 但Java允許任何類重寫equals()方法,重寫該方法就是讓程序員來自己決定兩個(gè)對(duì)象相等的標(biāo)準(zhǔn)——極端的情況下,我們完全可以設(shè)計(jì)出Person對(duì)象與Dog對(duì)象equals()比較返回true的情況——當(dāng)然一般不會(huì)這么設(shè)計(jì)。 實(shí)際上重寫equals()方法時(shí)通常會(huì)按如下格式: public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (pass == null) { if (other.pass != null) return false; } else if (!pass.equals(other.pass)) return false; return true; } 上面重寫equals()方法用于判斷兩個(gè)Person對(duì)象是否“相等”,程序只要兩個(gè)Person對(duì)象的name、pass相等,程序就可以把這兩個(gè)Person對(duì)象當(dāng)成相等——這是系統(tǒng)業(yè)務(wù)決定的。如果業(yè)務(wù)需要,我們也可以增加更多的參與判斷的Field,當(dāng)然也可以只根據(jù)name進(jìn)行判斷——只要兩個(gè)Person的name相等,就認(rèn)為兩個(gè)Person相等,這都是由系統(tǒng)的業(yè)務(wù)決定。 總結(jié)起來就是一句話:開發(fā)者重寫equals()方法就可以根據(jù)業(yè)務(wù)要求來決定兩個(gè)對(duì)象是否“相等”。 20、靜態(tài)變量和實(shí)例變量的區(qū)別?在語法定義上的區(qū)別:靜態(tài)變量前要加static關(guān)鍵字,而實(shí)例變量前則不加。 在程序運(yùn)行時(shí)的區(qū)別:實(shí)例變量屬于一個(gè)對(duì)象,必須先創(chuàng)建實(shí)例對(duì)象,它的實(shí)例變量才會(huì)被分配空間,才能使用這個(gè)實(shí)例變量。靜態(tài)變量則屬于類,所以也稱為類變量,只要程序加載了類的字節(jié)碼,不用創(chuàng)建任何實(shí)例對(duì)象,靜態(tài)變量就會(huì)被分配空間,靜態(tài)變量就可以被使用了??傊?,實(shí)例變量必須創(chuàng)建對(duì)象后才可以通過這個(gè)對(duì)象來使用,靜態(tài)變量則可以直接使用類名來引用。 例如,對(duì)于下面的程序: public class VarTest { public static int staticVar = 0; public int instanceVar = 0; public VarTest () { staticVar++; instanceVar++; System.out.println(“staticVar=” + staticVar + ”,instanceVar=” + instanceVar); } } 上面程序中的staticVar變量隨VarTest類初始化而分配內(nèi)存、執(zhí)行初始化的,以后無論創(chuàng)建多少個(gè)實(shí)例對(duì)象,不會(huì)再分配staticVar變量,因此用永遠(yuǎn)只有一個(gè)staticVar變量。 但instanceVar變量則是隨著VarTest對(duì)象初始化而分配內(nèi)存、執(zhí)行初始化的,因此每創(chuàng)建一個(gè)實(shí)例對(duì)象,就會(huì)分配一個(gè)instanceVar,即可以分配多個(gè)instanceVar。因此上面程序中每創(chuàng)建一個(gè)VarTest對(duì)象,staticVar的值就會(huì)自加一,但創(chuàng)建每個(gè)VarTest對(duì)象的instanceVar都只自加1。 21、是否可以從一個(gè)static方法內(nèi)部調(diào)用非static方法?不可以。靜態(tài)成員不能調(diào)用非靜態(tài)成員。 非static方法屬于對(duì)象,必須創(chuàng)建一個(gè)對(duì)象后,才可以在通過該對(duì)象來調(diào)用static方法。而static方法調(diào)用時(shí)不需要?jiǎng)?chuàng)建對(duì)象,通過類就可以調(diào)用該方法。也就是說,當(dāng)一個(gè)static方法被調(diào)用時(shí),可能還沒有創(chuàng)建任何實(shí)例對(duì)象,如果允許從一個(gè)static方法中調(diào)用非static方法的調(diào)用,那個(gè)非static方法是沒有調(diào)用對(duì)象的。因此Java不允許static方法內(nèi)部調(diào)用非static方法。 22、Math.round(11.5)等於多少? Math.round(-11.5)等於多少?Math類中提供了三個(gè)與取整有關(guān)的方法:ceil()、floor()、round(),這些方法的作用與它們的英文名稱的含義相對(duì)應(yīng),例如,ceil的英文意義是天花板,該方法就表示向上取整,所以,Math.ceil(11.3)的結(jié)果為12,Math.ceil(-11.3)的結(jié)果是-11;floor的英文意義是地板,該方法就表示向下取整,所以,Math.floor(11.6)的結(jié)果為11,Math.floor(-11.6)的結(jié)果是-12;最難掌握的是round方法,它表示“四舍五入”,算法為Math.floor(x+0.5),即將原來的數(shù)字加上0.5后再向下取整,所以,Math.round(11.5)的結(jié)果為12,Math.round(-11.5)的結(jié)果為-11。 23、請(qǐng)說出作用域public,private,protected,以及不寫時(shí)的區(qū)別這四個(gè)作用域的可見范圍如下表所示。 作用域 當(dāng)前類 同一package 子類 全局 public √ √ √ √ protected √ √ √ × default √ √ × × private √ × × × 說明:如果在修飾的元素上面沒有寫任何訪問修飾符,則表示default。 只要記住訪問權(quán)限由小到大依次是private → default → protected → public,然后再記住Java存在的4個(gè)訪問范圍,就很容易畫出上面的表格了。 24、外部類能用private、protected修飾嗎?內(nèi)部類可以用private、protected修飾嗎?外部類不能用private、protected修飾不能。內(nèi)部類能用private、protected修飾。 外部類的上一級(jí)程序單位是包,因此它只有兩個(gè)使用范圍:包內(nèi)和包外,因此它只能用public(表示可以在全局位置使用)和默認(rèn)修飾符(default,表示只能被同一個(gè)包的其他類使用)修飾。 內(nèi)部類的上一級(jí)程序單位是類,因此它有4個(gè)使用范圍:當(dāng)前類,同一個(gè)包內(nèi)、當(dāng)前類的子類中、全局范圍,因此可以使用private、默認(rèn)修飾符、protected、public的任意一個(gè)修飾符修飾。 25、一個(gè)類定義多個(gè)重載方法,參數(shù)分別是int ,char,和double,然后將double x = 2,傳遞進(jìn)去,會(huì)選擇哪個(gè)方法?選擇參數(shù)類型為double的方法。 26、說說has a與is a的區(qū)別。is a是典型的“一般到特殊”的關(guān)系,也就是典型的繼承關(guān)系。例如Apple is a Fruit。那么Apple是一種特殊的Fruit,也就是說Apple繼承了Fruit。 has a是典型的“組合”關(guān)系。比如Wolf has a Leg,也就是Leg組合成了Wolf。 需要指出的是:由于繼承會(huì)造成了對(duì)父類的破壞,因此有時(shí)候可以通過組合來代替的繼承。使用繼承的好處:程序語義更好理解。壞處是:子類可能重寫父類方法,不利于父類封裝;使用組合則造成語義的混淆,但組合類不會(huì)重寫被組合類的方法,因此更利于被復(fù)合類的封裝。 27、ClassLoader如何加載class 。JVM里有多個(gè)類加載,每個(gè)類加載可以負(fù)責(zé)加載特定位置的類,例如,Bootstrap類加載(根類加載器)負(fù)責(zé)加載它負(fù)責(zé)加載Java的核心類(jre/lib/rt.jar中的類), JDK常用的String、Math、HashSet、ArrayList等類都位于rt.jar中;Extension類加載器負(fù)責(zé)加載jar/lib/ext/*.jar中的類,應(yīng)用類加載器(App ClassLoader負(fù)責(zé)CLASSPATH指定的目錄或JAR包中的類。除了Bootstrap之外,其他的類加載器本身也都是Java類,它們的父類是ClassLoader; Bootstrap類加載器(根類加載器)非常特殊,它并不是java.lang.ClassLoader的子類,而是由JVM自身實(shí)現(xiàn)的。 28、GC是什么? 為什么要有GC?GC是垃圾收集的意思(Gabage Collection),內(nèi)存處理是編程人員容易出現(xiàn)問題的地方,忘記或者錯(cuò)誤的內(nèi)存回收會(huì)導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰,Java提供的GC功能可以自動(dòng)監(jiān)測對(duì)象是否超過作用域從而達(dá)到自動(dòng)回收內(nèi)存的目的。 Java的System類和Runtime類都提供了“通知”程序進(jìn)行垃圾回收的方法,例如如下代碼: Systme.gc(); 或 Runtime.getInstance().gc(); 但這兩個(gè)方法只是“通知”Java進(jìn)行垃圾回收,但實(shí)際上JVM何時(shí)進(jìn)行垃圾回收,還是由JVM自己決定。 29、垃圾回收的優(yōu)點(diǎn)和原理。并考慮2種回收機(jī)制。傳統(tǒng)的C/C++等編程語言,需要程序員負(fù)責(zé)回收已經(jīng)分配的內(nèi)存。顯式進(jìn)行垃圾回收是一件比較困難的事情,因?yàn)槌绦騿T并不總是知道內(nèi)存應(yīng)該何時(shí)被釋放。如果一些分配出去的內(nèi)存得不到及時(shí)回收,就會(huì)引起系統(tǒng)運(yùn)行速度下降,甚至導(dǎo)致程序癱瘓,這種現(xiàn)象被稱為內(nèi)存泄漏。總體而言,顯式進(jìn)行垃圾回收主要有如下兩個(gè)缺點(diǎn): A.程序忘記及時(shí)回收無用內(nèi)存,從而導(dǎo)致內(nèi)存泄漏,降低系統(tǒng)性能。 B.程序錯(cuò)誤地回收程序核心類庫的內(nèi)存,從而導(dǎo)致系統(tǒng)崩潰。 與C/C++程序不同,Java語言不需要程序員直接控制內(nèi)存回收,Java程序的內(nèi)存分配和回收都是由JRE在后臺(tái)自動(dòng)進(jìn)行的。JRE會(huì)負(fù)責(zé)回收那些不再使用的內(nèi)存,這種機(jī)制被稱為垃圾回收(Garbage Collection,也被稱為GC)。通常JRE會(huì)提供一條后臺(tái)線程來進(jìn)行檢測和控制,一般都是在CPU空閑或內(nèi)存不足時(shí)自動(dòng)進(jìn)行垃圾回收,而程序員無法精確控制垃圾回收的時(shí)間和順序等。 實(shí)際上,垃圾回收機(jī)制不可能實(shí)時(shí)檢測到每個(gè)Java對(duì)象的狀態(tài),當(dāng)一個(gè)對(duì)象失去引用后,它也不會(huì)被立即回收,只有等接下來垃圾回收器運(yùn)行時(shí)才會(huì)被回收。 對(duì)于一個(gè)垃圾回收器的設(shè)計(jì)算法來說,大致有如下可供選擇的設(shè)計(jì): A.串行回收(Serial)和并行回收(Parallel):串行回收就是不管系統(tǒng)有多少個(gè)CPU,始終只用一個(gè)CPU來執(zhí)行垃圾回收操作;而并行回收就是把整個(gè)回收工作拆分成多部分,每個(gè)部分由一個(gè)CPU負(fù)責(zé),從而讓多個(gè)CPU并行回收,并行回收的執(zhí)行效率很高,但復(fù)雜度增加,另外也有其他一些副作用,比如內(nèi)存碎片會(huì)增加。 B.并發(fā)執(zhí)行(Concurrent)和應(yīng)用程序停止(Stop-the-world):。Stop-the-world的垃圾回收方式在執(zhí)行垃圾回收的同時(shí)會(huì)導(dǎo)致應(yīng)用程序的暫停。并發(fā)執(zhí)行的垃圾回收雖然不會(huì)導(dǎo)致應(yīng)用程序的暫停,但由于并發(fā)執(zhí)行垃圾回收需要解決和應(yīng)用程序的執(zhí)行沖突(應(yīng)用程序可能會(huì)在垃圾回收的過稱中修改對(duì)象),因此并發(fā)執(zhí)行垃圾回收的系統(tǒng)開銷比Stop-the-world更好,而且執(zhí)行時(shí)也需要更多的堆內(nèi)存。 C.壓縮(Compacting)和不壓縮(Non-compacting)和復(fù)制(Copying):為了減少內(nèi)存碎片,支持壓縮的垃圾回收器會(huì)把所有的活對(duì)象搬遷到一起,然后將之前占用的內(nèi)存全部回收。不壓縮式的垃圾回收器只是回收內(nèi)存,這樣回收回來的內(nèi)存不可能是連續(xù)的,因此將會(huì)有較多的內(nèi)存碎片。較之壓縮式的垃圾回收,不壓縮式的垃圾回收回收內(nèi)存快了,而分配內(nèi)存時(shí)就會(huì)更慢,而且無法解決內(nèi)存碎片的問題。復(fù)制式的垃圾回收會(huì)將所有可達(dá)對(duì)象復(fù)制到另一塊相同的內(nèi)存中,這種方式的優(yōu)點(diǎn)是垃圾及回收過程不會(huì)產(chǎn)生內(nèi)存碎片,但缺點(diǎn)也很明顯,需要拷貝數(shù)據(jù)和額外的內(nèi)存。 30、垃圾回收器的基本原理是什么?垃圾回收器可以馬上回收內(nèi)存嗎?有什么辦法主動(dòng)通知虛擬機(jī)進(jìn)行垃圾回收?對(duì)于Java程序中對(duì)象而言,如果這個(gè)對(duì)象沒有任何引用變量引用它,那么這個(gè)對(duì)象將不可能被程序訪問,因此可認(rèn)為它是垃圾;只要有一個(gè)以上的引用變量引用該對(duì)象,該對(duì)象就不會(huì)被垃圾回收。 對(duì)于Java的垃圾回收器來說,它使用有向圖來記錄和管理堆內(nèi)存中的所有對(duì)象,通過這個(gè)有向圖就可以識(shí)別哪些對(duì)象是“可達(dá)的”(有引用變量引用它就是可達(dá)的),哪些對(duì)象是“不可達(dá)的”(沒有引用變量引用它就是不可達(dá)的),所有“不可達(dá)”對(duì)象都是可被垃圾回收的。 但對(duì)于如下程序: class A { B b; } class B { A a; } public class Test { public static void main(String[] args) { A a = new A(); a.b = new B(); a.b.a = a; a = null; } } 上面程序中A對(duì)象、B對(duì)象,它們都“相互”引用,A對(duì)象的b屬性引用B對(duì)象,而B對(duì)象的a屬性引用A對(duì)象,但實(shí)際上沒有引用變量引用A對(duì)象、B對(duì)象,因此它們?cè)谟邢驁D中依然是不可達(dá)的,因此也會(huì)被當(dāng)成垃圾處理。 程序員可以手動(dòng)執(zhí)行System.gc(),通知GC運(yùn)行,但這只是一個(gè)通知,而JVM依然有權(quán)決定何時(shí)進(jìn)行垃圾回收。 31、什么時(shí)候用assert。assertion(斷言)在軟件開發(fā)中是一種常用的調(diào)試方式,很多開發(fā)語言中都支持這種機(jī)制。在實(shí)現(xiàn)中,assertion就是在程序中的一條語句,它對(duì)一個(gè)boolean表達(dá)式進(jìn)行檢查,一個(gè)正確程序必須保證這個(gè)boolean表達(dá)式的值為true;如果該值為false,說明程序已經(jīng)處于不正確的狀態(tài)下,assert將給出警告或退出。 Java的assert是關(guān)鍵字。 public class TestAssert { public static void main(String[] args) { int a = 5; // 斷言a>3 assert a > 3; // 斷言a<3,否則顯示a不小于3,且a的值為:" + a assert a < 3 : "a不小于3,且a的值為:" + a; } } 從上面代碼可以看出,assert的兩個(gè)基本用法如下: assert logicExp; asert logicExp : expr; A.第一個(gè)直接進(jìn)行斷言, B.第二個(gè)也是進(jìn)行斷言,但當(dāng)斷言失敗失敗時(shí)顯示特定信息。 最后要指出: 雖然assert是JDK1.4新增的關(guān)鍵字,但有一點(diǎn)非常重要: 需要說明的是,Java命令默認(rèn)不啟動(dòng)斷言, 為了啟動(dòng)用戶斷言,應(yīng)該在運(yùn)行java命令時(shí)增加-ea(Enable Assert)選項(xiàng)。 為了啟動(dòng)系統(tǒng)斷言,應(yīng)該在運(yùn)行java命令時(shí)增加-esa(Enable System Assert)選項(xiàng)。 32、Java中會(huì)存在內(nèi)存泄漏嗎,請(qǐng)簡單描述。為了搞清楚Java程序是否有內(nèi)存泄露存在,首先了解一下什么是內(nèi)存泄露:程序運(yùn)行過程中會(huì)不斷地分配內(nèi)存空間;那些不再使用的內(nèi)存空間應(yīng)該即時(shí)回收它們,從而保證系統(tǒng)可以再次使用這些內(nèi)存。如果存在無用的內(nèi)存沒有被回收回來,那就是內(nèi)存泄露。 對(duì)于Java程序而言,只要Java對(duì)象一直處于可達(dá)狀態(tài),垃圾回收機(jī)制就不會(huì)回收它們——即使它們對(duì)于程序來說已經(jīng)變成了垃圾(程序再也不需要它們了);但對(duì)于垃圾回收機(jī)制來說,它們還不是垃圾(還處于可達(dá)狀態(tài)),因此不能回收。 看ArrayList中remove(int index)方法的源代碼,程序如下: public E remove(int index) { // 檢查index索引是否越界 RangeCheck(index); // 使修改次數(shù)加1 modCount++; // 獲取被刪除的元素 E oldValue = (E)elementData[index]; int numMoved = size - index - 1; // 整體搬家 if (numMoved > 0) System.arraycopy(elementData, index+1 , elementData, index, numMoved); / /將ArrayList的size減1, // 并將最后一個(gè)數(shù)組賦為null,讓垃圾回收機(jī)制回收最后一個(gè)元素 elementData[--size] = null; return oldValue; } 上面程序中粗體字代碼elementData[--size] = null;就是為了避免垃圾回收機(jī)制而編寫的代碼,如果沒有這行代碼,這個(gè)方法就會(huì)產(chǎn)生內(nèi)存泄露——每刪除一個(gè)對(duì)象,但該對(duì)象所占用的內(nèi)存空間卻不會(huì)釋放。 33、能不能自己寫個(gè)類,也叫java.lang.String?可以,但在應(yīng)用的時(shí)候,需要用自己的類加載器去加載,否則,系統(tǒng)的類加載器永遠(yuǎn)只是去加載rt.jar包中的那個(gè)java.lang.String。 但在Tomcat的Web應(yīng)用程序中,都是由webapp自己的類加載器先自己加載WEB-INF/classess目錄中的類,然后才委托上級(jí)的類加載器加載,如果我們?cè)赥omcat的Web應(yīng)用程序中寫一個(gè)java.lang.String,這時(shí)候Servlet程序加載的就是我們自己寫的java.lang.String,但是這么干就會(huì)出很多潛在的問題,原來所有用了java.lang.String類的都將出現(xiàn)問題。 34、ArrayList如何實(shí)現(xiàn)插入的數(shù)據(jù)按自定義的方式有序存放編程思路是:實(shí)現(xiàn)一個(gè)類對(duì)ArrayList進(jìn)行包裝,當(dāng)程序試圖向ArrayList中放入數(shù)據(jù)時(shí),程序?qū)⑾葯z查該元素與ArrayList集合中其他元素的大小,然后將該元素插入到指定位置。 class MyBean implements Comparable{ public int compareTo(Object obj){ if(! obj instanceof MyBean) throw new ClassCastException()。 MyBean other = (MyBean) obj; return age > other.age?1:age== other.age?0:-1; } } class MyTreeSet { private ArrayList datas = new ArrayList(); public void add(Object obj){ for(int i=0;i<datas.size();i++){ if(obj.compareTo(datas.get(i) != 1){ datas.add(i,obj); } } } } 35、序列化接口的版本號(hào)(id)有什么用?反序列化Java對(duì)象時(shí)必須提供該對(duì)象的class文件,現(xiàn)在的問題是隨著項(xiàng)目的升級(jí),系統(tǒng)的class文件也會(huì)升級(jí),Java如何保證兩個(gè)class文件的兼容性? Java序列化機(jī)制允許為序列化類提供一個(gè)private static final long類型的serialVersionUID值,該Field值用于標(biāo)識(shí)該Java類的序列化版本,也就是說如果一個(gè)類升級(jí)后,只要它的serialVersionUID值保持不變,序列化機(jī)制也會(huì)把它們當(dāng)成同一個(gè)序列化版本。 通常建議程序員為序列化類指定serialVersionUID指定值!如果程序員沒有為序列化類的serialVersionUID指定值,系統(tǒng)會(huì)該序列化類的serialVersionUID自動(dòng)分配一個(gè)值。無論程序員對(duì)該類進(jìn)行了怎樣怎么樣的修改(即使該修改對(duì)序列化沒有任何影響),系統(tǒng)也會(huì)自動(dòng)修改serialVersionUID的值;如果程序員主動(dòng)為序列化類的serialVersionUID分配值,則可以控制只有對(duì)該類的修改影響序列化機(jī)制才去修改serialVersionUID值。 36、hashCode()方法的作用?hashCode()方法與equals()方法相似,都是來自java.lang.Object類的方法,都允許用戶定義的子類重寫這兩個(gè)方法。 一般來說,equals()這個(gè)方法是給用戶調(diào)用的,如果你想根據(jù)自己的業(yè)務(wù)規(guī)則來判斷兩個(gè)對(duì)象是否相等,你可以重寫equals()方法。簡單來講,equals方法主要是用來判斷從表面上看或者從內(nèi)容上看,兩個(gè)對(duì)象是不是相等。 而hashCode()方法通常是給其他類來調(diào)用的,比如當(dāng)我們要把兩個(gè)對(duì)象放入HashSet時(shí),由于HashSet要求兩個(gè)對(duì)象不能相等,而HashSet判斷兩個(gè)對(duì)象是否相等的標(biāo)準(zhǔn)是通過equals()比較返回false、或兩個(gè)對(duì)象的hashCode()方法返回值不相等——只要滿足任意一個(gè)條件都可會(huì)認(rèn)為兩個(gè)對(duì)象不相等。 從這個(gè)角度來看,我們可以把hashCode()方法的返回值當(dāng)成這個(gè)對(duì)象的“標(biāo)識(shí)符”,如果兩個(gè)對(duì)象的hashCode()相等,即可認(rèn)為這兩個(gè)對(duì)象是相等的。因此當(dāng)我們重寫一個(gè)類的equals()方法時(shí),也應(yīng)該重寫它的hashCode()方法,而且這兩個(gè)方法判斷兩個(gè)對(duì)象相等的標(biāo)準(zhǔn)也應(yīng)該是一樣的。 37、編寫一個(gè)函數(shù)將一個(gè)十六進(jìn)制數(shù)的字符串參數(shù)轉(zhuǎn)換成整數(shù)返回。String str = "13abf"; int len = str.length(); int sum = 0; for(int i = 0 ; i < len ; i++) { char c = str.charAt(len - 1 - i); int n = Character.digit(c ,16); sum += n * (1 << (4 * i)); } System.out.println(sum); 其實(shí),也可以用Integer.parseInt(str,16),但面試官很可能是想考我們的編碼基本功。 38、銀行還款問題銀行貸款的還款方式中最常用的是一種叫“等額本息”,還款法,即借款人在約定還款期限內(nèi)的每一期(月)歸還的金額(產(chǎn)生的利息+部分本金)都是相等的,現(xiàn)有一筆總額為T元的N年期住房貸款,年利率為R,要求算出每一期的還款的本金和利息總額,請(qǐng)寫出解決思路和任意一種編程語言實(shí)現(xiàn)的主要代碼。 思路:既然是按月還款,那就要將N年按月來計(jì)算,即要還N*12個(gè)月,這樣就可以求出每月要還的本金。由于每月要還的那部分本金所欠的時(shí)間不同,所以,它們所產(chǎn)生的利息是不同的,該部分本金的利息為:部分本金額*所欠月數(shù)*月利率。應(yīng)該是這么個(gè)算法,如果利息還計(jì)利息,如果月還款不按年利率來算,老百姓算不明白的。 int monthMoney = T/N/12; float monthRate = R/12; int totalMonth = N * 12; float totalRate = 0; for(int i = 1 ; i <= totalMonth ; i++) { totalRate += monthMoney * monthRate * i; } int result = monthMoney + totalRate/N/12; 39、任意數(shù)字序列“123456”之類,輸出它們所有的排列組合String str = "fkjavx"; char[] arr = str.toCharArray(); String[] result = {""}; for (int i = 0 ; i < arr.length ; i++ ) { String[] tmp = new String[result.length * (arr.length - i)]; int counter = 0; for (int j = 0 ; j < result.length; j++ ) { for (int k = 0 ; k < arr.length ; k++ ) { System.out.println(j + " ----" + result[j]); if(!result[j].contains(arr[k] + "")) { tmp[counter++] = result[j] + arr[k]; } } } result = tmp; System.out.println(java.util.Arrays.toString(result)); } System.out.println(java.util.Arrays.toString(result)); 40、構(gòu)造器Constructor是否可被override?構(gòu)造器Constructor不能被繼承,因此不能重寫(Override),但可以被重載(Overload)。 41、接口是否可繼承接口? 抽象類是否可實(shí)現(xiàn)(implements)接口? 抽象類是否可繼承具體類(concrete class)? 抽象類中是否可以有靜態(tài)的main方法?接口可以繼承接口。抽象類可以實(shí)現(xiàn)(implements)接口,抽象類也可以繼承具體類。抽象類中可以有靜態(tài)的main方法。 只要記住《瘋狂Java講義》中的歸納:抽象類的特征是有得有失,得到的功能是抽象類可以擁有抽象方法(當(dāng)然也可以沒有);失去的功能的是抽象類不能創(chuàng)建實(shí)例了。至于其他的,抽象類與普通類在語法上大致是一樣的。 42、寫clone()方法時(shí),通常都有一行代碼,是什么?clone()有默認(rèn)行為:super.clone();,因?yàn)槭紫纫迅割愔械某蓡T復(fù)制到位,然后才是復(fù)制自己的成員。 43、abstract class和interface有什么區(qū)別?含有abstract修飾符的class即為抽象類,abstract 類不能創(chuàng)建的實(shí)例對(duì)象。含有abstract方法的類必須定義為abstract class,abstract class類中的方法不必是抽象的。abstract class類中定義抽象方法必須在具體(Concrete)子類中實(shí)現(xiàn),所以,不能有抽象構(gòu)造方法或抽象靜態(tài)方法。如果的子類沒有實(shí)現(xiàn)抽象父類中的所有抽象方法,那么子類也必須定義為abstract類型。 接口(interface)可以說成是抽象類的一種特例,接口中的所有方法都必須是抽象的。接口中的方法定義默認(rèn)為public abstract類型,接口中的成員變量類型默認(rèn)為public static final。 需要說明的是,Java 8增強(qiáng)后的接口可以定義默認(rèn)方法(使用default修飾的方法)和類方法(使用static修飾的方法),接口中的默認(rèn)方法和類方法都不再是抽象方法,都需要提供方法體。Java 9則允許接口中定義private方法,private方法可以擁有方法體。 下面比較一下兩者的語法區(qū)別: 1.抽象類可以有構(gòu)造器,接口中不能有構(gòu)造器。 2.抽象類中可以有普通成員變量,接口中沒有普通成員變量 3.抽象類中可以包含非抽象的普通方法,接口中的所有方法必須都是抽象的,不能有非抽象的普通方法。Java 8增強(qiáng)的接口可擁有默認(rèn)方法和類方法,接口中的默認(rèn)方法和類方法都不再是抽象方法,都需要提供方法體。Java 9則允許接口中定義private方法,private方法可以擁有方法體。 4. 抽象類中的抽象方法的訪問類型可以是public,protected和默認(rèn)訪問權(quán)限。但接口中的方法只能是public的。如果普通實(shí)例方法則必須是抽象方法,如果是默認(rèn)方法則必須使用default修飾;如果是類方法則必須使用static修飾。 5. 抽象類和接口中都可以包含靜態(tài)成員變量,抽象類中的靜態(tài)成員變量的訪問類型可以任意,但接口中定義的變量只能是public static final類型,并且默認(rèn)即為public static final類型。 7. 一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,但只能繼承一個(gè)抽象類。 下面接著再說說兩者在應(yīng)用上的區(qū)別: 接口更多的是在系統(tǒng)架構(gòu)設(shè)計(jì)方法發(fā)揮作用,接口體現(xiàn)的是一種規(guī)范。而抽象類在代碼實(shí)現(xiàn)方面發(fā)揮作用,可以實(shí)現(xiàn)代碼的重用,例如,模板模式是抽象類的一個(gè)典型應(yīng)用,假設(shè)項(xiàng)目中需要使用大量的DAO組件,這些DAO組件通常都具有增、刪、改、查等基本方法,因此我們就可以定義一個(gè)抽象的DAO基類,然后讓其他DAO組件來繼承這個(gè)DAO基類,把這個(gè)DAO基類當(dāng)成模板使用。 44、abstract的method是否可同時(shí)是static,是否可同時(shí)是native,是否可同時(shí)是synchronized?abstract的method 不可以是static的,因?yàn)槌橄蟮姆椒ㄊ且蛔宇悓?shí)現(xiàn)的,而static與子類扯不上關(guān)系! native方法表示該方法要用另外一種依賴平臺(tái)的編程語言實(shí)現(xiàn)的,不存在著被子類實(shí)現(xiàn)的問題,所以,它也不能是抽象的,不能與abstract混用。 關(guān)于synchronized與abstract不能同時(shí)使用。因?yàn)閟ynchronized修飾一個(gè)方法時(shí),表明將會(huì)使用該方法的調(diào)用者作為同步監(jiān)視器,但對(duì)于一個(gè)abstract方法而言,它所在類是一個(gè)抽象類,抽象類也無法創(chuàng)建實(shí)例,因此也就無法確定synchronized修飾方法時(shí)的同步監(jiān)視器了,因此synchronized與abstract不能同時(shí)使用。 45、什么是內(nèi)部類? Static Nested Class 和 Inner Class的不同。內(nèi)部類就是在一個(gè)類的內(nèi)部定義的類,非靜態(tài)內(nèi)部類中不能定義靜態(tài)成員。靜態(tài)內(nèi)部類不能訪問外部類的靜態(tài)成員。 內(nèi)部類作為其外部類的一個(gè)成員,因此內(nèi)部類可以直接訪問外部類的成員。但有一點(diǎn)需要指出:靜態(tài)成員不能訪問非靜態(tài)成員,因此靜態(tài)內(nèi)部類不能訪問外部類的非靜態(tài)成員。 如果內(nèi)部類使用了static修飾,那這個(gè)內(nèi)部類就是靜態(tài)內(nèi)部類,也就是所謂的static Nested Class;如果內(nèi)部類沒有使用修飾,它就是Inner Class。除此之外,還有一種局部內(nèi)部類:在方法中定義的內(nèi)部類就是局部內(nèi)部類,局部內(nèi)部類只在方法中有效。 對(duì)于Static Nested Class來說,它使用了static修飾,因此它屬于類成員,Static Nested Class的實(shí)例只要寄生在外部類中即可。因此使用Static Nested Class十分方便,開發(fā)者可以把外部類當(dāng)成Static Nested Class的一個(gè)包即可。 對(duì)于Inner Class而言,它是屬于實(shí)例成員,因此Inner Class的實(shí)例必須寄生在外部類的實(shí)例中,因此程序在創(chuàng)建Inner Class實(shí)例之前,必須先獲得一個(gè)它所寄生的外部類的實(shí)例。否則程序無法創(chuàng)建Inner Class的實(shí)例。例如如下代碼: class Outer { class Inner { } } public class Test { public static void main(String[] args) { Outer.Inner inner; Outer outer = new Outer(); // 必須先獲得外部類的實(shí)例,然后才能調(diào)用構(gòu)造器。 inner = outer.new Inner(); } } 46、內(nèi)部類可以引用它的外部類的成員嗎?有沒有什么限制?內(nèi)部類可以訪問所在外部類的成員。 但有一點(diǎn)需要注意:靜態(tài)成員不能訪問非靜態(tài)成員,因此靜態(tài)內(nèi)部類(屬于靜態(tài)成員)就不能訪問外部類的非靜態(tài)成員。 47、Anonymous Inner Class (匿名內(nèi)部類) 是否可以extends(繼承)其它類,是否可以implements(實(shí)現(xiàn))interface(接口)?匿名內(nèi)部類必須顯式繼承某個(gè)父類或?qū)崿F(xiàn)某個(gè)接口,但匿名內(nèi)部類只能顯式繼承某個(gè)父類或?qū)崿F(xiàn)某個(gè)接口。下面是匿名內(nèi)部類特殊的語法: new 父類|父接口() { 類體實(shí)現(xiàn)部分 } 從上面語法不難看出,匿名內(nèi)部類必須繼承其他類或?qū)崿F(xiàn)其他接口。 對(duì)于使用函數(shù)式接口(只包含一個(gè)抽象方法的接口),Java 8提供了簡潔的Lambda表達(dá)式來創(chuàng)建對(duì)象。對(duì)于之包含一個(gè)抽象方法的函數(shù)式接口而言,可使用如下代碼來創(chuàng)建實(shí)現(xiàn)該接口的對(duì)象 (被實(shí)現(xiàn)的抽象方法的形參列表) -> { 實(shí)現(xiàn)抽象方法的方法體; } 從上面語法規(guī)范可以看出,Lambda表達(dá)式就是負(fù)責(zé)實(shí)現(xiàn)函數(shù)式接口中的抽象方法,系統(tǒng)就根據(jù)Lambda表達(dá)式來創(chuàng)建實(shí)現(xiàn)函數(shù)式接口的對(duì)象。 48、super.getClass()方法調(diào)用下面程序的輸出結(jié)果是多少? import java.util.Date; public class Test extends Date{ public static void main(String[] args) { new Test().test(); } public void test(){ System.out.println(super.getClass().getName()); } } 程序輸出的是Test。 《瘋狂Java講義》(第2版)中有關(guān)于super關(guān)鍵字很透徹的解釋:super它只是一個(gè)限定詞,當(dāng)用super引用時(shí),它也是引用當(dāng)前對(duì)象本身,只是super只是限定了訪問當(dāng)前對(duì)象從父類那里繼承得到成員變量或方法。 如果需要訪問父類的類名,應(yīng)該使用如下語法: super.getClass().getSuperclass().getName() 49、JDK中哪些類是不能繼承的?使用final修飾的類都不可以被繼承。 實(shí)際上即使自己開發(fā)的類,也可以通過使用final修飾來阻止被繼承。通過使用final修飾的類被稱為最終類,最終類不能派生子類,這樣該類就被完全地封閉起來了,不會(huì)有子類來重寫它的方法,因此更加安全。 50、String s = "Hello";s = s + " world!";這兩行代碼執(zhí)行后,原始的String對(duì)象中的內(nèi)容到底變了沒有?沒有。因?yàn)镾tring被設(shè)計(jì)成不可變類(immutable),所以它的所有對(duì)象都是不可變對(duì)象。在這段代碼中,s原先指向一個(gè)String對(duì)象,內(nèi)容是 "Hello",然后我們對(duì)s進(jìn)行了+操作,那么s所指向的那個(gè)對(duì)象是否發(fā)生了改變呢?答案是沒有。這時(shí),s不指向原來那個(gè)對(duì)象了,而指向了另一個(gè) String對(duì)象,內(nèi)容為"Hello world!",原來那個(gè)對(duì)象還存在于內(nèi)存之中,只是s這個(gè)引用變量不再指向它了。 通過上面的說明,我們很容易導(dǎo)出另一個(gè)結(jié)論,如果經(jīng)常對(duì)字符串進(jìn)行各種各樣的修改,那么使用String來代表字符串的話會(huì)引起很大的內(nèi)存開銷。因?yàn)?String對(duì)象建立之后不能再改變,所以對(duì)于每一個(gè)不同的字符串,都需要一個(gè)String對(duì)象來表示。這時(shí),應(yīng)該考慮使用StringBuffer類,它允許修改,而不是每個(gè)不同的字符串都要生成一個(gè)新的對(duì)象。并且,這兩種類的對(duì)象轉(zhuǎn)換十分容易。 實(shí)際上,當(dāng)我們需要一個(gè)字符串對(duì)象時(shí),應(yīng)該使用如下語法來創(chuàng)建String對(duì)象: Sring s = "fkjava.org"; 也就是直接使用字符串直接量的語法。而不是: String s = new String("fkjava.org"); 對(duì)于第二種語法而言,每次都會(huì)調(diào)用構(gòu)造器生成新的String對(duì)象,性能低下且內(nèi)存開銷大,并且沒有意義,因?yàn)镾tring對(duì)象不可改變,所以對(duì)于內(nèi)容相同的字符串,只要一個(gè)String對(duì)象來表示就可以了。 基于這樣一種想法,Java提供了字符串緩存池來管理字符串直接量,當(dāng)程序多次用到同一個(gè)字符串直接量時(shí),系統(tǒng)會(huì)讓它們都引用字符串緩存池中的同一個(gè)String對(duì)象。因此使用在程序中使用字符串直接量可以充分利用這個(gè)特性來降低系統(tǒng)內(nèi)存開銷,提高程序性能。 51、是否可以繼承String類?String類是final類,不可以被繼承。 52、如何把一段逗號(hào)分割的字符串轉(zhuǎn)換成一個(gè)數(shù)組?A. 在以前的時(shí)候,Java提供了一個(gè)StingTokenizer工具類來處理字符串分割的問題。比如使用如下語法: StringTokenizer st = new StringTokenizer("this,is,a,test" , ","); while (st.hasMoreTokens()) { System.out.println(st.nextToken()); } 這樣程序?qū)?huì)輸出 this is a test B. 后來Java為String類增加了正則表達(dá)式支持,StingTokenizer基本上沒用了。因此上面代碼可以簡寫為: String [] result = "this,is,a,test".split(","); 其中result數(shù)組中就存放了this、is、a、test等字符串元素。 53、下面這條語句一共創(chuàng)建了多少個(gè)對(duì)象:String s="a"+"b"+"c"+"d";答:對(duì)于如下代碼: String s1 = "a"; String s2 = s1 + "b"; String s3 = "a" + "b"; System.out.println(s2 == "ab"); System.out.println(s3 == "ab"); 第一條語句打印的結(jié)果為false,第二條語句打印的結(jié)果為true。 Java會(huì)在編譯時(shí)對(duì)字符串相加進(jìn)行優(yōu)化處理,如果整個(gè)表達(dá)式中所有參與運(yùn)算的都是字符串直接量,Java會(huì)在編譯時(shí)就把這個(gè)表達(dá)式的值計(jì)算出來,然后直接將結(jié)果賦值給字符串引用變量。因此上面題目中定義的String s = "a" + "b" + "c" + "d";實(shí)際上相當(dāng)于直接定義了"abcd"的字符串直接量,所以,上面的代碼應(yīng)該只創(chuàng)建了一個(gè)String對(duì)象。 而且這個(gè)字符串直接量會(huì)被放入字符串緩存池中。如下兩行代碼, String s = "a" + "b" + "c" + "d"; System.out.println(s == "abcd"); 由于s引用了字符串緩存池中的"abcd"字符串,因此上面輸出結(jié)果應(yīng)該為true。 54、Collection框架中實(shí)現(xiàn)比較要實(shí)現(xiàn)什么接口Java集合框架中需要比較大小的集合包括TreeMap、TreeSet,其中TreeMap會(huì)根據(jù)key-value對(duì)中key的大小進(jìn)行排序,而TreeSet則會(huì)對(duì)集合元素進(jìn)行排序。 因此TreeMap的key、TreeSet的集合元素,都需要可以比較大小。集合框架中之比較大小的有兩種方式: A.自然排序:對(duì)于自然排序來說,要求TreeMap中的所有key都實(shí)現(xiàn)Comparable接口,實(shí)現(xiàn)該接口時(shí)需要實(shí)現(xiàn)一個(gè)int compareTo(T o)方法,用于判斷當(dāng)前對(duì)象與o對(duì)象之間的大小關(guān)系。如果該方法返回正整數(shù),則說明當(dāng)前對(duì)象大于被比較的o對(duì)象;如果該方法返回0,說明兩個(gè)對(duì)象相等;如果該方法返回負(fù)整數(shù),則說明當(dāng)前對(duì)象小于被比較的o對(duì)象;JDK的很多類都已經(jīng)實(shí)現(xiàn)了Comparable接口,例如String、Date、BigDecimal等。 B.定制排序:定制排序需要在創(chuàng)建TreeMap或TreeSet時(shí)傳入一個(gè)Comparator對(duì)象,此時(shí)TreeMap或TreeSet不再要求key、集合元素本身是可比較大小的,而是由Comparator來負(fù)責(zé)比較集合元素的大小。Comparator本身只是一個(gè)接口,因此創(chuàng)建Comparator對(duì)象只能是創(chuàng)建它的實(shí)現(xiàn)類的對(duì)象,Comparator的實(shí)現(xiàn)類需要實(shí)現(xiàn)int compare(T o1, T o2)方法,該方法用于判斷o1、o2兩個(gè)對(duì)象的大小,如果該方法返回正整數(shù),則說明o1大于o2、如果該方法返回負(fù)整數(shù),則說明o1小于o2、如果返回0,則說明兩個(gè)對(duì)象相等。 55、ArrayList和Vector的區(qū)別這兩個(gè)類都實(shí)現(xiàn)了List接口(List接口繼承了Collection接口),他們都是有序集合,即存儲(chǔ)在這兩個(gè)集合中的元素的位置都是有順序的,相當(dāng)于一種動(dòng)態(tài)的數(shù)組,以后可以按位置索引號(hào)取出某個(gè)元素,并且其中的數(shù)據(jù)是允許重復(fù)的——這是由List集合規(guī)范制訂的。 ArrayList與Vector底層都是基于數(shù)組的,因此它們的實(shí)現(xiàn)代碼也大致相似。區(qū)別在于Vector是一個(gè)古老的集合,從JDK1.0開始就有了,因此它包含了大量方法名很長的方法,JDK 1.2開始引入集合框架,引入List接口,才讓Vector實(shí)現(xiàn)了List接口,因此又增加了一些List接口中定義的方法??傮w來說,ArrayList可以完全代替Vector,除了在一些很古老的API中強(qiáng)制要求使用Vector之外。 Vector還有一個(gè)特征:它是線程安全的,因此性能比較差。而ArrayList并不是線程安全的,因此性能較好。實(shí)際上即使需要在多線程環(huán)境下使用List集合,也應(yīng)該選擇ArrayList,而不是Vector,因?yàn)镴ava還提供了一個(gè)Collections工具類,它可以把ArrayList包裝成線程安全的集合類,例如如下代碼: List list = Collections.synchronizedList(new ArrayList()); 56、HashMap和Hashtable的區(qū)別HashMap與Hashtable的區(qū)別類似于ArrayList與Vector的區(qū)別。 Hashtable與Vector都是JDK 1.0就有一個(gè)一個(gè)古老的集合,因此Hashtable是一個(gè)繼承自Dictionary的古老集合。 從JDK 1.2引入集合框架的Map接口之后,Java讓Hashtable也實(shí)現(xiàn)了Map接口,因此Hashtable也新增實(shí)現(xiàn)了一些Map接口中定義的方法。實(shí)際上Hashtable與HashMap底層的實(shí)現(xiàn)很相似,它們都是基于Hash表的實(shí)現(xiàn)。 HashMap與Hashtable的區(qū)別主要有如下兩點(diǎn): A.HashMap允許使用null作為key或value,而Hashtable不允許。 B.HashMap是線程不安全的,因此性能較好;但Hashtable是線程安全的,因此性能較差。 實(shí)際上,即使在多線程環(huán)境下,Java提供了Collections工具類把HashMap包裝成線程安全的類,因此依然應(yīng)該使用HashMap,如下代碼所示: Map map = Collections. synchronizedMap(new HashMap()); 簡單的說,編程時(shí)應(yīng)該盡量避免使用Hashtable,除非在一個(gè)古老的API中強(qiáng)制要求Hashtable。 57、List 和 Map 區(qū)別?表面來看,List是一個(gè)只是存放單個(gè)元素的集合,List集合所包含的元素可以重復(fù),元素按放入的先后順序來存放,程序可以通過元素的索引來讀取元素,因此List相當(dāng)于一個(gè)動(dòng)態(tài)數(shù)組;Map則是一個(gè)存放key-value對(duì)的集合,Map里存放的key-value對(duì)是無序的,Map包含的key是不允許重復(fù)的。程序可以key來取出該key對(duì)應(yīng)的value。 深入闡述:如果換個(gè)角度來看,完全可以把List當(dāng)成Map來看,List相當(dāng)于一個(gè)key都是int類型的Map,程序通過元素的索引(相當(dāng)于通過int類型的key)來讀取List集合的元素時(shí),完全也可以當(dāng)成Map根據(jù)key來讀取value。從另一個(gè)角度來看,Map也可以當(dāng)成元素索引可以是任意類型的List集合。 58、List, Set, Map是否繼承自Collection接口?List、Set是,Map不是。 59、List、Map、Set三個(gè)接口,存取元素時(shí),各有什么特點(diǎn)?Set集合是最接近Collection的集合,因此Set集合幾乎沒有在Collection增加什么方法。Set集合代表了元素?zé)o序、元素不允許重復(fù)的集合(Set只是在Collection規(guī)范上增加了元素不允許重復(fù)的約束)。 List集合則在Collection的基礎(chǔ)上為元素增加了索引的特性,因此List集合代表了集合元素有序、集合元素可以重復(fù)的集合。 Map則代表了存放key-value對(duì)的集合,程序可以通過key來獲取其中的value。 就Set集合來說,對(duì)于開發(fā)者而言,它的集合元素是無序的,似乎顯得有些雜亂、無規(guī)律,但對(duì)計(jì)算機(jī)而言這不可能,因此計(jì)算機(jī)需要快速存、取Set集合中的元素。Set集合有兩個(gè)實(shí)現(xiàn)類:HashSet與TreeSet,其中HashSet底層其實(shí)使用了一個(gè)數(shù)組來存放所有集合元素,然后通過Hash算法來決定每個(gè)集合元素在底層數(shù)組中存放位置,因此HashSet對(duì)集合元素的存、取就是Hash算法+數(shù)組存、取——也就是說HashSet只比數(shù)組存、取多了些Hash算法開銷,因此性能非??臁reeSet底層則完全是一個(gè)紅黑樹,因此紅黑樹是折衷平衡的排序二叉樹,它底層沒有數(shù)組開銷,存、取元素時(shí)都是基于紅黑樹算法的,因此內(nèi)存開銷較小,但性能略差。 對(duì)于List集合而言,主要有兩個(gè)實(shí)現(xiàn):ArrayList與LinkedList,其中ArrayList底層是基于數(shù)組的,而且ArrayList存、取元素本身就是通過元素索引來進(jìn)行的,因此ArrayList對(duì)元素的存、取性能非常好,幾乎等同于存、取數(shù)組元素。但則添加、刪除元素時(shí)需要對(duì)數(shù)組元素進(jìn)行“整體搬家”,因此添加、刪除元素時(shí)性能較差。而LinkedList底層則是基于一個(gè)鏈表實(shí)現(xiàn)的,當(dāng)從鏈表中存、取元素時(shí),需要定位元素的位置,系統(tǒng)開銷較大。但添加、刪除元素時(shí),只要修改元素的引用(相當(dāng)于指針)即可,因此性能非常好。 對(duì)于Map集合而言,其底層存、取性能與Set集合完全一樣。其實(shí)Set集合本身就是基于Map實(shí)現(xiàn)的——如果我們把Map集合的所有value都當(dāng)成空對(duì)象處理、只考慮Map集合的key,Map集合就變成了Set集合。換個(gè)角度來看,如果我們向Set集合中添加的對(duì)象是key-value所組成的Entry對(duì)象,那么Set集合也就變成了Map集合。 60、說出ArrayList,Vector, LinkedList的存儲(chǔ)性能和特性ArrayList和Vector都是使用數(shù)組方式存儲(chǔ)數(shù)據(jù),此數(shù)組元素?cái)?shù)大于實(shí)際存儲(chǔ)的數(shù)據(jù)以便增加和插入元素,它們都允許直接按序號(hào)索引元素,但是插入元素要涉及數(shù)組元素移動(dòng)等內(nèi)存操作,所以索引數(shù)據(jù)快而插入數(shù)據(jù)慢,Vector由于使用了synchronized方法(線程安全),通常性能上較ArrayList差,而LinkedList使用雙向鏈表實(shí)現(xiàn)存儲(chǔ),按序號(hào)索引數(shù)據(jù)需要進(jìn)行前向或后向遍歷,但是插入數(shù)據(jù)時(shí)只需要記錄本項(xiàng)的前后項(xiàng)即可,所以插入速度較快。 LinkedList也是線程不安全的,LinkedList提供了一些方法,使得LinkedList可以被當(dāng)作棧和隊(duì)列來使用。 實(shí)際上Java提供了Collections工具類,它可以把ArrayList、LinkedList包裝成線程安全的集合,因此實(shí)際編程中應(yīng)該避免使用Vector。 61、去掉一個(gè)Vector集合中重復(fù)的元素Vector newVector = new Vector(); for (int i = 0 ; i < vector.size() ; i++) { Object obj = vector.get(i); if(!newVector.contains(obj)) { newVector.add(obj); } } 另外,還有一種更見簡單的方式:將Vector添加到HashSet,例如如下代碼: HashSet set = new HashSet(vector); 但上面代碼將會(huì)導(dǎo)致Vector中元素丟失順序。 62、Set里的元素是不能重復(fù)的,那么用什么方法來區(qū)分重復(fù)與否呢? 是用==還是equals()? 它們有何區(qū)別?說明:其實(shí)這個(gè)題目本身有問題!因?yàn)镾et只是一個(gè)接口,它的不同實(shí)現(xiàn)類判斷元素是否相等的標(biāo)準(zhǔn)是不同的?;\統(tǒng)地說,Set里的元素是不能重復(fù)的,判斷元素重復(fù)使用equals()。而不是==。 對(duì)于HashSet而言,判斷兩個(gè)對(duì)象是否相等是通過equals()和hashCode()方法,只要兩個(gè)對(duì)象通過 equals()比較返回false、或兩個(gè)對(duì)象的hashCode()不相等,那么HashSet就會(huì)把它們當(dāng)成不相同。 對(duì)于TreeSet而言,判斷兩個(gè)對(duì)象相等的唯一標(biāo)準(zhǔn)是:兩個(gè)對(duì)象通過compareTo(Object obj)比較是否返回0,與equals()方法無關(guān)。只要兩個(gè)對(duì)象通過compareTo(Object obj)比較沒有返回0,Java就會(huì)把它們當(dāng)成兩個(gè)對(duì)象處理——這一點(diǎn)是很多人容易誤解的,不過我們可以通過《瘋狂Java講義》中的一個(gè)示例來說明: class Z implements Comparable { int age; public Z(int age) { this.age = age; } // 重寫equals()方法,總是返回true public boolean equals(Object obj) { return true; } //重寫了compareTo(Object obj)方法,總是返回正整數(shù) public int compareTo(Object obj) { return 1; } } public class TreeSetTest2 { public static void main(String[] args) { TreeSet set = new TreeSet(); Z z1 = new Z(6); set.add(z1); //輸出true,表明添加成功 System.out.println(set.add(z1)); //① //下面輸出set集合,將看到有兩個(gè)元素 System.out.println(set); //修改set集合的第一個(gè)元素的age變量 ((Z)(set.first())).age = 9; //輸出set集合的最后一個(gè)元素的age變量,將看到也變成了9 System.out.println(((Z)(set.last())).age); } } 上面程序中兩個(gè)Z對(duì)象通過equals()比較總會(huì)返回true,但通過compareTo(Object obj)比較總是不會(huì)返回0,因此兩次向TreeSet中添加同一個(gè)元素,TreeSet會(huì)把它們當(dāng)成不同的對(duì)象進(jìn)行處理,最后TreeSet集合中會(huì)顯示有兩個(gè)對(duì)象,但實(shí)際上是同一個(gè)對(duì)象。 63、你所知道的集合類都有哪些?主要方法?最常用的集合接口是 Set、List、Queue,它們都是Collection的子接口,除此之外還有Map接口。 對(duì)于Set集合而言,它的常用實(shí)現(xiàn)類包括HashSet與TreeSet。HashSet還有一個(gè)子類:LinkedHashSet。 對(duì)于List集合而言,它的常用實(shí)現(xiàn)類包括ArrayList、Vector與LinkedList。 對(duì)于Queue集合而言,它有一個(gè)子接口Deque(代表雙端隊(duì)列),它的常用實(shí)現(xiàn)類包括ArrayDeque與LinkedList。 對(duì)于Map集合而言,它的常用實(shí)現(xiàn)類是HashMap與TreeMap。HashMap還有一個(gè)子類:LinkedHashMap。 至于這些集合的方法,由于集合類也就是所謂的“容器類”,因此它的方法無非就是向容器中添加、刪除、取出、遍歷元素的方法。 對(duì)于List集合而言,由于它的集合元素都有有序的、有索引的,因此它包括了大量根據(jù)索引來添加、刪除、取出集合元素的方法。 對(duì)于Deque集合而言,由于它是雙端隊(duì)列,即可當(dāng)成隊(duì)列使用,也可當(dāng)成棧使用,因此它增加棧、隊(duì)列的方法,如offer、peek、push、pop等。 對(duì)Map而言,它所包含的無非就是根據(jù)key來添加、刪除、取出value的方法。 64、兩個(gè)對(duì)象值相同(x.equals(y) == true),但卻可有不同的hash code,這句話對(duì)不對(duì)?對(duì)。因?yàn)閑quals()方法可以由開發(fā)者重寫,hashCode()方法也可以由開發(fā)者來重寫,因此它們是否相等并沒有必然的關(guān)系。 一般來說,如果對(duì)象要保存在HashSet或作為HashMap的key中,它們通過equals()比較相等,那么它們的hashCode()返回值也應(yīng)該相等。 65、TreeSet里面放對(duì)象,如果同時(shí)放入了父類和子類的實(shí)例對(duì)象,那比較時(shí)使用的是父類的compareTo()方法,還是使用的子類的compareTo()方法,還是拋異常!根據(jù)TreeSet底層的實(shí)現(xiàn):TreeSet底層的實(shí)現(xiàn)就是紅黑樹,因此當(dāng)程序向TreeSet中添加集合元素時(shí),程序會(huì)多次調(diào)用該對(duì)象的compareTo()方法與TreeSet中的集合元素進(jìn)行比較,直到找到該元素在紅黑樹中應(yīng)當(dāng)所在節(jié)點(diǎn)位置。因此該問題的答案是:當(dāng)前正在添加父類對(duì)象就多次調(diào)用父類對(duì)象的compareTo()方法;當(dāng)前正在添加子類對(duì)象就多次調(diào)用子類對(duì)象的compareTo()方法。 至于程序是否拋出異常,則取決于compareTo()方法的實(shí)現(xiàn),如果子類在實(shí)現(xiàn)compareTo()方法時(shí),試圖把被比較對(duì)象轉(zhuǎn)換為子類對(duì)象之后再進(jìn)行比較——如果TreeSet集合中已經(jīng)包括了父類對(duì)象,這就會(huì)引起ClassCastException。 示例代碼如下: class A implements Comparable { int age; public A(int age) { this.age = age; } public int compareTo(Object obj) { System.out.println("AAAAAAAAAA"); A target = (A)obj; return age > target.age ? 1 : age < target.age ? -1 : 0; } public String toString() { return getClass() + ",age:" + age; } } class B extends A implements Comparable { public B(int age) { super(age); } public int compareTo(Object obj) { System.out.println("BBBBBBBBB"); A target = (A)obj; return age > target.age ? 1 : age < target.age ? -1 : 0; } } public class TreeSetTest2 { public static void main(String[] args) { TreeSet set = new TreeSet(); set.add(new A(3)); set.add(new B(1)); set.add(new A(2)); for(Iterator it = set.iterator(); it.hasNext() ;) { System.out.println(it.next()); } } } 上面程序可以看到,輸出: AAAAAAAAAA BBBBBBBBB AAAAAAAAAA AAAAAAAAAA 第一次添加A對(duì)象,所以調(diào)用A對(duì)象的compareTo()方法;第二次添加B對(duì)象,所以程序調(diào)用了B對(duì)象的compareTo()方法;第三次再次添加A對(duì)象,由于集合中已經(jīng)有兩個(gè)對(duì)象,因此程序兩次調(diào)用了A對(duì)象的compareTo()方法與集合中的元素進(jìn)行比較。 66、說出一些常用的類,包,接口,請(qǐng)各舉5個(gè)常用的包有: java.lang包下包括Math、System、StringBuilder、StringBuffer、Runtime、Thread、Runnable等。 java.util包下包括List、Set、Map,以及這些接口的常用實(shí)現(xiàn)類:ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等。 java.io包下包括InputStream、OutputStream、Reader、Writer、FileInputStream、FileOutputStream、FileReader、FileWriter、BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter等 java.sql包下包括Connection、Statement、PreparedStatement、ResultSet等。 java.net包下包括Socket、ServerSocket、URL、URLConnection、DatagramPacket、DatagramSocket等。 如果為讓別人感覺你對(duì)Android很熟悉,還應(yīng)該增加一些Android常用的包、類,如: android.app包下有:Activity、ListActivty、TabActivity、AlertDialog、AlertDialog.Builder、Notification、Service等。 android.content包下有:ContentProvider、ContentResolver、ContentUris、ContentValues、Context等 android.database包下有Cursor等 android.database.sqlite包下有:SQLiteDatabase、SQLiteOpenHelper等 android.graphics包下有Bitmap、BitmapFactory、Canvas、Color、Matrix、Paint、Path等。 android.widget包下有TextView、Button、CheckBox、RadioButton、ListView、GridView、Spinner、Gallery等。 67、Java中有幾種類型的流?JDK為每種類型的流提供了一些抽象類以供繼承,請(qǐng)說出他們分別是哪些類?字節(jié)流,字符流。字節(jié)流由InputStream OutputStream派生出來,字符流由Reader、Writer派生出來。在java.io包中還有許多其他的流,主要是為了提高性能和使用方便。 Java的IO流還也分為節(jié)點(diǎn)流和過濾流,其中節(jié)點(diǎn)流是直接關(guān)聯(lián)到物理IO節(jié)點(diǎn)的流,而過濾流則需要建立在其他流的基礎(chǔ)之上。Java為過濾流提供了FilterInputStream、FilterOutputStream、FilterReader、FilterWriter這四個(gè)基類。 一般來說,建議讀者按《瘋狂Java講義精粹》的11.4節(jié)(或瘋狂Java講義15.4節(jié))關(guān)于IO流體系的表格進(jìn)行回答。 68、字節(jié)流與字符流的區(qū)別字節(jié)流和字符流區(qū)別非常簡單,它們的用法幾乎完全一樣,區(qū)別在于字節(jié)流和字符流所操作的數(shù)據(jù)單元不同:字節(jié)流操作的數(shù)據(jù)單元是8位的字節(jié),而字符流操作的數(shù)據(jù)單元是16位的字符。 字節(jié)流主要由InputStream和OutputStream作為基類,而字符流則主要由Reader和Writer作為基類。 字節(jié)流直接是基于字節(jié)進(jìn)行輸入、輸出的,因此它的適用性更廣。字符流則在處理文本內(nèi)容的的輸入、輸出時(shí)更加方便——不會(huì)出現(xiàn)讀取半個(gè)字符的情形。 Java提供了將字節(jié)流轉(zhuǎn)換為字符串的InputStreamReader和OutputStreamWriter,但沒有提供將字符流轉(zhuǎn)化為字節(jié)流的方法。因?yàn)椋鹤止?jié)流比字符流的使用范圍更廣,但字符流比字節(jié)流操作方便。如果有一個(gè)流已經(jīng)是字符流了,也就是說是一個(gè)用起來更方便的流,為什么要轉(zhuǎn)換成字節(jié)流呢?反之,如果現(xiàn)在有一個(gè)字節(jié)流,但我們知道這個(gè)字節(jié)流的內(nèi)容都是文本內(nèi)容,那么把它轉(zhuǎn)換成字符流來處理就會(huì)更方便一些,所以Java只提供了將字節(jié)流轉(zhuǎn)換字符流的轉(zhuǎn)換流,沒有提供將字符流轉(zhuǎn)換成字節(jié)流的轉(zhuǎn)換流。 69、什么是Java序列化,如何實(shí)現(xiàn)Java序列化?或者請(qǐng)解釋Serializable接口的作用。Java序列化的目標(biāo)是將對(duì)象保存到磁盤中,或允許在網(wǎng)絡(luò)中直接傳輸對(duì)象,對(duì)象序列化機(jī)制允許把內(nèi)存中的Java對(duì)象轉(zhuǎn)換成平臺(tái)無關(guān)的二進(jìn)制流,從而允許把這種二進(jìn)制流持久保存在磁盤上,通過網(wǎng)絡(luò)將這種二進(jìn)制流傳輸?shù)搅硪粋€(gè)網(wǎng)絡(luò)節(jié)點(diǎn)。其他程序一旦獲得了這種二進(jìn)制流(無論是從磁盤中獲取,還是通過網(wǎng)絡(luò)獲?。?,都可以將這種二進(jìn)制流恢復(fù)成原來的Java對(duì)象。 實(shí)現(xiàn)Java序列化有兩種方式: A.讓Java類實(shí)現(xiàn)Serializable接口。 B.讓Java類實(shí)現(xiàn)Externalizable接口,實(shí)現(xiàn)該接口時(shí)還必須實(shí)現(xiàn)readExternal()、writeExternal()這兩個(gè)方法。 一旦Java類實(shí)現(xiàn)了上面兩種接口,接下來程序中就可通過ObjectInputStream、ObjectOutputStream來讀取、保存Java對(duì)象。 Serializable接口只是一個(gè)標(biāo)記接口,實(shí)現(xiàn)該接口無需實(shí)現(xiàn)任何方法,實(shí)現(xiàn)了該接口的類就是可序列化的類。 序列化對(duì)于Java開發(fā)非常重要,例如在Web開發(fā)中,如果對(duì)象需要保存在了Session中,Tomcat在某些時(shí)候需要把Session中的對(duì)象序列化到硬盤,因此放入Session中的對(duì)象必須是可序列化的,要么實(shí)現(xiàn)Serializable接口,要么實(shí)現(xiàn)Externalizable接口。還有,如果一個(gè)對(duì)象要經(jīng)過網(wǎng)絡(luò)傳輸(比如RMI遠(yuǎn)程方法調(diào)用的形參或返回值),這個(gè)對(duì)象也應(yīng)該是可序列化的。 70、描述一下JVM加載class文件的原理機(jī)制?當(dāng)程序主動(dòng)使用某個(gè)類時(shí),如果該類還未被加載到內(nèi)存中,系統(tǒng)會(huì)通過 加載 連接 初始化 三個(gè)步驟來對(duì)該類進(jìn)行初始化,如果沒有意外,JVM將會(huì)連續(xù)完成這三個(gè)步驟,所以有時(shí)也把這三個(gè)步驟統(tǒng)稱為類加載或類初始化。 類加載指的是將類的class文件讀入內(nèi)存,并為之創(chuàng)建一個(gè)java.lang.Class對(duì)象,也就是說當(dāng)程序中使用任何類時(shí),系統(tǒng)都會(huì)為之建立一個(gè)java.lang.Class對(duì)象。 類的加載由類加載器完成,類加載器通常由JVM提供,這些類加載器也是我們前面所有程序運(yùn)行的基礎(chǔ),JVM提供的這些類加載器通常被稱為系統(tǒng)類加載器。除此之外,開發(fā)者可以通過繼承ClassLoader基類來創(chuàng)建自己的類加載器。 通過使用不同的類加載器,可以從不同來源加載類的二進(jìn)制數(shù)據(jù),通常有如下幾種來源: A.從本地文件系統(tǒng)來加載class文件,這是前面絕大部分類加載方式。 B.從JAR包中加載class文件,這種方式也是很常見的,前面介紹JDBC編程時(shí)用到的數(shù)據(jù)庫驅(qū)動(dòng)類就是放在JAR文件中,JVM可以從JAR文件中直接加載該class文件。 C.通過網(wǎng)絡(luò)加載class文件。 D.把一個(gè)Java源文件動(dòng)態(tài)編譯、并執(zhí)行加載。 當(dāng)JVM啟動(dòng)時(shí),會(huì)形成由三個(gè)類加載器組成的初始類加載器層次結(jié)構(gòu): 1.Bootstrap ClassLoader:根類加載器。 2.Extension ClassLoader:擴(kuò)展類加載器。 3.System ClassLoader:系統(tǒng)類加載器。 Bootstrap ClassLoader,被稱為引導(dǎo)(也稱為原始或根)類加載器。它負(fù)責(zé)加載Java的核心類。在Sun的JVM中,當(dāng)執(zhí)行java.exe的命令時(shí)使用-Xbootclasspath選項(xiàng)或使用-D選項(xiàng)指定sun.boot.class.path系統(tǒng)屬性值可以指定加載附加的類。 Extension Classloader,被稱為擴(kuò)展類加載器,它負(fù)責(zé)加載JRE的擴(kuò)展目錄(%JAVA_HOME%/jre/lib/ext或者由java.ext.dirs系統(tǒng)屬性指定的目錄)中JAR包的類。 通過這種方式,我們就可以為Java擴(kuò)展核心類以外的新功能,只要我們把自己開發(fā)的類打包成JAR文件,然后放入JAVA_HOME/jre/lib/ext路徑即可(對(duì)于筆者的安裝環(huán)境來說,擴(kuò)展路徑為:D:/Java/jdk1.8.0.5/jre/lib/ext)。 System Classloader,被稱為系統(tǒng)(也稱為應(yīng)用)類加載器,它負(fù)責(zé)在JVM啟動(dòng)時(shí)加載來自java命令的-classpath選項(xiàng)、java.class.path系統(tǒng)屬性,或CLASSPATH環(huán)境變量所指定的JAR包和類路徑。程序可以通過ClassLoader的靜態(tài)方法getSystemClassLoader()獲取系統(tǒng)類加載器。如果沒有特別指定,則用戶自定義的類加載器都以類加載器作為父加載器。 71、heap和stack有什么區(qū)別。stack內(nèi)存指的是程序進(jìn)入一個(gè)方法時(shí),系統(tǒng)會(huì)專門為這個(gè)方法分配一塊內(nèi)存空間,這塊內(nèi)存空間也被稱為該方法棧區(qū),該方法的棧區(qū)專門用于存儲(chǔ)該方法中定義的局部變量,包括基本類型的變量和引用變量。當(dāng)這個(gè)方法結(jié)束時(shí),該方法棧區(qū)將會(huì)自動(dòng)被銷毀,棧區(qū)中的所有局部變量都會(huì)隨之銷毀。 heap內(nèi)存是Java虛擬機(jī)擁有的內(nèi)存區(qū),所有Java對(duì)象都將被放在heap內(nèi)存內(nèi),位于heap內(nèi)存中的Java對(duì)象由系統(tǒng)的垃圾回收器負(fù)責(zé)跟蹤管理——也就是進(jìn)行垃圾回收,當(dāng)堆內(nèi)存中的Java對(duì)象沒有引用變量引用它時(shí),這個(gè)Java對(duì)象就變成了垃圾,垃圾回收期就會(huì)在合適的時(shí)候回收它。 72、try{}里有一個(gè)return語句,那么緊跟在這個(gè)try后的finally{}里的code會(huì)不會(huì)被執(zhí)行,什么時(shí)候被執(zhí)行,在return前還是后?肯定會(huì)執(zhí)行。finally{}塊的代碼只有在try{}塊中包含遇到System.exit(0);之類的導(dǎo)致Java虛擬機(jī)直接退出的語句才會(huì)不執(zhí)行。 當(dāng)程序執(zhí)行try{}遇到return時(shí),程序會(huì)先執(zhí)行return語句,但并不會(huì)立即返回——也就是把return語句要做的一切事情都準(zhǔn)備好,也就是在將要返回、但并未返回的時(shí)候,程序把執(zhí)行流程轉(zhuǎn)去執(zhí)行finally塊,當(dāng)finally塊執(zhí)行完成后就直接返回剛才return語句已經(jīng)準(zhǔn)備好的結(jié)果。 例如我們有如下程序: public class Test { public static void main(String[] args) { System.out.println(new Test().test());; } static int test() { int x = 1; try { return x; } finally { System.out.println("finally塊執(zhí)行:" + ++x); } } } 此時(shí)的輸出結(jié)果為: finally塊執(zhí)行:2 1 看到上面程序中finally塊已經(jīng)執(zhí)行了,而且程序執(zhí)行finally塊時(shí)已經(jīng)把x變量增加到2了。但test()方法返回的依然是1,這就是由return語句執(zhí)行流程決定的,Java會(huì)把return語句先執(zhí)行完、把所有需要處理的東西都先處理完成,需要返回的值也都準(zhǔn)備好之后,但是還未返回之前,程序流程會(huì)轉(zhuǎn)去執(zhí)行finally塊,但此時(shí)finally塊中的對(duì)x變量的修改已經(jīng)不會(huì)影響return要返回的值了。 但如果finally塊里也包含return語句,那就另當(dāng)別論了, 因?yàn)閒inally塊里的return語句也會(huì)導(dǎo)致方法返回,例如把程序該為如下形式: public class Test { public static void main(String[] args) { System.out.println(new Test().test());; } static int test() { int x = 1; try { return x++; } finally { System.out.println("finally塊執(zhí)行:" + ++x); return x; } } } 此時(shí)的輸出結(jié)果為: finally塊執(zhí)行:3 3 正如介紹的,程序在執(zhí)行return x++;時(shí),程序會(huì)把return語句執(zhí)行完成,只是等待返回,此時(shí)x的值已經(jīng)是2了,但程序此處準(zhǔn)備的返回值依然是1。接下來程序流程轉(zhuǎn)去執(zhí)行finally塊,此時(shí)程序會(huì)再次對(duì)x自加,于是x變成了3,而且由于finally塊中也有return x;語句,因此程序?qū)?huì)直接由這條語句返回了,因此上面test()方法將會(huì)返回3。 73、下面的程序代碼輸出的結(jié)果是多少?public class smallT { public static void main(String args[]) { smallT t = new smallT(); int b = t.get(); System.out.println(b); } public int get() { try { return 1 ; } finally { return 2 ; } } } 輸出結(jié)果是:2。 這個(gè)程序還是剛才介紹的return語句和finally塊的順序問題。 Java會(huì)把return語句先執(zhí)行完、把所有需要處理的東西都先處理完成,需要返回的值也都準(zhǔn)備好之后,但是還未返回之前,程序流程會(huì)轉(zhuǎn)去執(zhí)行finally塊。但如果在執(zhí)行finally塊時(shí)遇到了return語句,程序?qū)?huì)直接使用finally塊中的return語句來返回——因此上面程序?qū)?huì)輸出2。 74、final, finally, finalize的區(qū)別。final是一個(gè)修飾符,它可以修飾類、方法、變量。 final修飾類時(shí)表明這個(gè)類不可以被繼承。 final修飾方法時(shí)表明這個(gè)方法不可以被其子類重寫。 final修飾變量時(shí)可分為局部變量、實(shí)例變量和靜態(tài)變量,當(dāng)final修飾局部變量時(shí),該局部變量可以被一次賦值,以后該變量的值不能發(fā)生改變;當(dāng)final修飾實(shí)例變量時(shí),實(shí)例變量必須由程序員在構(gòu)造器、初始化塊、定義時(shí)這3個(gè)位置的其中之一指定初始值;當(dāng)final修飾靜態(tài)變量時(shí),靜態(tài)變量必須由程序在靜態(tài)初始化塊、定義時(shí)這2個(gè)位置的其中之一指定初始值。 finally是異常處理語句結(jié)構(gòu)的一部分,表示總會(huì)執(zhí)行的代碼塊。 finalize是Object類的一個(gè)方法,在垃圾收集器執(zhí)行的時(shí)候會(huì)調(diào)用被回收對(duì)象的此方法,可以覆蓋此方法提供垃圾收集時(shí)的其他資源回收。但實(shí)際上重寫該方法進(jìn)行資源回收并不安全,因?yàn)镴VM并不保證該方法總被調(diào)用。 75、運(yùn)行時(shí)異常與一般異常有何異同?Checked異常體現(xiàn)了Java的設(shè)計(jì)哲學(xué):沒有完善錯(cuò)誤處理的代碼根本就不會(huì)被執(zhí)行! 對(duì)于Checked異常的處理方式有兩種: A.當(dāng)前方法明確知道如何處理該異常,程序應(yīng)該使用try...catch塊來捕獲該異常,然后在對(duì)應(yīng)的catch塊中修補(bǔ)該異常。 B.當(dāng)前方法不知道如何處理這種異常,應(yīng)該在定義該方法時(shí)聲明拋出該異常。 運(yùn)行時(shí)異常指的就是RuntimeException或其子類的實(shí)例,運(yùn)行時(shí)異常則更加靈活,編譯器不會(huì)強(qiáng)制要求程序員必須處理該異常,運(yùn)行時(shí)異常可以既不顯式聲明拋出,也不使用try...catch進(jìn)行捕捉。但如果程序需要捕捉運(yùn)行時(shí)異常,也可以使用try...catch塊來捕捉運(yùn)行時(shí)異常。 運(yùn)行時(shí)異常的優(yōu)點(diǎn)在于靈活:程序員想處理就處理,不想處理直接忽略該異常即可。但由于編譯器不會(huì)強(qiáng)制檢查運(yùn)行時(shí)異常,因此程序完全有可能在運(yùn)行時(shí)因?yàn)檫@些異常而結(jié)束。常見的運(yùn)行時(shí)異常有NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException、ArithmeticException、IllegalArgumentException等。 76、error和exception有什么區(qū)別?Error錯(cuò)誤,一般是指虛擬機(jī)相關(guān)的問題,如系統(tǒng)崩潰、虛擬機(jī)出錯(cuò)誤、動(dòng)態(tài)鏈接失敗等,這種錯(cuò)誤無法恢復(fù)或不可能捕獲,將導(dǎo)致應(yīng)用程序中斷。通常應(yīng)用程序無法處理這些錯(cuò)誤,因此應(yīng)用程序不應(yīng)該試圖使用catch塊來捕獲Error對(duì)象。 由于編譯器會(huì)對(duì)Error進(jìn)行檢查,不會(huì)強(qiáng)制要求程序員必須處理Error,因此Error也被歸入unchecked異常分類中(另外:運(yùn)行時(shí)異常也屬于unchecked異常)。 Exception表示一種設(shè)計(jì)或?qū)崿F(xiàn)問題。也就是說,程序員應(yīng)該對(duì)這些情況進(jìn)行考慮、并提供相應(yīng)的處理。 77、Java中的異常處理機(jī)制的簡單原理和應(yīng)用。程序運(yùn)行過程中可能出現(xiàn)各種“非預(yù)期”情況,這些非預(yù)期情況可能導(dǎo)致程序非正常結(jié)束。為了提高程序的健壯性,Java提供了異常處理機(jī)制: try { s1... s2... s3... } catch(Exception ex) { // 對(duì)異常情況的修復(fù)處理 } 對(duì)于上面處理流程,當(dāng)程序執(zhí)行try塊里的s1、s2、s3遇到異常時(shí),Java虛擬機(jī)將會(huì)把這個(gè)異常情況封裝成異常對(duì)象,這個(gè)異常對(duì)象可以被后面對(duì)應(yīng)的catch塊捕捉到,這樣保證這些異常會(huì)得到合適的處理。 Java對(duì)異常進(jìn)行了分類,不同類型的異常分別用不同的Java類表示,所有異常的根類為java.lang.Throwable,Throwable下面又派生了兩個(gè)子類:Error和Exception,Error錯(cuò)誤,一般是指虛擬機(jī)相關(guān)的問題,如系統(tǒng)崩潰、虛擬機(jī)出錯(cuò)誤、動(dòng)態(tài)鏈接失敗等,這種錯(cuò)誤無法恢復(fù)或不可能捕獲,將導(dǎo)致應(yīng)用程序中斷。通常應(yīng)用程序無法處理這些錯(cuò)誤,因此應(yīng)用程序不應(yīng)該試圖使用catch塊來捕獲Error對(duì)象。 Exception表示一種設(shè)計(jì)或?qū)崿F(xiàn)問題。也就是說,程序員應(yīng)該對(duì)這些情況進(jìn)行考慮、并提供相應(yīng)的處理。 異常有可分為Runtime異常和Checked異常,Checked異常體現(xiàn)了Java的設(shè)計(jì)哲學(xué):沒有完善錯(cuò)誤處理的代碼根本就不會(huì)被執(zhí)行!對(duì)于Checked異常的處理方式有兩種: A.當(dāng)前方法明確知道如何處理該異常,程序應(yīng)該使用try...catch塊來捕獲該異常,然后在對(duì)應(yīng)的catch塊中修補(bǔ)該異常。 B.當(dāng)前方法不知道如何處理這種異常,應(yīng)該在定義該方法時(shí)聲明拋出該異常。 實(shí)際上Java的Checked異常后來“爭議不斷”,因?yàn)镃hecked異常要求程序員要么顯式聲明拋出,要么進(jìn)行捕捉,不能對(duì)Checked異常不聞不問,這樣就給編程帶來了一定的復(fù)雜度,比如Spring、Hibernate框架的一大特點(diǎn)就是把Checked異常包裝成了Runtime異常。 Runtime異常則比較靈活,開發(fā)者既可以選擇捕獲Runtime異常,也可以不捕獲。 78、請(qǐng)寫出你最常見到的5個(gè)runtime exception。對(duì)于一個(gè)有1~2年左右編程經(jīng)驗(yàn)的人來說,總會(huì)經(jīng)常遇到一些常見的異常,其中有些就是Runtime Exception。比如: NullPointerException - 當(dāng)調(diào)用一個(gè)未初始化的引用變量(實(shí)際值為null)的實(shí)例Field、實(shí)例方法時(shí)都會(huì)引發(fā)該異常。 ArithmeticException - 算術(shù)異常。比如5/0將引發(fā)該異常。 ArrayIndexOutOfBoundsException:數(shù)組索引越界異常。 ClassCastException:類型轉(zhuǎn)換異常。 IllegalArgumentException:參數(shù)非法的異常。 79、Java語言如何進(jìn)行異常處理,關(guān)鍵字:throws、throw、try、catch、finally分別代表什么意義?在try塊中可以拋出異常嗎?try塊表示程序正常的業(yè)務(wù)執(zhí)行代碼。如果程序在執(zhí)行try塊的代碼時(shí)出現(xiàn)了“非預(yù)期”情況,JVM將會(huì)生成一個(gè)異常對(duì)象,這個(gè)異常對(duì)象將會(huì)被后面相應(yīng)的catch塊捕獲。 catch塊表示一個(gè)異常捕獲塊。當(dāng)程序執(zhí)行try塊引發(fā)異常時(shí),這個(gè)異常對(duì)象將會(huì)被后面相應(yīng)的catch塊捕獲。 throw用于手動(dòng)地拋出異常對(duì)象。throw后面需要一個(gè)異常對(duì)象。 throws用于在方法簽名中聲明拋出一個(gè)或多個(gè)異常類,throws關(guān)鍵字后可以緊跟一個(gè)或多個(gè)異常類。 finally塊代表異常處理流程中總會(huì)執(zhí)行的代碼塊。 對(duì)于一個(gè)完整的異常處理流程而言,try塊是必須的,try塊后可以緊跟一個(gè)或多個(gè)catch塊,最后還可以帶一個(gè)finally塊。Java 7引入了自動(dòng)關(guān)閉資源的try語句,這種自動(dòng)關(guān)閉資源的try語句可以單獨(dú)存在。例如如下代碼是正確的: try( // 聲明、并創(chuàng)建可以被自動(dòng)關(guān)閉的資源 ) { // 執(zhí)行語句 } 在上面try關(guān)鍵字的圓括號(hào)內(nèi),可用于聲明、并創(chuàng)建能被自動(dòng)關(guān)閉的資源,這些能被自動(dòng)關(guān)閉的資源必須實(shí)現(xiàn)Closeable或AutoCloseable接口。 80、Java中有幾種方法可以實(shí)現(xiàn)一個(gè)線程?用什么關(guān)鍵字修飾同步方法? stop()和suspend()方法為何不推薦使用?在Java5以前,有如下兩種: 第一種:繼承Thread類,重寫它的run()方法。 代碼如下: new Thread() { public void run() { // 線程執(zhí)行體 } }.start(); 第二種:實(shí)現(xiàn)Runnable接口,并重寫它的run()方法。 代碼如下: new Thread(new Runnable() { public void run() { // 線程執(zhí)行體 } }).start(); 從上面代碼不難看出,線程的執(zhí)行體是一個(gè)run()方法,然后程序通過start()方法啟動(dòng)一條線程。 從Java 5開始,Java提供了第三種方式來創(chuàng)建多線程:實(shí)現(xiàn)Callable接口,并實(shí)現(xiàn)call()方法。Callable接口相當(dāng)于Runnable接口的增強(qiáng)版,因?yàn)镃allable接口中定義的call()方法既擁有返回值,也可以聲明拋出異常。 代碼如下: new Thread(new FutureTask<Object >(new Callable<Object>() { public Object call() throws Exception { // 線程執(zhí)行體 } })).start(); 不僅如此,Java 5還提供了線程支持,ExecutorService對(duì)象就代表了線程池,如果開發(fā)者利用ExecutorService來啟動(dòng)線程,ExecutorService底層會(huì)負(fù)責(zé)管理線程池。此時(shí),開發(fā)者只要把Runnable對(duì)象傳給ExecutorService即可。如下代碼: ExecutorService pool = Executors.newFixedThreadPool(3) pool.execute(new Runnable() { public void run() { // 線程執(zhí)行體 } }); 如果執(zhí)行通過Callable方式實(shí)現(xiàn)的線程,則可按如下代碼: ExecutorService pool = Executors.newFixedThreadPool(3) pool.execute(new FutureTask<Object >(new Callable<Object>() { public Object call() throws Exception { //線程執(zhí)行體 } })); 用synchronized關(guān)鍵字修飾同步方法。需要指出的是,非靜態(tài)的同步方法的同步監(jiān)視器是this,也就是調(diào)用該方法的對(duì)象,而靜態(tài)的同步方法的同步監(jiān)視器則是該類本身。因此使用synchronized修飾的靜態(tài)方法、非靜態(tài)方法的同步監(jiān)視器并不相同,只有基于同一個(gè)同步監(jiān)視器的同步方法、同步代碼塊才能實(shí)現(xiàn)同步。 反對(duì)使用stop(),是因?yàn)樗话踩K鼤?huì)解除由線程獲取的所有鎖定,而且如果對(duì)象處于一種不連貫狀態(tài),那么其他線程能在那種狀態(tài)下檢查和修改它們。結(jié)果很難檢查出真正的問題所在。suspend()方法容易發(fā)生死鎖。調(diào)用suspend()的時(shí)候,目標(biāo)線程會(huì)停下來,但卻仍然持有在這之前獲得的鎖定。此時(shí),其他任何線程都不能訪問鎖定的資源,除非被"掛起"的線程恢復(fù)運(yùn)行。對(duì)任何線程來說,如果它們想恢復(fù)目標(biāo)線程,同時(shí)又試圖使用任何一個(gè)鎖定的資源,就會(huì)造成死鎖。所以不應(yīng)該使用suspend(),而應(yīng)在自己的Thread類中置入一個(gè)標(biāo)志,指出線程應(yīng)該活動(dòng)還是掛起。若標(biāo)志指出線程應(yīng)該掛起,便用wait()命其進(jìn)入等待狀態(tài)。若標(biāo)志指出線程應(yīng)當(dāng)恢復(fù),則用一個(gè)notify()重新啟動(dòng)線程。 82、sleep()和wait()有什么區(qū)別?sleep()是Thread類的靜態(tài)方法,它的作用是讓當(dāng)前線程從運(yùn)行狀態(tài)轉(zhuǎn)入阻塞狀態(tài),線程執(zhí)行暫停下來,當(dāng)一個(gè)線程通過sleep()方法暫停之后,該線程并不會(huì)釋放它對(duì)同步監(jiān)視器的加鎖。 wait()是Object對(duì)象的方法,但實(shí)際上只有同步監(jiān)視器才能調(diào)用該方法。當(dāng)程序在同步代碼塊、或同步方法內(nèi)通過同步監(jiān)視器調(diào)用該方法時(shí),將會(huì)導(dǎo)致當(dāng)前線程釋放對(duì)該同步監(jiān)視器的加鎖,而該線程則會(huì)進(jìn)入該同步監(jiān)視器的等待池中,直到該同步監(jiān)視器調(diào)用notify()或notifyAll()來通知該線程。 83、同步和異步有何異同,在什么情況下分別使用他們?舉例說明。如果有一個(gè)資源需要被一個(gè)或多個(gè)線程共享,這個(gè)資源就變成了“競爭”資源,此時(shí)多條線程必須按某種既定的規(guī)則、依次訪問、修改這個(gè)“競爭”資源,當(dāng)一條線程正在訪問、修改該“競爭”資源時(shí),其他線程不能同時(shí)修改這份“競爭”資源,這就是同步處理。 對(duì)于一個(gè)銀行賬戶,如果有多個(gè)線程試圖去訪問這個(gè)賬戶時(shí),如果不對(duì)多個(gè)線程進(jìn)行同步控制,有可能賬戶余額只有1000塊,但多個(gè)線程都試圖取款800塊時(shí),這些線程同時(shí)判斷余額之后,都會(huì)顯示余額足夠,從而導(dǎo)致每個(gè)線程都取款成功。這顯然不是我們希望看到結(jié)果。 當(dāng)程序試圖執(zhí)行一個(gè)耗時(shí)操作時(shí),程序不希望阻塞當(dāng)前執(zhí)行流,因此程序也不應(yīng)該試圖立即獲取該耗時(shí)操作返回的結(jié)果,此時(shí)就使用異步編程了,典型的應(yīng)用場景就是Ajax。當(dāng)瀏覽器通過JavaScript發(fā)出一個(gè)異步請(qǐng)求之后,JavaScript執(zhí)行流并不會(huì)停下來,而是繼續(xù)向下執(zhí)行,這就是異步。程序會(huì)通過監(jiān)聽器來監(jiān)聽遠(yuǎn)程服務(wù)器響應(yīng)的到來。 84、多線程有幾種實(shí)現(xiàn)方法?同步有幾種實(shí)現(xiàn)方法?在Java5以前,有如下兩種: 第一種:繼承Thread類,重寫它的run()方法。 代碼如下: new Thread() { public void run() { //線程執(zhí)行體 } }.start(); 第二種:實(shí)現(xiàn)Runnable接口,并重寫它的run()方法。 代碼如下: new Thread(new Runnable() { public void run() { // 線程執(zhí)行體 } }).start(); 從上面代碼不難看出,線程的執(zhí)行體是一個(gè)run()方法,然后程序通過start()方法啟動(dòng)一條線程。 從Java 5開始,Java提供了第三種方式來創(chuàng)建多線程:實(shí)現(xiàn)Callable接口,并實(shí)現(xiàn)call()方法。Callable接口相當(dāng)于Runnable接口的增強(qiáng)版,因?yàn)镃allable接口中定義的call()方法既擁有返回值,也可以聲明拋出異常。 代碼如下: new Thread(new FutureTask<Object >(new Callable<Object>() { public Object call() throws Exception { // 線程執(zhí)行體 } })).start(); 不僅如此,Java 5還提供了線程支持,ExecutorService對(duì)象就代表了線程池,如果開發(fā)者利用ExecutorService來啟動(dòng)線程,ExecutorService底層會(huì)負(fù)責(zé)管理線程池。此時(shí),開發(fā)者只要把Runnable對(duì)象傳給ExecutorService即可。如下代碼: ExecutorService pool = Executors.newFixedThreadPool(3) pool.execute(new Runnable() { public void run() { // 線程執(zhí)行體 } }); 如果執(zhí)行通過Callable方式實(shí)現(xiàn)的線程,則可按如下代碼: ExecutorService pool = Executors.newFixedThreadPool(3) pool.execute(new FutureTask<Object >(new Callable<Object>() { public Object call() throws Exception { //線程執(zhí)行體 } })); 85、啟動(dòng)一個(gè)線程是用run()還是start()?啟動(dòng)一個(gè)線程是調(diào)用start()方法,使線程進(jìn)入就緒狀態(tài),以后可以被調(diào)度為運(yùn)行狀態(tài)。run()方法是線程的線程執(zhí)行體——也就是線程將要完成的事情。 86、當(dāng)一個(gè)線程進(jìn)入一個(gè)對(duì)象的一個(gè)synchronized方法后,其它線程是否可進(jìn)入此對(duì)象的其它方法?當(dāng)一個(gè)線程進(jìn)行一個(gè)對(duì)象的synchronized方法之后,其他線程完全有可能再次進(jìn)入該對(duì)象的其他方法。 不過要分幾種情況來看: 1、如果其他方法沒有使用synchronized關(guān)鍵字修飾,則可以進(jìn)入。 2、如果當(dāng)前線程進(jìn)入的synchronized方法是static方法,其他線程可以進(jìn)入其他synchronized修飾的非靜態(tài)方法;如果當(dāng)前線程進(jìn)入的synchronized方法是非static方法,其他線程可以進(jìn)入其他synchronized修飾的靜態(tài)方法。 3、如果兩個(gè)方法都是靜態(tài)方法、或者都是非靜態(tài)方法,并且都使用了synchronized修飾,但只要在該方法內(nèi)部調(diào)用了同步監(jiān)視器的wait(),則其他線程依然可以進(jìn)入其他使用synchronized方法修飾的方法。 4、如果兩個(gè)方法都是靜態(tài)方法、或者都是非靜態(tài)方法,并且都使用了synchronized修飾,而且沒有在該方法內(nèi)部調(diào)用了同步監(jiān)視器的wait(),則其他線程不能進(jìn)入其他使用synchronized方法修飾的方法。 87、線程的基本概念、線程的基本狀態(tài)以及狀態(tài)之間的關(guān)系多線程擴(kuò)展了多進(jìn)程的概念,使得同一個(gè)進(jìn)程可以同時(shí)并發(fā)處理多個(gè)任務(wù)。線程(Thread)也被稱作輕量級(jí)進(jìn)程(Lightweight Process),線程是進(jìn)程的執(zhí)行單元。就像進(jìn)程在操作系統(tǒng)中的地位一樣,線程在程序中是獨(dú)立的、并發(fā)的執(zhí)行流。當(dāng)進(jìn)程被初始化后,主線程就被創(chuàng)建了。對(duì)于絕大多數(shù)的應(yīng)用程序來說,通常僅要求有一個(gè)主線程,但我們也可以在該進(jìn)程內(nèi)創(chuàng)建多條順序執(zhí)行流,這些順序執(zhí)行流就是線程,每條線程也是互相獨(dú)立的。 線程是進(jìn)程的組成部分,一個(gè)進(jìn)程可以擁有多個(gè)線程,一個(gè)線程必須有一個(gè)父進(jìn)程。線程可以擁有自己的堆棧、自己的程序計(jì)數(shù)器和自己的局部變量,但不再擁有系統(tǒng)資源,它與父進(jìn)程的其他線程共享該進(jìn)程所擁有的全部資源。因?yàn)槎鄠€(gè)線程共享父進(jìn)程里的全部資源,因此編程更加方便;但必須更加小心,我們必須確保線程不會(huì)妨礙同一進(jìn)程里的其他線程。 線程的執(zhí)行需要經(jīng)過如下狀態(tài): 新建 就緒 運(yùn)行 阻塞 死亡 各狀態(tài)的轉(zhuǎn)換關(guān)系如下圖所示: 88、簡述synchronized和java.util.concurrent.locks.Lock的異同 ?主要相同點(diǎn):Lock能完成synchronized所實(shí)現(xiàn)的所有功能 主要不同點(diǎn):Lock有比synchronized更精確的線程語義和更好的性能。synchronized會(huì)自動(dòng)釋放鎖,而Lock一定要求程序員手工釋放,并且必須在finally從句中釋放。Lock還有更強(qiáng)大的功能,例如,它的tryLock方法可以非阻塞方式去拿鎖。 Java代碼查錯(cuò)部分1. abstract class Name { private String name; public abstract boolean isStupidName(String name) {} } 答案: 錯(cuò)。abstract方法必須以分號(hào)結(jié)尾,且不帶花括號(hào)。 2. public class Something { void doSomething () { private String s = ""; int l = s.length(); } } 有錯(cuò)嗎? 答案: 錯(cuò)。局部變量前不能放置任何訪問修飾符 (private,public,和protected)。final可以用來修飾局部變量 (final如同abstract和strictfp,都是非訪問修飾符,strictfp只能修飾class和method而非variable)。 3. abstract class Something { private abstract String doSomething (); } 這好像沒什么錯(cuò)吧? 答案: 錯(cuò)。abstract的methods不能以private修飾。abstract的methods就是讓子類implement(實(shí)現(xiàn))具體細(xì)節(jié)的,怎么可以用private把a(bǔ)bstract method隱藏起來呢? (同理,abstract method前不能加final)。 4. public class Something { public int addOne(final int x) { return ++x; } } 答案: 錯(cuò)。int x被修飾成final,意味著x不能在addOne method中被修改。 5. public class Something { public static void main(String[] args) { Other o = new Other(); new Something().addOne(o); } public void addOne(final Other o) { o.i++; } } class Other { public int i; } 和上面的很相似,都是關(guān)于final的問題,這有錯(cuò)嗎? 答案: 正確。在addOne method中,參數(shù)o被修飾成final。如果在addOne method里我們修改了o的引用 (比如: o = new Other();),那么如同上例這題也是錯(cuò)的。但這里修改的是o的成員變量,(成員變量),而o的reference并沒有改變,因此程序沒有錯(cuò)誤。 6. class Something { int i; public void doSomething() { System.out.println("i = " + i); } } 有什么錯(cuò)呢? 答案: 正確。輸出的是"i = 0"。int i是定義實(shí)例變量。系統(tǒng)會(huì)對(duì)實(shí)例變量執(zhí)行默認(rèn)初始化,因此i的默認(rèn)被賦值為0 。 7. class Something { final int i; public void doSomething() { System.out.println("i = " + i); } } 和上面一題只有一個(gè)地方不同,就是多了一個(gè)final。 答案: 錯(cuò)。使用final修飾的實(shí)例變量必須由程序員顯式指定初始值,為final變量指定初始值有3個(gè)地方: A.定義時(shí)指定初始值: B.在初始化塊中指定初始值。 C.在構(gòu)造器中指定初始值。 8. public class Something { public static void main(String[] args) { Something s = new Something(); System.out.println("s.doSomething() returns " + doSomething()); } public String doSomething() { return "Do something ..."; } } 答案: 錯(cuò)。靜態(tài)成員不允許訪問非靜態(tài)成員。上面main方法是靜態(tài)方法,而doSomething()是非靜態(tài)方法,因此程序?qū)е戮幾g錯(cuò)誤。 9. 此處,Something類的文件名叫OtherThing.java class Something { private static void main(String[] something_to_do) { System.out.println("Do something ..."); } } 答案: 正確。由于Something類不是public類,因此Java源文件的主文件名可以是任意的。但public class的名字必須和文件名相同。 10. interface A{ int x = 0; } class B{ int x =1; } class C extends B implements A { public void printX(){ System.out.println(x); } public static void main(String[] args) { new C().printX (); } } 答案:錯(cuò)誤。在編譯時(shí)會(huì)發(fā)生錯(cuò)誤。因?yàn)镃類既實(shí)現(xiàn)了A接口,也繼承B,因此它將會(huì)從A接口、B類中分別繼承到成員變量x,因此上面程序中System.out.println(x);代碼所引用的x是不明確的。 對(duì)于父類的變量,可以用super.x來明確指定,而接口的屬性默認(rèn)隱含為 public static final.所以可以通過A.x來明確指定。 11. interface Playable { void play(); } interface Bounceable { void play(); } interface Rollable extends Playable, Bounceable { Ball ball = new Ball("PingPang"); } class Ball implements Rollable { private String name; public String getName() { return name; } public Ball(String name) { this.name = name; } public void play() { ball = new Ball("Football"); System.out.println(ball.getName()); } } 答案: 錯(cuò)。"interface Rollable extends Playable, Bounceable"沒有問題。interface可繼承多個(gè)interfaces,所以這里沒錯(cuò)。問題出在interface Rollable里的"Ball ball = new Ball("PingPang");"。在接口里聲明的成員變量總是常量,也就是默認(rèn)使用public static final修飾。也就是說"Ball ball = new Ball("PingPang");"實(shí)際上是"public static final Ball ball = new Ball("PingPang");"。在Ball類的Play()方法中,"ball = new Ball("Football");"嘗試去改變了ball的引用,而這里的ball引用變量是在Rollable中定義的,因此它有final修飾,final修飾的引用變量是不能被重新賦值的,因此上面程序會(huì)導(dǎo)致編譯錯(cuò)誤。 |
|