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