Java初級面試題

2020-08-14 21:08:15
  1. Java基礎部分

基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,執行緒的語法,集合的語法,io的語法,虛擬機器方面的語法。

1、一個".java"原始檔中是否可以包括多個類(不是內部類)?有什麼限制?

可以有多個類,但只能有一個public的類,並且public的類名必須與檔名相一致。

2、Java有沒有goto?

java中的保留字,現在沒有在java中使用。

3、說說&和&&的區別。

&和&&都可以用作邏輯與的運算子,表示邏輯與(and),當運算子兩邊的表達式的結果都爲true時,整個運算結果才爲true,否則,只要有一方爲false,則結果爲false。

&&還具有短路的功能,即如果第一個表達式爲false,則不再計算第二個表達式,例如,對於if(str != null&& !str.equals(「」))表達式,當str爲null時,後面的表達式不會執行,所以不會出現NullPointerException如果將&&改爲&,則會拋出NullPointerException異常。If(x33 &++y>0) y會增長,If(x33 && ++y>0)不會增長

&還可以用作位運算子,當&操作符兩邊的表達式不是boolean型別時,&表示按位元與操作,我們通常使用0x0f來與一個整數進行&運算,來獲取該整數的最低4個bit位,例如,0x31 & 0x0f的結果爲0x01。

備註:這道題先說兩者的共同點,再說出&&和&的特殊之處,並列舉一些經典的例子來表明自己理解透徹深入、實際經驗豐富。 f

4、在JAVA中如何跳出當前的多重巢狀回圈?

在Java中,要想跳出多重回圈,可以在外面的回圈語句前定義一個標號,然後在裏層回圈體的程式碼中使用帶有標號的break語句,即可跳出外層回圈。例如,

ok:

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 ok;

}

}

另外,我個人通常並不使用標號這種方式,而是讓外層的回圈條件表達式的結果可以受到裏層回圈體程式碼的控制,例如,要在二維陣列中查詢到某個數位。

int arr[][] ={{1,2,3},{4,5,6,7},{9}};

boolean found = false;

for(int i=0;i

for(int j=0;j

System.out.println(「i=」 + i + 「,j=」 + j);

if(arr[i][j] ==5) {

found = true;

break;

}

}

}

5、switch語句能否作用在byte上,能否作用在long上,能否作用在String上?

在switch(expr1)中,expr1只能是一個整數表達式或者列舉常數(更大字型),整數表達式可以是int基本型別或Integer包裝型別,由於,byte,short,char都可以隱含轉換爲int,所以,這些型別以及這些型別的包裝型別也是可以的。顯然,long和String型別都不符合switch的語法規定,並且不能被隱式轉換成int型別,所以,它們不能作用於swtich語句中。

6、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語言規定的運算子,java編譯器會對它進行特殊處理,因此可以正確編譯。

7、char型變數中能不能存貯一箇中文漢字?爲什麼?

char型變數是用來儲存Unicode編碼的字元的,unicode編碼字元集中包含了漢字,所以,char型變數中當然可以儲存漢字啦。不過,如果某個特殊的漢字沒有被包含在unicode編碼字元集中,那麼,這個char型變數中就不能儲存這個特殊漢字。補充說明:unicode編碼佔用兩個位元組,所以,char型別的變數也是佔用兩個位元組。

備註:後面一部分回答雖然不是在正面回答題目,但是,爲了展現自己的學識和表現自己對問題理解的透徹深入,可以回答一些相關的知識,做到知無不言,言無不盡。

8、用最有效率的方法算出2乘以8等於幾?

2 << 3,

因爲將一個數左移n位,就相當於乘以了2的n次方,那麼,一個數乘以8只要將其左移3位即可,而位運算cpu直接支援的,效率最高,所以,2乘以8等於幾的最效率的方法是2 << 3。

9、請設計一個一百億的計算器

首先要明白這道題目的考查點是什麼,一是大家首先要對計算機原理的底層細節要清楚、要知道加減法的位運算原理和知道計算機中的算術運算會發生越界的情況,二是要具備一定的物件導向的設計思想。

首先,計算機中用固定數量的幾個位元組來儲存的數值,所以計算機中能夠表示的數值是有一定的範圍的,爲了便於講解和理解,我們先以byte型別的整數爲例,它用1個位元組進行儲存,表示的最大數值範圍爲-128到+127。-1在記憶體中對應的二進制數據爲11111111,如果兩個-1相加,不考慮Java運算時的型別提升,運算後會產生進位,二進制結果爲1,11111110,由於進位後超過了byte型別的儲存空間,所以進位部分被捨棄,即最終的結果爲11111110,也就是-2,這正好利用溢位的方式實現了負數的運算。-128在記憶體中對應的二進制數據爲10000000,如果兩個-128相加,不考慮Java運算時的型別提升,運算後會產生進位,二進制結果爲1,00000000,由於進位後超過了byte型別的儲存空間,所以進位部分被捨棄,即最終的結果爲00000000,也就是0,這樣的結果顯然不是我們期望的,這說明計算機中的算術運算是會發生越界情況的,兩個數值的運算結果不能超過計算機中的該型別的數值範圍。由於Java中涉及表達式運算時的型別自動提升,我們無法用byte型別來做演示這種問題和現象的實驗,大家可以用下面 下麪一個使用整數做實驗的例子程式體驗一下:

int a = Integer.MAX_VALUE;

int b = Integer.MAX_VALUE;

int sum = a + b;

System.out.println(「a=」+a+」,b=」+b+」,sum=」+sum);

先不考慮long型別,由於int的正數範圍爲2的31次方,表示的最大數值約等於210001000*1000,也就是20億的大小,所以,要實現一個一百億的計算器,我們得自己設計一個類可以用於表示很大的整數,並且提供了與另外一個整數進行加減乘除的功能,大概功能如下:

()這個類內部有兩個成員變數,一個表示符號,另一個用位元組陣列表示數值的二進制數

()有一個構造方法,把一個包含有多位數值的字串轉換到內部的符號和位元組陣列中

()提供加減乘除的功能

public class BigInteger{

int sign;

byte[] val;

public Biginteger(String val) {

sign = ;

val = ;

}

public BigInteger add(BigInteger other) {

}

public BigInteger subtract(BigInteger other) {

}

public BigInteger multiply(BigInteger other){

}

public BigInteger divide(BigInteger other){

}

}

備註:要想寫出這個類的完整程式碼,是非常複雜的,如果有興趣的話,可以參看jdk中自帶的java.math.BigInteger類的原始碼。面試的人也知道誰都不可能在短時間內寫出這個類的完整程式碼的,他要的是你是否有這方面的概念和意識,他最重要的還是考查你的能力,所以,你不要因爲自己無法寫出完整的最終結果就放棄答這道題,你要做的就是你比別人寫得多,證明你比別人強,你有這方面的思想意識就可以了,畢竟別人可能連題目的意思都看不懂,什麼都沒寫,你要敢於答這道題,即使只答了一部分,那也與那些什麼都不懂的人區別出來,拉開了距離,算是矮子中的高個,機會當然就屬於你了。另外,答案中的框架程式碼也很重要,體現了一些物件導向設計的功底,特別是其中的方法命名很專業,用的英文單詞很精準,這也是能力、經驗、專業性、英語水平等多個方面的體現,會給人留下很好的印象,在程式設計能力和其他方麪條件差不多的情況下,英語好除了可以使你獲得更多機會外,薪水可以高出一千元。

10、使用final關鍵字修飾一個變數時,是參照不能變,還是參照的物件不能變?

使用final關鍵字修飾一個變數時,是指參照變數不能變,參照變數所指向的物件中的內容還是可以改變的。例如,對於如下語句:

final StringBuffer a=new StringBuffer(「immutable」);

執行如下語句將報告編譯期錯誤:

a=new StringBuffer("");

但是,執行如下語句則可以通過編譯:

a.append(" broken!");

有人在定義方法的參數時,可能想採用如下形式來阻止方法內部修改傳進來的參數物件:

public void method(final StringBuffer param){

}

實際上,這是辦不到的,在該方法內部仍然可以增加如下程式碼來修改參數物件:

param.append(「a」);

11、"=="和equals方法究竟有什麼區別?

(單獨把一個東西說清楚,然後再說清楚另一個,這樣,它們的區別自然就出來了,混在一起說,則很難說清楚)

操作符專門用來比較兩個變數的值是否相等,也就是用於比較變數所對應的記憶體中所儲存的數值是否相同,要比較兩個基本型別的數據或兩個參照變數是否相等,只能用操作符。

如果一個變數指向的數據是物件型別的,那麼,這時候涉及了兩塊記憶體,物件本身佔用一塊記憶體(堆記憶體),變數也佔用一塊記憶體,例如Objet obj = new Object();變數obj是一個記憶體,new Object()是另一個記憶體,此時,變數obj所對應的記憶體中儲存的數值就是物件佔用的那塊記憶體的首地址。對於指向物件型別的變數,如果要比較兩個變數是否指向同一個物件,即要看這兩個變數所對應的記憶體中的數值是否相等,這時候就需要用==操作符進行比較。

equals方法是用於比較兩個獨立物件的內容是否相同,就好比去比較兩個人的長相是否相同,它比較的兩個物件是獨立的。例如,對於下面 下麪的程式碼:

String a=new String(「foo」);

String b=new String(「foo」);

兩條new語句建立了兩個物件,然後用a/b這兩個變數分別指向了其中一個物件,這是兩個不同的物件,它們的首地址是不同的,即a和b中儲存的數值是不相同的,所以,表達式a==b將返回false,而這兩個物件中的內容是相同的,所以,表達式a.equals(b)將返回true。

在實際開發中,我們經常要比較傳遞進行來的字串內容是否等,例如,String input = …;input.equals(「quit」),許多人稍不注意就使用==進行比較了,這是錯誤的,隨便從網上找幾個專案實戰的教學視訊看看,裏面就有大量這樣的錯誤。記住,字串的比較基本上都是使用equals方法。

如果一個類沒有自己定義equals方法,那麼它將繼承Object類的equals方法,Object類的equals方法的實現程式碼如下:

boolean equals(Object o){

return this==o;

}

這說明,如果一個類沒有自己定義equals方法,它預設的equals方法(從Object類繼承的)就是使用操作符,也是在比較兩個變數指向的物件是否是同一物件,這時候使用equals和使用會得到同樣的結果,如果比較的是兩個獨立的物件則總返回false。如果你編寫的類希望能夠比較該類建立的兩個範例物件的內容是否相同,那麼你必須覆蓋equals方法,由你自己寫程式碼來決定在什麼情況即可認爲兩個物件的內容是相同的。

12、靜態變數和範例變數的區別?

在語法定義上的區別:靜態變數前要加static關鍵字,而範例變數前則不加。

在程式執行時的區別:範例變數屬於某個物件的屬性,必須建立了範例物件,其中的範例變數纔會被分配空間,才能 纔能使用這個範例變數。靜態變數不屬於某個範例物件,而是屬於類,所以也稱爲類變數,只要程式載入了類的位元組碼,不用建立任何範例物件,靜態變數就會被分配空間,靜態變數就可以被使用了。總之,範例變數必須建立物件後纔可以通過這個物件來使用,靜態變數則可以直接使用類名來參照。

例如,對於下面 下麪的程式,無論建立多少個範例物件,永遠都只分配了一個staticVar變數,並且每建立一個範例物件,這個staticVar就會加1;但是,每建立一個範例物件,就會分配一個instanceVar,即可能分配多個instanceVar,並且每個instanceVar的值都只自加了1次。

public class VariantTest{

public static int staticVar = 0;

public int instanceVar = 0;

public VariantTest(){

staticVar++;

instanceVar++;

System.out.println(「staticVar=」 + staticVar + 」,instanceVar=」+ instanceVar);

}

}

備註:這個解答除了說清楚兩者的區別外,最後還用一個具體的應用例子來說明兩者的差異,體現了自己有很好的解說問題和設計案例的能力,思維敏捷,超過一般程式設計師,有寫作能力!

13、是否可以從一個static方法內部發出對非static方法的呼叫?

不可以。因爲非static方法是要與物件關聯在一起的,必須建立一個物件後,纔可以在該物件上進行方法呼叫,而static方法呼叫時不需要建立物件,可以直接呼叫。也就是說,當一個static方法被呼叫時,可能還沒有建立任何範例物件,如果從一個static方法中發出對非static方法的呼叫,那個非static方法是關聯到哪個物件上的呢?這個邏輯無法成立,所以,一個static方法內部發出對非static方法的呼叫。

14、Integer與int的區別

int是java提供的8種原始數據型別之一。Java爲每個原始型別提供了封裝類,Integer是java爲int提供的封裝類。int的預設值爲0,而Integer的預設值爲null,即Integer可以區分出未賦值和值爲0的區別,int則無法表達出未賦值的情況,例如,要想表達出沒有參加考試和考試成績爲0的區別,則只能使用Integer。在JSP開發中,Integer的預設爲null,所以用el表達式在文字方塊中顯示時,值爲空白字串,而int預設的預設值爲0,所以用el表達式在文字方塊中顯示時,結果爲0,所以,int不適合作爲web層的表單數據的型別。

在Hibernate中,如果將OID定義爲Integer型別,那麼Hibernate就可以根據其值是否爲null而判斷一個物件是否是臨時的,如果將OID定義爲了int型別,還需要在hbm對映檔案中設定其unsaved-value屬性爲0。

另外,Integer提供了多個與整數相關的操作方法,例如,將一個字串轉換成整數,Integer中還定義了表示整數的最大值和最小值的常數。

15、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.ceil(11.6)的結果爲11,Math.ceil(-11.6)的結果是-12;最難掌握的是round方法,它表示「四捨五入」,演算法爲Math.floor(x+0.5),即將原來的數位加上0.5後再向下取整,所以,Math.round(11.5)的結果爲12,Math.round(-11.5)的結果爲-11。

16、下面 下麪的程式碼有什麼不妥之處?

  1. if(username.equals(「zxx」){}

username可能爲NULL,會報空指針錯誤;改爲"zxx".equals(username)

2.intx = 1;

return x1?true:false; 這個改成return x1;就可以!

17、請說出作用域public,private,protected,以及不寫時的區別

這四個作用域的可見範圍如下表所示。

說明:如果在修飾的元素上面沒有寫任何存取修飾符,則表示friendly。

作用域 當前類同一package子孫類其他package

public √ √ √ √

protected √ √ √ ×

friendly √ √ × ×

private √ × × ×

備註:只要記住了有4種存取許可權,4個存取範圍,然後將全選和範圍在水平和垂直方向上分別按排從小到大或從大到小的順序排列,就很容易畫出上面的圖了。

18、Overload和Override的區別。Overloaded的方法是否可以改變返回值的型別?

Overload是過載的意思,Override是覆蓋的意思,也就是重寫。

過載Overload表示同一個類中可以有多個名稱相同的方法,但這些方法的參數列表各不相同(即參數個數或型別不同)。

重寫Override表示子類中的方法可以與父類別中的某個方法的名稱和參數完全相同,通過子類建立的範例物件呼叫這個方法時,將呼叫子類中的定義方法,這相當於把父類別中定義的那個完全相同的方法給覆蓋了,這也是物件導向程式設計的多型性的一種表現。子類覆蓋父類別的方法時,只能比父類別拋出更少的異常,或者是拋出父類別拋出的異常的子異常,因爲子類可以解決父類別的一些問題,不能比父類別有更多的問題。子類方法的存取許可權只能比父類別的更大,不能更小。如果父類別的方法是private型別,那麼,子類則不存在覆蓋的限制,相當於子類中增加了一個全新的方法。

至於Overloaded的方法是否可以改變返回值的型別這個問題,要看你倒底想問什麼呢?這個題目很模糊。如果幾個Overloaded的方法的參數列表不一樣,它們的返回者型別當然也可以不一樣。但我估計你想問的問題是:如果兩個方法的參數列表完全一樣,是否可以讓它們的返回值不同來實現過載Overload。這是不行的,我們可以用反證法來說明這個問題,因爲我們有時候呼叫一個方法時也可以不定義返回結果變數,即不要關心其返回結果,例如,我們呼叫map.remove(key)方法時,雖然remove方法有返回值,但是我們通常都不會定義接收返回結果的變數,這時候假設該類中有兩個名稱和參數列表完全相同的方法,僅僅是返回型別不同,java就無法確定程式設計者倒底是想呼叫哪個方法了,因爲它無法通過返回結果型別來判斷。

override可以翻譯爲覆蓋,從字面就可以知道,它是覆蓋了一個方法並且對其重寫,以求達到不同的作用。對我們來說最熟悉的覆蓋就是對介面方法的實現,在介面中一般只是對方法進行了宣告,而我們在實現時,就需要實現介面宣告的所有方法。除了這個典型的用法以外,我們在繼承中也可能會在子類覆蓋父類別中的方法。在覆蓋要注意以下的幾點:

1、覆蓋的方法的標誌必須要和被覆蓋的方法的標誌完全匹配,才能 纔能達到覆蓋的效果;

2、覆蓋的方法的返回值必須和被覆蓋的方法的返回一致;

3、覆蓋的方法所拋出的異常必須和被覆蓋方法的所拋出的異常一致,或者是其子類;

4、被覆蓋的方法不能爲private,否則在其子類中只是新定義了一個方法,並沒有對其進行覆蓋。

overload對我們來說可能比較熟悉,可以翻譯爲過載,它是指我們可以定義一些名稱相同的方法,通過定義不同的輸入參數來區分這些方法,然後再呼叫時,VM就會根據不同的參數樣式,來選擇合適的方法執行。在使用過載要注意以下的幾點:

1、在使用過載時只能通過不同的參數樣式。例如,不同的參數型別,不同的參數個數,不同的參數順序(當然,同一方法內的幾個參數型別必須不一樣,例如可以是fun(int,float),但是不能爲fun(int,int));

2、不能通過存取許可權、返回型別、拋出的異常進行過載;

3、方法的異常型別和數目不會對過載造成影響;

4、對於繼承來說,如果某一方法在父類別中是存取許可權是priavte,那麼就不能在子類對其進行過載,如果定義的話,也只是定義了一個新方法,而不會達到過載的效果。

19、構造器Constructor是否可被override?

構造器Constructor不能被繼承,因此不能重寫Override,但可以被過載Overload。

20、介面是否可繼承介面?抽象類是否可實現(implements)介面?抽象類是否可繼承具體類(concrete class)?抽象類中是否可以有靜態的main方法?

介面可以繼承介面。抽象類可以實現(implements)介面,抽象類可以繼承具體類。抽象類中可以有靜態的main方法。

備註:只要明白了介面和抽象類的本質和作用,這些問題都很好回答,你想想,如果你是java語言的設計者,你是否會提供這樣的支援,如果不提供的話,有什麼理由嗎?如果你沒有道理不提供,那答案就是肯定的了。

只有記住抽象類與普通類的唯一區別就是不能建立範例物件和允許有abstract方法。

21、寫clone()方法時,通常都有一行程式碼,是什麼?

clone 有預設行爲,super.clone();因爲首先要把父類別中的成員複製到位,然後纔是複製自己的成員。

22、物件導向的特徵有哪些方面

計算機軟件系統是現實生活中的業務在計算機中的對映,而現實生活中的業務其實就是一個個物件共同作業的過程。物件導向程式設計就是按現實業務一樣的方式將程式程式碼按一個個物件進行組織和編寫,讓計算機系統能夠識別和理解用物件方式組織和編寫的程式程式碼,這樣就可以把現實生活中的業務物件對映到計算機系統中。

物件導向的程式語言有4個主要的特徵。

1封裝:

封裝是保證軟體部件具有優良的模組性的基礎,封裝的目標就是要實現軟體部件的「高內聚、低耦合」,防止程式相互依賴性而帶來的變動影響。在物件導向的程式語言中,物件是封裝的最基本單位,物件導向的封裝比傳統語言的封裝更爲清晰、更爲有力。物件導向的封裝就是把描述一個物件的屬性和行爲的程式碼封裝在一個「模組」中,也就是一個類中,屬性用變數定義,行爲用方法進行定義,方法可以直接存取同一個物件中的屬性。通常情況下,只要記住讓變數和存取這個變數的方法放在一起,將一個類中的成員變數全部定義成私有的,只有這個類自己的方法纔可以存取到這些成員變數,這就基本上實現物件的封裝,就很容易找出要分配到這個類上的方法了,就基本上算是會物件導向的程式設計了。把握一個原則:把對同一事物進行操作的方法和相關的方法放在同一個類中,把方法和它操作的數據放在同一個類中。

例如,人要在黑板上畫圓,這一共涉及三個物件:人、黑板、圓,畫圓的方法要分配給哪個物件呢?由於畫圓需要使用到圓心和半徑,圓心和半徑顯然是圓的屬性,如果將它們在類中定義成了私有的成員變數,那麼,畫圓的方法必須分配給圓,它才能 纔能存取到圓心和半徑這兩個屬性,人以後只是呼叫圓的畫圓方法、表示給圓發給訊息而已,畫圓這個方法不應該分配在人這個物件上,這就是物件導向的封裝性,即將物件封裝成一個高度自治和相對封閉的個體,物件狀態(屬性)由這個物件自己的行爲(方法)來讀取和改變。一個更便於理解的例子就是,司機將火車剎住了,剎車的動作是分配給司機,還是分配給火車,顯然,應該分配給火車,因爲司機自身是不可能有那麼大的力氣將一個火車給停下來的,只有火車自己才能 纔能完成這一動作,火車需要呼叫內部的離合器和剎車片等多個器件共同作業才能 纔能完成剎車這個動作,司機剎車的過程只是給火車發了一個訊息,通知火車要執行剎車動作而已。

抽象:

抽象就是找出一些事物的相似和共性之處,然後將這些事物歸爲一個類,這個類只考慮這些事物的相似和共性之處,並且會忽略與當前主題和目標無關的那些方面,將注意力集中在與當前目標有關的方面。例如,看到一隻螞蟻和大象,你能夠想象出它們的相同之處,那就是抽象。抽象包括行爲抽象和狀態抽象兩個方面。例如,定義一個Person類,如下:

Class Person{

String name;

int age;

}

人本來是很複雜的事物,有很多方面,但因爲當前系統只需要瞭解人的姓名和年齡,所以上面定義的類中只包含姓名和年齡這兩個屬性,這就是一種抽像,使用抽象可以避免考慮一些與目標無關的細節。我對抽象的理解就是不要用顯微鏡去看一個事物的所有方面,這樣涉及的內容就太多了,而是要善於劃分問題的邊界,當前系統需要什麼,就只考慮什麼。

繼承:

在定義和實現一個類的時候,可以在一個已經存在的類的基礎之上來進行,把這個已經存在的類所定義的內容作爲自己的內容,並可以加入若幹新的內容,或修改原來的方法使之更適合特殊的需要,這就是繼承。繼承是子類自動共用父類別數據和方法的機制 機製,這是類之間的一種關係,提高了軟體的可重用性和可延伸性。

多型:

多型是指程式中定義的參照變數所指向的具體型別和通過該參照變數發出的方法呼叫在程式設計時並不確定,而是在程式執行期間才確定,即一個參照變數倒底會指向哪個類的範例物件,該參照變數發出的方法呼叫到底是哪個類中實現的方法,必須在由程式執行期間才能 纔能決定。因爲在程式執行時才確定具體的類,這樣,不用修改源程式程式碼,就可以讓參照變數系結到各種不同的類實現上,從而導致該參照呼叫的具體方法隨之改變,即不修改程式程式碼就可以改變程式執行時所系結的具體程式碼,讓程式可以選擇多個執行狀態,這就是多型性。多型性增強了軟體的靈活性和擴充套件性。例如,下面 下麪程式碼中的UserDao是一個介面,它定義參照變數userDao指向的範例物件由daofactory.getDao()在執行的時候返回,有時候指向的是UserJdbcDao這個實現,有時候指向的是UserHibernateDao這個實現,這樣,不用修改原始碼,就可以改變userDao指向的具體類實現,從而導致userDao.insertUser()方法呼叫的具體程式碼也隨之改變,即有時候呼叫的是UserJdbcDao的insertUser方法,有時候呼叫的是UserHibernateDao的insertUser方法:

UserDao userDao =daofactory.getDao();

userDao.insertUser(user);

比喻:人吃飯,你看到的是左手,還是右手?

23、java中實現多型的機制 機製是什麼?

靠的是父類別或介面定義的參照變數可以指向子類或具體實現類的範例物件,而程式呼叫的方法在執行期才動態系結,就是參照變數所指向的具體範例物件的方法,也就是記憶體裡正在執行的那個物件的方法,而不是參照變數的型別中定義的方法。

24、abstract class和interface有什麼區別?

含有abstract修飾符的class即爲抽象類,abstract類不能建立範例物件。含有abstract方法的類必須定義爲abstract class,abstract class類中的方法不必是抽象的。abstract class類中定義抽象方法必須在具體(Concrete)子類中實現,所以,不能有抽象構造方法或抽象靜態方法。如果的子類沒有實現抽象父類別中的所有抽象方法,那麼子類也必須定義爲abstract型別。

介面(interface)可以說成是抽象類的一種特例,介面中的所有方法都必須是抽象的。介面中的方法定義預設爲public abstract型別,介面中的成員變數型別預設爲public static final。

下面 下麪比較一下兩者的語法區別:

1.抽象類可以有構造方法,介面中不能有構造方法。

2.抽象類中可以有普通成員變數,介面中沒有普通成員變數

3.抽象類中可以包含非抽象的普通方法,介面中的所有方法必須都是抽象的,不能有非抽象的普通方法。

4.抽象類中的抽象方法的存取型別可以是public,protected和(預設型別,雖然

eclipse下不報錯,但應該也不行),但介面中的抽象方法只能是public型別的,並且預設即爲public abstract型別。

5.抽象類中可以包含靜態方法,介面中不能包含靜態方法

6.抽象類和介面中都可以包含靜態成員變數,抽象類中的靜態成員變數的存取型別可以任意,但介面中定義的變數只能是public static final型別,並且預設即爲public static final型別。

  1. 一個類可以實現多個介面,但只能繼承一個抽象類。

下面 下麪接着再說說兩者在應用上的區別:

介面更多的是在系統架構設計方法發揮作用,主要用於定義模組之間的通訊契約。而抽象類在程式碼實現方面發揮作用,可以實現程式碼的重用,例如,模板方法設計模式是抽象類的一個典型應用,假設某個專案的所有Servlet類都要用相同的方式進行許可權判斷、記錄存取日誌和處理異常,那麼就可以定義一個抽象的基礎類別,讓所有的Servlet都繼承這個抽象基礎類別,在抽象基礎類別的service方法中完成許可權判斷、記錄存取日誌和處理異常的程式碼,在各個子類中只是完成各自的業務邏輯程式碼,虛擬碼如下:

public abstract classBaseServlet extends HttpServlet{

public final void service(HttpServletRequest request,HttpServletResponse response) throws IOExcetion,ServletException {

記錄存取日誌

進行許可權判斷

if(具有許可權){

try{

doService(request,response);

}

catch(Excetpion e) {

記錄異常資訊

}

}

}

protected abstract void doService(HttpServletRequest request,HttpServletResponse response) throws IOExcetion,ServletException;

//注意存取許可權定義成protected,顯得既專業,又嚴謹,因爲它是專門給子類用的

}

public class MyServlet1 extendsBaseServlet

{

protected voiddoService(HttpServletRequest request, HttpServletResponse response) throwsIOExcetion,ServletException

{

本Servlet只處理的具體業務邏輯程式碼

}

}

父類別方法中間的某段程式碼不確定,留給子類幹,就用模板方法設計模式。

備註:這道題的思路是先從總體解釋抽象類和介面的基本概念,然後再比較兩者的語法細節,最後再說兩者的應用區別。比較兩者語法細節區別的條理是:先從一個類中的構造方法、普通成員變數和方法(包括抽象方法),靜態變數和方法,繼承性等6個方面逐一去比較回答,接着從第三者繼承的角度的回答,特別是最後用了一個典型的例子來展現自己深厚的技術功底。

25、abstract的method是否可同時是static,是否可同時是native,是否可同時是synchronized?

abstract的method不可以是static的,因爲抽象的方法是要被子類實現的,而static與子類扯不上關係!

native方法表示該方法要用另外一種依賴平臺的程式語言實現的,不存在着被子類實現的問題,所以,它也不能是抽象的,不能與abstract混用。例如,FileOutputSteam類要硬體打交道,底層的實現用的是操作系統相關的api實現,例如,在windows用c語言實現的,所以,檢視jdk的原始碼,可以發現FileOutputStream的open方法的定義如下:

private native void open(Stringname) throws FileNotFoundException;

如果我們要用java呼叫別人寫的c語言函數,我們是無法直接呼叫的,我們需要按照java的要求寫一個c語言的函數,又我們的這個c語言函數去呼叫別人的c語言函數。由於我們的c語言函數是按java的要求來寫的,我們這個c語言函數就可以與java對接上,java那邊的對接方式就是定義出與我們這個c函數相對應的方法,java中對應的方法不需要寫具體的程式碼,但需要在前面宣告native。

關於synchronized與abstract合用的問題,我覺得也不行,因爲在我幾年的學習和開發中,從來沒見到過這種情況,並且我覺得synchronized應該是作用在一個具體的方法上纔有意義。而且,方法上的synchronized同步所使用的同步鎖物件是this,而抽象方法上無法確定this是什麼。

26、什麼是內部類?Static Nested Class和Inner Class的不同。

內部類就是在一個類的內部定義的類,內部類中不能定義靜態成員(靜態成員不是物件的特性,只是爲了找一個容身之處,所以需要放到一個類中而已,這麼一點小事,你還要把它放到類內部的一個類中,過分了啊!提供內部類,不是爲讓你幹這種事情,無聊,不讓你幹。我想可能是既然靜態成員類似c語言的全域性變數,而內部類通常是用於建立內部物件用的,所以,把「全域性變數」放在內部類中就是毫無意義的事情,既然是毫無意義的事情,就應該被禁止),內部類可以直接存取外部類中的成員變數,內部類可以定義在外部類的方法外面,也可以定義在外部類的方法體中,如下所示:

public class Outer

{

int out_x = 0;

public void method()

{

Inner1 inner1 = new Inner1();

public class Inner2 //在方法體內部定義的內部類

{

public method()

{

out_x = 3;

}

}

Inner2 inner2 = new Inner2();

}

public class Inner1 //在方法體外面定義的內部類

{

}

}

在方法體外面定義的內部類的存取型別可以是public,protecte,預設的,private等4種類型,這就好像類中定義的成員變數有4種存取型別一樣,它們決定這個內部類的定義對其他類是否可見;對於這種情況,我們也可以在外面建立內部類的範例物件,建立內部類的範例物件時,一定要先建立外部類的範例物件,然後用這個外部類的範例物件去建立內部類的範例物件,程式碼如下:

Outer outer = new Outer();

Outer.Inner1 inner1 = outer.new Innner1();

在方法內部定義的內部類前面不能有存取型別修飾符,就好像方法中定義的區域性變數一樣,但這種內部類的前面可以使用final或abstract修飾符。這種內部類對其他類是不可見的其他類無法參照這種內部類,但是這種內部類建立的範例物件可以傳遞給其他類存取。這種內部類必須是先定義,後使用,即內部類的定義程式碼必須出現在使用該類之前,這與方法中的區域性變數必須先定義後使用的道理也是一樣的。這種內部類可以存取方法體中的區域性變數,但是,該區域性變數前必須加final修飾符。

對於這些細節,只要在eclipse寫程式碼試試,根據開發工具提示的各類錯誤資訊就可以馬上瞭解到。

在方法體內部還可以採用如下語法來建立一種匿名內部類,即定義某一介面或類的子類的同時,還建立了該子類的範例物件,無需爲該子類定義名稱:

public class Outer

{

public void start()

{

new Thread(

new Runable(){

public void run(){};

}

).start();

}

}

最後,在方法外部定義的內部類前面可以加上static關鍵字,從而成爲Static Nested Class,它不再具有內部類的特性,所有,從狹義上講,它不是內部類。Static Nested Class與普通類在執行時的行爲和功能上沒有什麼區別,只是在程式設計參照時的語法上有一些差別,它可以定義成public、protected、預設的、private等多種型別,而普通類只能定義成public和預設的這兩種型別。在外面參照Static Nested Class類的名稱爲「外部類名.內部類名」。在外面不需要建立外部類的範例物件,就可以直接建立Static Nested Class,例如,假設Inner是定義在Outer類中的Static Nested Class,那麼可以使用如下語句建立Inner類:

Outer.Inner inner = new Outer.Inner();

由於static Nested Class不依賴於外部類的範例物件,所以,static Nested Class能存取外部類的非static成員變數(不能直接存取,需要建立外部類範例才能 纔能存取非靜態變數)。當在外部類中存取Static Nested Class時,可以直接使用Static Nested Class的名字,而不需要加上外部類的名字了,在Static Nested Class中也可以直接參照外部類的static的成員變數,不需要加上外部類的名字。

在靜態方法中定義的內部類也是Static Nested Class,這時候不能在類前面加static關鍵字,靜態方法中的Static Nested Class與普通方法中的內部類的應用方式很相似,它除了可以直接存取外部類中的static的成員變數,還可以存取靜態方法中的區域性變數,但是,該區域性變數前必須加final修飾符。

備註:首先根據你的印象說出你對內部類的總體方面的特點:例如,在兩個地方可以定義,可以存取外部類的成員變數,不能定義靜態成員,這是大的特點。然後再說一些細節方面的知識,例如,幾種定義方式的語法區別,靜態內部類,以及匿名內部類。

27、內部類可以參照它的包含類的成員嗎?有沒有什麼限制?

完全可以。如果不是靜態內部類,那沒有什麼限制!

如果你把靜態巢狀類當作內部類的一種特例,那在這種情況下不可以存取外部類的普通成員變數,而只能存取外部類中的靜態成員,例如,下面 下麪的程式碼:

class Outer

{

static int x;

static class Inner

{

void test()

{

syso(x);

}

}

}

答題時,也要能察言觀色,揣摩提問者的心思,顯然人家希望你說的是靜態內部類不能存取外部類的成員,但你一上來就頂牛,這不好,要先順着人家,讓人家滿意,然後再說特殊情況,讓人家吃驚。

28、Anonymous Inner Class (匿名內部類)是否可以extends(繼承)其它類,是否可以implements(實現)interface(介面)?

可以繼承其他類或實現其他介面。不僅是可以,而是必須!

29、super.getClass()方法呼叫

下面 下麪程式的輸出結果是多少?

Import java.util.Date;

publicclassTestextendsDate{

public static voidmain(String[] args) {

newTest().test();

}

public voidtest(){

System.out.println(super.getClass().getName());

}

}

很奇怪,結果是Test

這屬於腦筋急轉彎的題目,在一個qq羣有個網友正好問過這個問題,我覺得挺有趣,就研究了一下,沒想到今天還被你面到了,哈哈。

在test方法中,直接呼叫getClass().getName()方法,返回的是Test類名

由於getClass()在Object類中定義成了final,子類不能覆蓋該方法,所以,在

test方法中呼叫getClass().getName()方法,其實就是在呼叫從父類別繼承的getClass()方法,等效於呼叫super.getClass().getName()方法,所以,super.getClass().getName()方法返回的也應該是Test。

如果想得到父類別的名稱,應該用如下程式碼:

getClass().getSuperClass().getName();

30、String是最基本的數據型別嗎?

基本數據型別包括byte、int、char、long、float、double、boolean和short。

java.lang.String類是final型別的,因此不可以繼承這個類、不能修改這個類。爲了提高效率節省空間,我們應該用StringBuffer類

31、String s = 「Hello」;s = s + " world!";這兩行程式碼執行後,原始的String物件中的內容到底變了沒有?

沒有。因爲String被設計成不可變(immutable)類,所以它的所有物件都是不可變物件。在這段程式碼中,s原先指向一個String物件,內容是 「Hello」,然後我們對s進行了+操作,那麼s所指向的那個物件是否發生了改變呢?答案是沒有。這時,s不指向原來那個物件了,而指向了另一個 String物件,內容爲"Hello world!",原來那個物件還存在於記憶體之中,只是s這個參照變數不再指向它了。

通過上面的說明,我們很容易導出另一個結論,如果經常對字串進行各種各樣的修改,或者說,不可預見的修改,那麼使用String來代表字串的話會引起很大的記憶體開銷。因爲 String物件建立之後不能再改變,所以對於每一個不同的字串,都需要一個String物件來表示。這時,應該考慮使用StringBuffer類,它允許修改,而不是每個不同的字串都要生成一個新的物件。並且,這兩種類的物件轉換十分容易。

同時,我們還可以知道,如果要使用內容相同的字串,不必每次都new一個String。例如我們要在構造器中對一個名叫s的String參照變數進行初始化,把它設定爲初始值,應當這樣做:

public class Demo {

private String s;

public Demo {

s = 「Initial Value」;

}

}

而非

s = new String(「Initial Value」);

後者每次都會呼叫構造器,生成新物件,效能低下且記憶體開銷大,並且沒有意義,因爲String物件不可改變,所以對於內容相同的字串,只要一個String物件來表示就可以了。也就說,多次呼叫上面的構造器建立多個物件,他們的String型別屬性s都指向同一個物件。

上面的結論還基於這樣一個事實:對於字串常數,如果內容相同,Java認爲它們代表同一個String物件。而用關鍵字new呼叫構造器,總是會建立一個新的物件,無論內容是否相同。

至於爲什麼要把String類設計成不可變類,是它的用途決定的。其實不只String,很多Java標準類庫中的類都是不可變的。在開發一個系統的時候,我們有時候也需要設計不可變類,來傳遞一組相關的值,這也是物件導向思想的體現。不可變類有一些優點,比如因爲它的物件是隻讀的,所以多執行緒併發存取也不會有任何問題。當然也有一些缺點,比如每個不同的狀態都要一個物件來代表,可能會造成效能上的問題。所以Java標準類庫還提供了一個可變版本,即 StringBuffer。

32、是否可以繼承String類?

String類是final類故不可以繼承。

33、String s = new String(「xyz」);建立了幾個String Object?二者之間有什麼區別?

兩個或一個,」xyz」對應一個物件,這個物件放在字串常數緩衝區,常數」xyz」不管出現多少遍,都是緩衝區中的那一個。New String每寫一遍,就建立一個新的物件,它一句那個常數」xyz」物件的內容來建立出一個新String物件。如果以前就用過’xyz’,這句代表就不會建立」xyz」自己了,直接從緩衝區拿。

34、String和StringBuffer的區別

JAVA平臺提供了兩個類:String和StringBuffer,它們可以儲存和操作字串,即包含多個字元的字元數據。這個String類提供了數值不可改變的字串。而這個StringBuffer類提供的字串進行修改。當你知道字元數據要改變的時候你就可以使用StringBuffer。典型地,你可以使用StringBuffers來動態構造字元數據。另外,String實現了equals方法,new String(「abc」).equals(newString(「abc」)的結果爲true,而StringBuffer沒有實現equals方法,所以,new StringBuffer(「abc」).equals(newStringBuffer(「abc」)的結果爲false。

接着要舉一個具體的例子來說明,我們要把1到100的所有數位拼起來,組成一個串。

StringBuffer sbf = new StringBuffer();

for(int i=0;i<100;i++)

{

sbf.append(i);

}

上面的程式碼效率很高,因爲只建立了一個StringBuffer物件,而下面 下麪的程式碼效率很低,因爲建立了101個物件。

String str = new String();

for(int i=0;i<100;i++)

{

str = str + i;

}

在講兩者區別時,應把回圈的次數搞成10000,然後用endTime-beginTime來比較兩者執行的時間差異,最後還要講講StringBuilder與StringBuffer的區別。

String覆蓋了equals方法和hashCode方法,而StringBuffer沒有覆蓋equals方法和hashCode方法,所以,將StringBuffer物件儲存進Java集合類中時會出現問題。

35、如何把一段逗號分割的字串轉換成一個數組?

如果不查jdk api,我很難寫出來!我可以說說我的思路:

1用正則表達式,程式碼大概爲:String [] result = orgStr.split(「,」);

2用 StingTokenizer ,程式碼爲:StringTokenizer tokener = StringTokenizer(orgStr,」,」);

String [] result =new String[tokener .countTokens()];

Int i=0;

while(tokener.hasNext(){

result[i++]=toker.nextToken();

}

36、陣列有沒有length()這個方法? String有沒有length()這個方法?

陣列沒有length()這個方法,有length的屬性。String有有length()這個方法。

37、下面 下麪這條語句一共建立了多少個物件: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,這說明javac編譯可以對字串常數直接相加的表達式進行優化,不必要等到執行期去進行加法運算處理,而是在編譯時去掉其中的加號,直接將其編譯成一個這些常數相連的結果。

題目中的第一行程式碼被編譯器在編譯時優化後,相當於直接定義了一個」abcd」的字串,所以,上面的程式碼應該只建立了一個String物件。寫如下兩行程式碼,

String s =「a」 + 「b」 + 「c」 + 「d」;

System.out.println(s== 「abcd」);

最終列印的結果應該爲true。

38、try {}裡有一個return語句,那麼緊跟在這個try後的finally {}裡的code會不會被執行,什麼時候被執行,在return前還是後?

也許你的答案是在return之前,但往更細地說,我的答案是在return中間執行,請看下面 下麪程式程式碼的執行結果:

publicclassTest {

/**

*@paramargs add by zxx ,Dec 9, 2008

*/

public static voidmain(String[] args) {

//TODOAuto-generated method stub

System.out.println(newTest().test());;

}

staticinttest()

{

intx = 1;

try

{

returnx;

}

finally

{

++x;

}

}

}

---------執行結果 ---------

1

執行結果是1,爲什麼呢?主函數呼叫子函數並得到結果的過程,好比主函數準備一個空罐子,當子函數要返回結果時,先把結果放在罐子裡,然後再將程式邏輯返回到主函數。所謂返回,就是子函數說,我不執行了,你主函數繼續執行吧,這沒什麼結果可言,結果是在說這話之前放進罐子裡的。

39、下面 下麪的程式程式碼輸出的結果是多少?

public classsmallT

{

public staticvoid main(String args[])

{

smallT t = new smallT();

int b = t.get();

System.out.println(b);

}

public int get()

{

try

{

return1 ;

}

finally

{

return2 ;

}

}

}

返回的結果是2。

我可以通過下面 下麪一個例子程式來幫助我解釋這個答案,從下面 下麪例子的執行結果中可以發現,try中的return語句呼叫的函數先於finally中呼叫的函數執行,也就是說return語句先執行,finally語句後執行,所以,返回的結果是2。Return並不是讓函數馬上返回,而是return語句執行後,將把返回結果放置進函數棧中,此時函數並不是馬上返回,它要執行finally語句後才真正開始返回。

在講解答案時可以用下面 下麪的程式來幫助分析:

publicclassTest {

/**

*@paramargs add by zxx ,Dec 9, 2008

*/

public static voidmain(String[] args) {

//TODOAuto-generated method stub

System.out.println(newTest().test());;

}

inttest()

{

try

{

returnfunc1();

}

finally

{

returnfunc2();

}

}

intfunc1()

{

System.out.println(「func1」);

return1;

}

intfunc2()

{

System.out.println(「func2」);

return2;

}

}

-----------執行結果-----------------

func1

func2

2

結論:finally中的程式碼比return和break語句後執行

40、final, finally, finalize的區別。

final 用於宣告屬性,方法和類,分別表示屬性不可變,方法不可覆蓋,類不可繼承。

內部類要存取區域性變數,區域性變數必須定義成final型別,例如,一段程式碼……

finally是例外處理語句結構的一部分,表示總是執行。

finalize是Object類的一個方法,在垃圾收集器執行的時候會呼叫被回收物件的此方法,可以覆蓋此方法提供垃圾收集時的其他資源回收,例如關閉檔案等。JVM不保證此方法總被呼叫

41、執行時異常與一般異常有何異同?

異常表示程式執行過程中可能出現的非正常狀態,執行時異常表示虛擬機器的通常操作中可能遇到的異常,是一種常見執行錯誤。java編譯器要求方法必須宣告拋出可能發生的非執行時異常,但是並不要求必須宣告拋出未被捕獲的執行時異常。

42、error和exception有什麼區別?

error 表示恢復不是不可能但很困難的情況下的一種嚴重問題。比如說記憶體溢位。不可能指望程式能處理這樣的情況。 exception表示一種設計或實現問題。也就是說,它表示如果程式執行正常,從不會發生的情況。

43、Java中的例外處理機制 機製的簡單原理和應用。

異常是指java程式執行時(非編譯)所發生的非正常情況或錯誤,與現實生活中的事件很相似,現實生活中的事件可以包含事件發生的時間、地點、人物、情節等資訊,可以用一個物件來表示,Java使用物件導向的方式來處理異常,它把程式中發生的每個異常也都分別封裝到一個物件來表示的,該物件中包含有異常的資訊。

Java對異常進行了分類,不同類型的異常分別用不同的Java類表示,所有異常的根類爲java.lang.Throwable,Throwable下面 下麪又派生了兩個子類:Error和Exception,Error表示應用程式本身無法克服和恢復的一種嚴重問題,程式只有死的份了,例如,說記憶體溢位和執行緒死鎖等系統問題。Exception表示程式還能夠克服和恢復的問題,其中又分爲系統異常和普通異常,系統異常是軟體本身缺陷所導致的問題,也就是軟件開發人員考慮不周所導致的問題,軟體使用者無法克服和恢復這種問題,但在這種問題下還可以讓軟件系統繼續執行或者讓軟體死掉,例如,陣列指令碼越界(ArrayIndexOutOfBoundsException),空指針異常(NullPointerException)、類轉換異常(ClassCastException);普通異常是執行環境的變化或異常所導致的問題,是使用者能夠克服的問題,例如,網路斷線,硬碟空間不夠,發生這樣的異常後,程式不應該死掉。

java爲系統異常和普通異常提供了不同的解決方案,編譯器強制普通異常必須try…catch處理或用throws宣告繼續拋給上層呼叫方法處理,所以普通異常也稱爲checked異常,而系統異常可以處理也可以不處理,所以,編譯器不強制用try…catch處理或用throws宣告,所以系統異常也稱爲unchecked異常。

提示答題者:就按照三個級別去思考:虛擬機器必須宕機的錯誤,程式可以死掉也可以不死掉的錯誤,程式不應該死掉的錯誤;

44、請寫出你最常見到的5個runtime exception。

這道題主要考你的程式碼量到底多大,如果你長期寫程式碼的,應該經常都看到過一些系統方面的異常,你不一定真要回答出5個具體的系統異常,但你要能夠說出什麼是系統異常,以及幾個系統異常就可以了,當然,這些異常完全用其英文名稱來寫是最好的,如果實在寫不出,那就用中文吧,有總比沒有強!

所謂系統異常,就是……,它們都是RuntimeException的子類,在jdk doc中查RuntimeException類,就可以看到其所有的子類列表,也就是看到了所有的系統異常。我比較有印象的系統異常有:NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException。

45、JAVA語言如何進行例外處理,關鍵字:throws,throw,try,catch,finally分別代表什麼意義?在try塊中可以拋出異常嗎?

46、java中有幾種方法可以實現一個執行緒?用什麼關鍵字修飾同步方法? stop()和suspend()方法爲何不推薦使用?

java5以前,有如下兩種:

第一種:

new Thread(){}.start();這表示呼叫Thread子類物件的run方法,new Thread(){}表示一個Thread的匿名子類的範例物件,子類加上run方法後的程式碼如下:

new Thread(){

public void run(){

}

}.start();

第二種:

new Thread(new Runnable(){}).start();這表示呼叫Thread物件接受的Runnable物件的run方法,new Runnable(){}表示一個Runnable的匿名子類的範例物件,runnable的子類加上run方法後的程式碼如下:

new Thread(new Runnable(){

public voidrun(){

}

}

).start();

從java5開始,還有如下一些執行緒池建立多執行緒的方式:

ExecutorService pool = Executors.newFixedThreadPool(3)

for(int i=0;i<10;i++)

{

pool.execute(newRunable(){public void run(){}});

}

Executors.newCachedThreadPool().execute(new Runable(){publicvoid run(){}});

Executors.newSingleThreadExecutor().execute(new Runable(){publicvoid run(){}});

有兩種實現方法,分別使用new Thread()和new Thread(runnable)形式,第一種直接呼叫thread的run方法,所以,我們往往使用Thread子類,即new SubThread()。第二種呼叫runnable的run方法。

有兩種實現方法,分別是繼承Thread類與實現Runnable介面

用synchronized關鍵字修飾同步方法

反對使用stop(),是因爲它不安全。它會解除由執行緒獲取的所有鎖定,而且如果物件處於一種不連貫狀態,那麼其他執行緒能在那種狀態下檢查和修改它們。結果很難檢查出真正的問題所在。suspend()方法容易發生死鎖。呼叫suspend()的時候,目標執行緒會停下來,但卻仍然持有在這之前獲得的鎖定。此時,其他任何執行緒都不能存取鎖定的資源,除非被"掛起"的執行緒恢復執行。對任何執行緒來說,如果它們想恢復目標執行緒,同時又試圖使用任何一個鎖定的資源,就會造成死鎖。所以不應該使用suspend(),而應在自己的Thread類中置入一個標誌,指出線程應該活動還是掛起。若標誌指出線程應該掛起,便用wait()命其進入等待狀態。若標誌指出線程應當恢復,則用一個notify()重新啓動執行緒。

47、sleep()和 wait()有什麼區別?

(網上的答案:sleep是執行緒類(Thread)的方法,導致此執行緒暫停執行指定時間,給執行機會給其他執行緒,但是監控狀態依然保持,到時後會自動恢復。呼叫sleep不會釋放物件鎖。 wait是Object類的方法,對此物件呼叫wait方法導致本執行緒放棄物件鎖,進入等待此物件的等待鎖定池,只有針對此物件發出notify方法(或notifyAll)後本執行緒才進入物件鎖定池準備獲得物件鎖進入執行狀態。)

sleep就是正在執行的執行緒主動讓出cpu,cpu去執行其他執行緒,在sleep指定的時間過後,cpu纔會回到這個執行緒上繼續往下執行,如果當前執行緒進入了同步鎖,sleep方法並不會釋放鎖,即使當前執行緒使用sleep方法讓出了cpu,但其他被同步鎖擋住了的執行緒也無法得到執行。wait是指在一個已經進入了同步鎖的執行緒內,讓自己暫時讓出同步鎖,以便其他正在等待此鎖的執行緒可以得到同步鎖並執行,只有其他執行緒呼叫了notify方法(notify並不釋放鎖,只是告訴呼叫過wait方法的執行緒可以去參與獲得鎖的競爭了,但不是馬上得到鎖,因爲鎖還在別人手裏,別人還沒釋放。如果notify方法後面的程式碼還有很多,需要這些程式碼執行完後纔會釋放鎖,可以在notfiy方法後增加一個等待和一些程式碼,看看效果),呼叫wait方法的執行緒就會解除wait狀態和程式可以再次得到鎖後繼續向下執行。對於wait的講解一定要配合例子程式碼來說明,才顯得自己真明白。

packagecom.huawei.interview;

publicclassMultiThread {

/**

*@paramargs

*/

public static voidmain(String[] args) {

//TODOAuto-generated method stub

newThread(newThread1()).start();

try{

Thread.sleep(10);

}catch(InterruptedException e) {

//TODOAuto-generated catchblock

e.printStackTrace();

}

newThread(newThread2()).start();

}

private static classThread1implementsRunnable

{

@Override

public voidrun() {

//TODOAuto-generated methodstub

//由於這裏的Thread1和下面 下麪的Thread2內部run方法要用同一物件作爲監視器,我們這裏不能用this,因爲在Thread2裏面的this和這個Thread1的this不是同一個物件。我們用MultiThread.class這個位元組碼物件,當前虛擬機器裡參照這個變數時,指向的都是同一個物件。

synchronized(MultiThread.class){

System.out.println(「enterthread1…」);

System.out.println(「thread1is waiting」);

try{

//釋放鎖有兩種方式,第一種方式是程式自然離開監視器的範圍,也就是離開了synchronized關鍵字管轄的程式碼範圍,另一種方式就是在synchronized關鍵字管轄的程式碼內部呼叫監視器物件的wait方法。這裏,使用wait方法釋放鎖。

MultiThread.class.wait();

}catch(InterruptedException e) {

//TODOAuto-generatedcatch block

e.printStackTrace();

}

System.out.println(「thread1is going on…」);

System.out.println(「thread1is being over!」);

}

}

}

private static classThread2implementsRunnable

{

@Override

public voidrun() {

//TODOAuto-generated methodstub

synchronized(MultiThread.class){

System.out.println(「enterthread2…」);

System.out.println(「thread2notify other thread can release wait status…」);

//由於notify方法並不釋放鎖,即使thread2呼叫下面 下麪的sleep方法休息了10毫秒,但thread1仍然不會執行,因爲thread2沒有釋放鎖,所以Thread1無法得不到鎖。

MultiThread.class.notify();

System.out.println(「thread2is sleeping ten millisecond…」);

try{

Thread.sleep(10);

}catch(InterruptedExceptione) {

//TODOAuto-generatedcatch block

e.printStackTrace();

}

System.out.println(「thread2is going on…」);

System.out.println(「thread2is being over!」);

}

}

}

}

48、同步和非同步有何異同,在什麼情況下分別使用他們?舉例說明。

如果數據將線上程間共用。例如正在寫的數據以後可能被另一個執行緒讀到,或者正在讀的數據可能已經被另一個執行緒寫過了,那麼這些數據就是共用數據,必須進行同步存取。

當應用程式在物件上呼叫了一個需要花費很長時間來執行的方法,並且不希望讓程式等待方法的返回時,就應該使用非同步程式設計,在很多情況下採用非同步途徑往往更有效率。

  1. 下面 下麪兩個方法同步嗎?(自己發明)

class Test

{

synchronizedstatic voidsayHello3()

{

}

synchronizedvoidgetX(){}

}

50、多執行緒有幾種實現方法?同步有幾種實現方法?

多執行緒有兩種實現方法,分別是繼承Thread類與實現Runnable介面

同步的實現方面有兩種,分別是synchronized,wait與notify

wait():使一個執行緒處於等待狀態,並且釋放所持有的物件的lock。

sleep():使一個正在執行的執行緒處於睡眠狀態,是一個靜態方法,呼叫此方法要捕捉InterruptedException異常。

notify():喚醒一個處於等待狀態的執行緒,注意的是在呼叫此方法的時候,並不能確切的喚醒某一個等待狀態的執行緒,而是由JVM確定喚醒哪個執行緒,而且不是按優先順序。

Allnotity():喚醒所有處入等待狀態的執行緒,注意並不是給所有喚醒執行緒一個物件的鎖,而是讓它們競爭。

51、啓動一個執行緒是用run()還是start()? .

啓動一個執行緒是呼叫start()方法,使執行緒就緒狀態,以後可以被排程爲執行狀態,一個執行緒必須關聯一些具體的執行程式碼,run()方法是該執行緒所關聯的執行程式碼。

52、當一個執行緒進入一個物件的一個synchronized方法後,其它執行緒是否可進入此物件的其它方法?

分幾種情況:

1.其他方法前是否加了synchronized關鍵字,如果沒加,則能。

2.如果這個方法內部呼叫了wait,則可以進入其他synchronized方法。

3.如果其他個方法都加了synchronized關鍵字,並且內部沒有呼叫wait,則不能。

4.如果其他方法是static,它用的同步鎖是當前類的位元組碼,與非靜態的方法不能同步,因爲非靜態的方法用的是this。

53、執行緒的基本概念、執行緒的基本狀態以及狀態之間的關係

一個程式中可以有多條執行線索同時執行,一個執行緒就是程式中的一條執行線索,每個執行緒上都關聯有要執行的程式碼,即可以有多段程式程式碼同時執行,每個程式至少都有一個執行緒,即main方法執行的那個執行緒。如果只是一個cpu,它怎麼能夠同時執行多段程式呢?這是從宏觀上來看的,cpu一會執行a線索,一會執行b線索,切換時間很快,給人的感覺是a,b在同時執行,好比大家在同一個辦公室上網,只有一條鏈接到外部網線,其實,這條網線一會爲a傳數據,一會爲b傳數據,由於切換時間很短暫,所以,大家感覺都在同時上網。

狀態:就緒,執行,synchronize阻塞,wait和sleep掛起,結束。wait必須在synchronized內部呼叫。

呼叫執行緒的start方法後執行緒進入就緒狀態,執行緒排程系統將就緒狀態的執行緒轉爲執行狀態,遇到synchronized語句時,由執行狀態轉爲阻塞,當synchronized獲得鎖後,由阻塞轉爲執行,在這種情況可以呼叫wait方法轉爲掛起狀態,當執行緒關聯的程式碼執行完後,執行緒變爲結束狀態。

54、簡述synchronized和java.util.concurrent.locks.Lock的異同?

主要相同點:Lock能完成synchronized所實現的所有功能

主要不同點:Lock有比synchronized更精確的執行緒語意和更好的效能。synchronized會自動釋放鎖,而Lock一定要求程式設計師手工釋放,並且必須在finally從句中釋放。Lock還有更強大的功能,例如,它的tryLock方法可以非阻塞方式去拿鎖。

舉例說明(對下面 下麪的題用lock進行了改寫):

packagecom.huawei.interview;

importjava.util.concurrent.locks.Lock;

importjava.util.concurrent.locks.ReentrantLock;

publicclassThreadTest {

/**

*@paramargs

*/

private intj;

privateLock lock =newReentrantLock();

public static voidmain(String[] args) {

//TODOAuto-generated method stub

ThreadTest tt =newThreadTest();

for(inti=0;i<2;i++)

{

newThread(tt.newAdder()).start();

newThread(tt.newSubtractor()).start();

}

}

private classSubtractorimplementsRunnable

{

@Override

public voidrun() {

//TODOAuto-generated methodstub

while(true)

{

/*synchronized (ThreadTest.this) {

System.out.println(「j–=」+ j–);

//這裏拋異常了,鎖能釋放嗎?

}*/

lock.lock();

try

{

System.out.println(「j–=」+ j–);

}finally

{

lock.unlock();

}

}

}

}

private classAdderimplementsRunnable

{

@Override

public voidrun() {

//TODOAuto-generated methodstub

while(true)

{

/*synchronized (ThreadTest.this) {

System.out.println(「j++=」+ j++);

}*/

lock.lock();

try

{

System.out.println(「j++=」+ j++);

}finally

{

lock.unlock();

}

}

}

}

}

55、設計4個執行緒,其中兩個執行緒每次對j增加1,另外兩個執行緒對j每次減少1。寫出程式。

以下程式使用內部類實現執行緒,對j增減的時候沒有考慮順序問題。

public class ThreadTest1

{

private int j;

public static void main(String args[]){

ThreadTest1 tt=newThreadTest1();

Inc inc=tt.new Inc();

Dec dec=tt.new Dec();

for(inti=0;i<2;i++){

Thread t=newThread(inc);

t.start();

t=new Thread(dec);

t.start();

}

}

private synchronized void inc(){

j++;

System.out.println(Thread.currentThread().getName()+"-inc:"+j);

}

private synchronized void dec(){

j–;

System.out.println(Thread.currentThread().getName()+"-dec:"+j);

}

class Inc implements Runnable{

public void run(){

for(inti=0;i<100;i++){

inc();

}

}

}

class Dec implements Runnable{

public void run(){

for(inti=0;i<100;i++){

dec();

}

}

}

}

----------隨手再寫的一個-------------

class A

{

JManger j =new JManager();

main()

{

new A().call();

}

void call

{

for(int i=0;i<2;i++)

{

new Thread(

newRunnable(){ public void run(){while(true){j.accumulate()}}}

).start();

new Thread(newRunnable(){ public void run(){while(true){j.sub()}}}).start();

}

}

}

class JManager

{

private j = 0;

public synchronized voidsubtract()

{

j–

}

public synchronized voidaccumulate()

{

j++;

}

}

56、子執行緒回圈10次,接着主執行緒回圈100,接着又回到子執行緒回圈10次,接着再回到主執行緒又回圈100,如此回圈50次,請寫出程式。

最終的程式程式碼如下:

publicclassThreadTest {

/**

*@paramargs

*/

public static voidmain(String[] args) {

//TODOAuto-generated method stub

newThreadTest().init();

}

public voidinit()

{

finalBusiness business =newBusiness();

newThread(

newRunnable()

{

public voidrun() {

for(inti=0;i<50;i++)

{

business.SubThread(i);

}

}

}

).start();

for(inti=0;i<50;i++)

{

business.MainThread(i);

}

}

private classBusiness

{

booleanbShouldSub =true;//這裏相當於定義了控制該誰執行的一個信號燈

public synchronized voidMainThread(inti)

{

if(bShouldSub)

try{

this.wait();

}catch(InterruptedException e) {

//TODOAuto-generatedcatch block

e.printStackTrace();

}

for(intj=0;j<5;j++)

{

System.out.println(Thread.currentThread().getName()+ 「:i=」 + i +",j=" + j);

}

bShouldSub =true;

this.notify();

}

public synchronized voidSubThread(inti)

{

if(!bShouldSub)

try{

this.wait();

}catch(InterruptedExceptione) {

//TODOAuto-generatedcatch block

e.printStackTrace();

}

for(intj=0;j<10;j++)

{

System.out.println(Thread.currentThread().getName()+ 「:i=」 + i +",j=" + j);

}

bShouldSub =false;

this.notify();

}

}

}

備註:不可能一上來就寫出上面的完整程式碼,最初寫出來的程式碼如下,問題在於兩個執行緒的程式碼要參照同一個變數,即這兩個執行緒的程式碼要共用數據,所以,把這兩個執行緒的執行程式碼搬到同一個類中去:

packagecom.huawei.interview.lym;

publicclassThreadTest {

private static booleanbShouldMain=false;

public static voidmain(String[]args) {

//TODOAuto-generated method stub

/*new Thread(){

public void run()

{

for(int i=0;i<50;i++)

{

for(int j=0;j<10;j++)

{

System.out.println(「i=」+ i + 「,j=」 + j);

}

}

}

}.start();*/

//final String str = newString("");

newThread(

newRunnable()

{

public voidrun()

{

for(inti=0;i<50;i++)

{

synchronized(ThreadTest.class) {

if(bShouldMain)

{

try{

ThreadTest.class.wait();}

catch(InterruptedException e) {

e.printStackTrace();

}

}

for(intj=0;j<10;j++)

{

System.out.println(

Thread.currentThread().getName()+

「i=」+ i + 「,j=」 + j);

}

bShouldMain=true;

ThreadTest.class.notify();

}

}

}

}

).start();

for(inti=0;i<50;i++)

{

synchronized(ThreadTest.class){

if(!bShouldMain)

{

try{

ThreadTest.class.wait();}

catch(InterruptedException e) {

e.printStackTrace();

}

}

for(intj=0;j<5;j++)

{

System.out.println(

Thread.currentThread().getName()+

「i=」 + i +",j=" + j);

}

bShouldMain=false;

ThreadTest.class.notify();

}

}

}

}

下面 下麪使用jdk5中的併發庫來實現的:

import java.util.concurrent.Executors;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

import java.util.concurrent.locks.Condition;

public class ThreadTest

{

private static Locklock = new ReentrantLock();

private staticCondition subThreadCondition = lock.newCondition();

private staticboolean bBhouldSubThread = false;

public static voidmain(String [] args)

{

ExecutorServicethreadPool = Executors.newFixedThreadPool(3);

threadPool.execute(newRunnable(){

publicvoid run()

{

for(inti=0;i<50;i++)

{

lock.lock();

try

{

if(!bBhouldSubThread)

subThreadCondition.await();

for(intj=0;j<10;j++)

{

System.out.println(Thread.currentThread().getName()+ 「,j=」 + j);

}

bBhouldSubThread= false;

subThreadCondition.signal();

}catch(Exceptione)

{

}

finally

{

lock.unlock();

}

}

}

});

threadPool.shutdown();

for(inti=0;i<50;i++)

{

lock.lock();

try

{

if(bBhouldSubThread)

subThreadCondition.await();

for(intj=0;j<10;j++)

{

System.out.println(Thread.currentThread().getName()+ 「,j=」 + j);

}

bBhouldSubThread= true;

subThreadCondition.signal();

}catch(Exceptione)

{

}

finally

{

lock.unlock();

}

}

}

}

57、介紹Collection框架的結構

答:隨意發揮題,天南海北誰便談,只要讓別覺得你知識淵博,理解透徹即可。

58、Collection框架中實現比較要實現什麼介面

comparable/comparator

59、ArrayList和Vector的區別

答:

這兩個類都實現了List介面(List介面繼承了Collection介面),他們都是有序集合,即儲存在這兩個集閤中的元素的位置都是有順序的,相當於一種動態的陣列,我們以後可以按位元置索引號取出某個元素,,並且其中的數據是允許重複的,這是HashSet之類的集合的最大不同處,HashSet之類的集合不可以按索引號去檢索其中的元素,也不允許有重複的元素(本來題目問的與hashset沒有任何關係,但爲了說清楚ArrayList與Vector的功能,我們使用對比方式,更有利於說明問題)。

接着才說ArrayList與Vector的區別,這主要包括兩個方面:.

(1)同步性:

Vector是執行緒安全的,也就是說是它的方法之間是執行緒同步的,而ArrayList是執行緒序不安全的,它的方法之間是執行緒不同步的。如果只有一個執行緒會存取到集合,那最好是使用ArrayList,因爲它不考慮執行緒安全,效率會高些;如果有多個執行緒會存取到集合,那最好是使用Vector,因爲不需要我們自己再去考慮和編寫執行緒安全的程式碼。

備註:對於Vector&ArrayList、Hashtable&HashMap,要記住執行緒安全的問題,記住Vector與Hashtable是舊的,是java一誕生就提供了的,它們是執行緒安全的,ArrayList與HashMap是java2時才提供的,它們是執行緒不安全的。所以,我們講課時先講老的。

(2)數據增長:

ArrayList與Vector都有一個初始的容量大小,當儲存進它們裏面的元素的個數超過了容量時,就需要增加ArrayList與Vector的儲存空間,每次要增加儲存空間時,不是隻增加一個儲存單元,而是增加多個儲存單元,每次增加的儲存單元的個數在記憶體空間利用與程式效率之間要取得一定的平衡。Vector預設增長爲原來兩倍,而ArrayList的增長策略在文件中沒有明確規定(從原始碼看到的是增長爲原來的1.5倍)。ArrayList與Vector都可以設定初始的空間大小,Vector還可以設定增長的空間大小,而ArrayList沒有提供設定增長空間的方法。

總結:即Vector增長原來的一倍,ArrayList增加原來的0.5倍。

60、HashMap和Hashtable的區別

(條理上還需要整理,也是先說相同點,再說不同點)

HashMap是Hashtable的輕量級實現(非執行緒安全的實現),他們都完成了Map介面,主要區別在於HashMap允許空(null)鍵值(key),由於非執行緒安全,在只有一個執行緒存取的情況下,效率要高於Hashtable。

HashMap允許將null作爲一個entry的key或者value,而Hashtable不允許。

HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因爲contains方法容易讓人引起誤解。

Hashtable繼承自Dictionary類,而HashMap是Java1.2引進的Map interface的一個實現。

最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多個執行緒存取Hashtable時,不需要自己爲它的方法實現同步,而HashMap就必須爲之提供外同步。

Hashtable和HashMap採用的hash/rehash演算法都大概一樣,所以效能不會有很大的差異。

就HashMap與HashTable主要從三方面來說。

一.歷史原因:Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map介面的一個實現

二.同步性:Hashtable是執行緒安全的,也就是說是同步的,而HashMap是執行緒序不安全的,不是同步的

三.值:只有HashMap可以讓你將空值作爲一個表的條目的key或value

61、List和 Map區別?

一個是儲存單列數據的集合,另一個是儲存鍵和值這樣的雙列數據的集合,List中儲存的數據是有順序,並且允許重複;Map中儲存的數據是沒有順序的,其鍵是不能重複的,它的值是可以有重複的。

62、List, Set, Map是否繼承自Collection介面?

List,Set是,Map不是

63、List、Map、Set三個介面,存取元素時,各有什麼特點?

這樣的題屬於隨意發揮題:這樣的題比較考水平,兩個方面的水平:一是要真正明白這些內容,二是要有較強的總結和表述能力。如果你明白,但表述不清楚,在別人那裏則等同於不明白。

首先,List與Set具有相似性,它們都是單列元素的集合,所以,它們有一個功共同的父介面,叫Collection。Set裏面不允許有重複的元素,所謂重複,即不能有兩個相等(注意,不是僅僅是相同)的物件,即假設Set集閤中有了一個A物件,現在我要向Set集合再存入一個B物件,但B物件與A物件equals相等,則B物件儲存不進去,所以,Set集合的add方法有一個boolean的返回值,當集閤中沒有某個元素,此時add方法可成功加入該元素時,則返回true,當集合含有與某個元素equals相等的元素時,此時add方法無法加入該元素,返回結果爲false。Set取元素時,沒法說取第幾個,只能以Iterator介面取得所有的元素,再逐一遍歷各個元素。

List表示有先後順序的集合,注意,不是那種按年齡、按大小、按價格之類的排序。當我們多次呼叫add(Obj e)方法時,每次加入的物件就像火車站買票有排隊順序一樣,按先來後到的順序排序。有時候,也可以插隊,即呼叫add(int index,Obj e)方法,就可以指定當前物件在集閤中的存放位置。一個物件可以被反覆 反復儲存進List中,每呼叫一次add方法,這個物件就被插入進集閤中一次,其實,並不是把這個物件本身儲存進了集閤中,而是在集閤中用一個索引變數指向這個物件,當這個物件被add多次時,即相當於集閤中有多個索引指向了這個物件,如圖x所示。List除了可以以Iterator介面取得所有的元素,再逐一遍歷各個元素之外,還可以呼叫get(index i)來明確說明取第幾個。

Map與List和Set不同,它是雙列的集合,其中有put方法,定義如下:put(obj key,objvalue),每次儲存時,要儲存一對key/value,不能儲存重複的key,這個重複的規則也是按equals比較相等。取則可以根據key獲得相應的value,即get(Object key)返回值爲key所對應的value。另外,也可以獲得所有的key的結合,還可以獲得所有的value的結合,還可以獲得key和value組合成的Map.Entry物件的集合。

List 以特定次序來持有元素,可有重複元素。Set無法擁有重複元素,內部排序。Map儲存key-value值,value可多值。

HashSet按照hashcode值的某種運算方式進行儲存,而不是直接按hashCode值的大小進行儲存。例如,「abc」—> 78,「def」 —> 62,「xyz」 —> 65在hashSet中的儲存順序不是62,65,78,這些問題感謝以前一個叫崔健的學員提出,最後通過檢視原始碼給他解釋清楚,看本次培訓學員當中有多少能看懂原始碼。LinkedHashSet按插入的順序儲存,那被儲存物件的hashcode方法還有什麼作用呢?學員想想!hashset集合比較兩個物件是否相等,首先看hashcode方法是否相等,然後看equals方法是否相等。new兩個Student插入到HashSet中,看HashSet的size,實現hashcode和equals方法後再看size。

同一個物件可以在Vector中加入多次。往集合裏面加元素,相當於集合裡用一根繩子連線到了目標物件。往HashSet中卻加不了多次的。

64、說出ArrayList,Vector, LinkedList的儲存效能和特性

ArrayList和Vector都是使用陣列方式儲存數據,此陣列元素數大於實際儲存的數據以便增加和插入元素,它們都允許直接按序號索引元素,但是插入元素要涉及陣列元素移動等記憶體操作,所以索引數據快而插入數據慢,Vector由於使用了synchronized方法(執行緒安全),通常效能上較ArrayList差,而LinkedList使用雙向鏈表實現儲存,按序號索引數據需要進行前向或後向遍歷,但是插入數據時只需要記錄本項的前後項即可,所以插入速度較快。

LinkedList也是執行緒不安全的,LinkedList提供了一些方法,使得LinkedList可以被當作堆疊和佇列來使用。

65、去掉一個Vector集閤中重複的元素

Vector newVector = new Vector();

For (int i=0;i

{

Object obj = vector.get(i);

if(!newVector.contains(obj);

newVector.add(obj);

}

還有一種簡單的方式,HashSet set = new HashSet(vector);

66、Collection和 Collections的區別。

Collection是集合類的上級介面,繼承與他的介面主要有Set和List.

Collections是針對集合類的一個幫助類,他提供一系列靜態方法實現對各種集合的搜尋、排序、執行緒安全化等操作。

67、Set裡的元素是不能重複的,那麼用什麼方法來區分重複與否呢?是用==還是equals()?它們有何區別?

Set裡的元素是不能重複的,元素重複與否是使用equals()方法進行判斷的。

equals()和==方法決定參照值是否指向同一物件equals()在類中被覆蓋,爲的是當兩個分離的物件的內容和型別相配的話,返回真值。

68、你所知道的集合類都有哪些?主要方法?

最常用的集合類是 List 和 Map。 List的具體實現包括 ArrayList和 Vector,它們是可變大小的列表,比較適合構建、儲存和操作任何型別物件的元素列表。 List適用於按數值索引存取元素的情形。

Map 提供了一個更通用的元素儲存方法。 Map集合類用於儲存元素對(稱作"鍵"和"值"),其中每個鍵對映到一個值。

ArrayList/VectoràList

àCollection

HashSet/TreeSetàSet

PropetiesàHashTable

àMap

Treemap/HashMap

我記的不是方法名,而是思想,我知道它們都有增刪改查的方法,但這些方法的具體名稱,我記得不是很清楚,對於set,大概的方法是add,remove, contains;對於map,大概的方法就是put,remove,contains等,因爲,我只要在eclispe下按點操作符,很自然的這些方法就出來了。我記住的一些思想就是List類會有get(int index)這樣的方法,因爲它可以按順序取元素,而set類中沒有get(int index)這樣的方法。List和set都可以迭代出所有元素,迭代時先要得到一個iterator物件,所以,set和list類都有一個iterator方法,用於返回那個iterator物件。map可以返回三個集合,一個是返回所有的key的集合,另外一個返回的是所有value的集合,再一個返回的key和value組合成的EntrySet物件的集合,map也有get方法,參數是key,返回值是key對應的value。

69、兩個物件值相同(x.equals(y) == true),但卻可有不同的hash code,這句話對不對?

對。

如果物件要儲存在HashSet或HashMap中,它們的equals相等,那麼,它們的hashcode值就必須相等。

如果不是要儲存在HashSet或HashMap,則與hashcode沒有什麼關係了,這時候hashcode不等是可以的,例如arrayList儲存的物件就不用實現hashcode,當然,我們沒有理由不實現,通常都會去實現的。

70、TreeSet裏面放物件,如果同時放入了父類別和子類的範例物件,那比較時使用的是父類別的compareTo方法,還是使用的子類的compareTo方法,還是拋異常!

(應該是沒有針對問題的確切的答案,當前的add方法放入的是哪個物件,就呼叫哪個物件的compareTo方法,至於這個compareTo方法怎麼做,就看當前這個物件的類中是如何編寫這個方法的)

實驗程式碼:

publicclassParentimplementsComparable {

private intage = 0;

publicParent(intage){

this.age = age;

}

public intcompareTo(Object o){

//TODOAuto-generated method stub

System.out.println(「method ofparent」);

Parent o1 = (Parent)o;

returnage>o1.age?1:age

}

}

publicclassChildextendsParent {

publicChild(){

super(3);

}

public intcompareTo(Object o){

//TODOAuto-generated methodstub

System.out.println(「methodof child」);

//Child o1 = (Child)o;

return1;

}

}

publicclassTreeSetTest {

/**

*@paramargs

*/

public static voidmain(String[] args) {

//TODOAuto-generated method stub

TreeSet set =newTreeSet();

set.add(newParent(3));

set.add(newChild());

set.add(newParent(4));

System.out.println(set.size());

}

}

71、說出一些常用的類,包,介面,請各舉5個

要讓人家感覺你對java ee開發很熟,所以,不能僅僅只列core java中的那些東西,要多列你在做ssh專案中涉及的那些東西。就寫你最近寫的那些程式中涉及的那些類。

常用的類:BufferedReaderBufferedWriter FileReader FileWirter String Integer

java.util.Date,System,Class,List,HashMap

常用的包:java.langjava.io java.util java.sql,javax.servlet,org.apache.strtuts.action,org.hibernate

常用的介面:RemoteList Map Document NodeList,Servlet,HttpServletRequest,HttpServletResponse,Transaction(Hibernate)、Session(Hibernate),HttpSession

72、java中有幾種型別的流?JDK爲每種型別的流提供了一些抽象類以供繼承,請說出他們分別是哪些類?

位元組流,字元流。位元組流繼承於InputStream OutputStream,字元流繼承於InputStreamReaderOutputStreamWriter。在java.io包中還有許多其他的流,主要是爲了提高效能和使用方便。

73、位元組流與字元流的區別

要把一片二進制數據數據逐一輸出到某個裝置中,或者從某個裝置中逐一讀取一片二進制數據,不管輸入輸出裝置是什麼,我們要用統一的方式來完成這些操作,用一種抽象的方式進行描述,這個抽象描述方式起名爲IO流,對應的抽象類爲OutputStream和InputStream,不同的實現類就代表不同的輸入和輸出裝置,它們都是針對位元組進行操作的。

在應用中,經常要完全是字元的一段文字輸出去或讀進來,用位元組流可以嗎?計算機中的一切最終都是二進制的位元組形式存在。對於「中國」這些字元,首先要得到其對應的位元組,然後將位元組寫入到輸出流。讀取時,首先讀到的是位元組,可是我們要把它顯示爲字元,我們需要將位元組轉換成字元。由於這樣的需求很廣泛,人家專門提供了字元流的包裝類。

底層裝置永遠只接受位元組數據,有時候要寫字串到底層裝置,需要將字串轉成位元組再進行寫入。字元流是位元組流的包裝,字元流則是直接接受字串,它內部將串轉成位元組,再寫入底層裝置,這爲我們向IO設別寫入或讀取字串提供了一點點方便。

字元向位元組轉換時,要注意編碼的問題,因爲字串轉成位元組陣列,

其實是轉成該字元的某種編碼的位元組形式,讀取也是反之的道理。

講解位元組流與字元流關係的程式碼案例:

import java.io.BufferedReader;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.FileReader;

import java.io.FileWriter;

import java.io.InputStreamReader;

import java.io.PrintWriter;

public class IOTest {

public static void main(String[]args) throws Exception {

String str = 「中國人」;

/*FileOutputStreamfos = newFileOutputStream(「1.txt」);

fos.write(str.getBytes(「UTF-8」));

fos.close();*/

/*FileWriter fw =new FileWriter(「1.txt」);

fw.write(str);

fw.close();*/

PrintWriter pw =new PrintWriter(「1.txt」,「utf-8」);

pw.write(str);

pw.close();

/*FileReader fr =new FileReader(「1.txt」);

char[] buf = newchar[1024];

int len =fr.read(buf);

String myStr = newString(buf,0,len);

System.out.println(myStr);*/

/*FileInputStreamfr = new FileInputStream(「1.txt」);

byte[] buf = newbyte[1024];

int len =fr.read(buf);

String myStr = newString(buf,0,len,「UTF-8」);

System.out.println(myStr);*/

BufferedReader br =new BufferedReader(

newInputStreamReader(

newFileInputStream(「1.txt」),「UTF-8」

)

);

String myStr =br.readLine();

br.close();

System.out.println(myStr);

}

}

74、什麼是java序列化,如何實現java序列化?或者請解釋Serializable介面的作用。

我們有時候將一個java物件變成位元組流的形式傳出去或者從一個位元組流中恢復成一個java物件,例如,要將java物件儲存到硬碟或者傳送給網路上的其他計算機,這個過程我們可以自己寫程式碼去把一個java物件變成某個格式的位元組流再傳輸,但是,jre本身就提供了這種支援,我們可以呼叫OutputStream的writeObject方法來做,如果要讓java幫我們做,要被傳輸的物件必須實現serializable介面,這樣,javac編譯時就會進行特殊處理,編譯的類纔可以被writeObject方法操作,這就是所謂的序列化。需要被序列化的類必須實現Serializable介面,該介面是一個mini介面,其中沒有需要實現的方法,implementsSerializable只是爲了標註該物件是可被序列化的。

例如,在web開發中,如果物件被儲存在了Session中,tomcat在重新啓動時要把Session物件序列化到硬碟,這個物件就必須實現Serializable介面。如果物件要經過分佈式系統進行網路傳輸或通過rmi等遠端呼叫,這就需要在網路上傳輸物件,被傳輸的物件就必須實現Serializable介面。

75、描述一下JVM載入class檔案的原理機制 機製?

JVM中類的裝載是由ClassLoader和它的子類來實現的,Java ClassLoader是一個重要的Java執行時系統元件。它負責在執行時查詢和裝入類檔案的類。

76、heap和stack有什麼區別。

java的記憶體分爲兩類,一類是棧記憶體,一類是堆記憶體。棧記憶體是指程式進入一個方法時,會爲這個方法單獨分配一塊私屬儲存空間,用於儲存這個方法內部的區域性變數,當這個方法結束時,分配給這個方法的棧會釋放,這個棧中的變數也將隨之釋放。

堆是與棧作用不同的記憶體,一般用於存放不放在當前方法棧中的那些數據,例如,使用new建立的物件都放在堆裡,所以,它不會隨方法的結束而消失。方法中的區域性變數使用final修飾後,放在堆中,而不是棧中。

77、GC是什麼?爲什麼要有GC?

GC是垃圾收集的意思(Gabage Collection),記憶體處理是程式設計人員容易出現問題的地方,忘記或者錯誤的記憶體回收會導致程式或系統的不穩定甚至崩潰,Java提供的GC功能可以自動監測物件是否超過作用域從而達到自動回收記憶體的目的,Java語言沒有提供釋放已分配記憶體的顯示操作方法。

78、垃圾回收的優點和原理。並考慮2種回收機制 機製。

Java語言中一個顯著的特點就是引入了垃圾回收機制 機製,使c++程式設計師最頭疼的記憶體管理的問題迎刃而解,它使得Java程式設計師在編寫程式的時候不再需要考慮記憶體管理。由於有個垃圾回收機制 機製,Java中的物件不再有"作用域"的概念,只有物件的參照纔有"作用域"。垃圾回收可以有效的防止記憶體泄露,有效的使用可以使用的記憶體。垃圾回收器通常是作爲一個單獨的低級別的執行緒執行,不可預知的情況下對記憶體堆中已經死亡的或者長時間沒有使用的物件進行清楚和回收,程式設計師不能實時的呼叫垃圾回收器對某個物件或所有物件進行垃圾回收。回收機制 機製有分代複製垃圾回收和標記垃圾回收,增量垃圾回收。

79、垃圾回收器的基本原理是什麼?垃圾回收器可以馬上回收記憶體嗎?有什麼辦法主動通知虛擬機器進行垃圾回收?

對於GC來說,當程式設計師建立物件時,GC就開始監控這個物件的地址、大小以及使用情況。通常,GC採用有向圖的方式記錄和管理堆(heap)中的所有物件。通過這種方式確定哪些物件是"可達的",哪些物件是"不可達的"。當GC確定一些物件爲"不可達"時,GC就有責任回收這些記憶體空間。可以。程式設計師可以手動執行System.gc(),通知GC執行,但是Java語言規範並不保證GC一定會執行。

80、什麼時候用assert。

assertion(斷言)在軟件開發中是一種常用的偵錯方式,很多開發語言中都支援這種機制 機製。在實現中,assertion就是在程式中的一條語句,它對一個boolean表達式進行檢查,一個正確程式必須保證這個boolean表達式的值爲true;如果該值爲false,說明程式已經處於不正確的狀態下,assert將給出警告或退出。一般來說,assertion用於保證程式最基本、關鍵的正確性。assertion檢查通常在開發和測試時開啓。爲了提高效能,在軟體發佈後,assertion檢查通常是關閉的。

packagecom.huawei.interview;

publicclassAssertTest {

/**

*@paramargs

*/

public static voidmain(String[] args) {

//TODOAuto-generated method stub

inti = 0;

for(i=0;i<5;i++)

{

System.out.println(i);

}

//假設程式不小心多了一句–i;

–i;

asserti==5;

}

}

81、java中會存在記憶體漏失嗎,請簡單描述。

所謂記憶體泄露就是指一個不再被程式使用的物件或變數一直被佔據在記憶體中。java中有垃圾回收機制 機製,它可以保證一物件不再被參照的時候,即物件程式設計了孤兒的時候,物件將自動被垃圾回收器從記憶體中清除掉。由於Java使用有向圖的方式進行垃圾回收管理,可以消除參照回圈的問題,例如有兩個物件,相互參照,只要它們和根進程不可達的,那麼GC也是可以回收它們的,例如下面 下麪的程式碼可以看到這種情況的記憶體回收:

packagecom.huawei.interview;

importjava.io.IOException;

publicclassGarbageTest {

/**

*@paramargs

*@throwsIOException

*/

public static voidmain(String[] args)throwsIOException {

//TODOAuto-generated method stub

try{

gcTest();

}catch(IOException e) {

//TODOAuto-generated catch block

e.printStackTrace();

}

System.out.println(「hasexited gcTest!」);

System.in.read();

System.in.read();

System.out.println(「out begingc!」);

for(inti=0;i<100;i++)

{

System.gc();

System.in.read();

System.in.read();

}

}

private static voidgcTest()throwsIOException {

System.in.read();

System.in.read();

Person p1 =newPerson();

System.in.read();

System.in.read();

Person p2 =newPerson();

p1.setMate(p2);

p2.setMate(p1);

System.out.println(「beforeexit gctest!」);

System.in.read();

System.in.read();

System.gc();

System.out.println(「exitgctest!」);

}

private static classPerson

{

byte[] data =new byte[20000000];

Person mate =null;

public voidsetMate(Personother)

{

mate = other;

}

}

}

java中的記憶體泄露的情況:長生命週期的物件持有短生命週期物件的參照就很可能發生記憶體泄露,儘管短生命週期物件已經不再需要,但是因爲長生命週期物件持有它的參照而導致不能被回收,這就是java中記憶體泄露的發生場景,通俗地說,就是程式設計師可能建立了一個物件,以後一直不再使用這個物件,這個物件卻一直被參照,即這個物件無用但是卻無法被垃圾回收器回收的,這就是java中可能出現記憶體泄露的情況,例如,快取系統,我們載入了一個物件放在快取中(例如放在一個全域性map物件中),然後一直不再使用它,這個物件一直被快取參照,但卻不再被使用。

檢查java中的記憶體泄露,一定要讓程式將各種分支情況都完整執行到程式結束,然後看某個物件是否被使用過,如果沒有,則才能 纔能判定這個物件屬於記憶體泄露。

如果一個外部類的範例物件的方法返回了一個內部類的範例物件,這個內部類物件被長期參照了,即使那個外部類範例物件不再被使用,但由於內部類持久外部類的範例物件,這個外部類物件將不會被垃圾回收,這也會造成記憶體泄露。

下面 下麪內容來自於網上(主要特點就是清空堆疊中的某個元素,並不是徹底把它從陣列中拿掉,而是把儲存的總數減少,本人寫得可以比這個好,在拿掉某個元素時,順便也讓它從陣列中消失,將那個元素所在的位置的值設定爲null即可):

我實在想不到比那個堆疊更經典的例子了,以致於我還要參照別人的例子,下面 下麪的例子不是我想到的,是書上看到的,當然如果沒有在書上看到,可能過一段時間我自己也想的到,可是那時我說是我自己想到的也沒有人相信的。

public class Stack {

private Object[] elements=new Object[10];

private int size = 0;

public void push(Object e){

ensureCapacity();

elements[size++] = e;

}

public Object pop(){

if( size == 0)

throw new EmptyStackException();

return elements[–size];

}

private void ensureCapacity(){

if(elements.length == size){

Object[] oldElements = elements;

elements = new Object[2 * elements.length+1];

System.arraycopy(oldElements,0, elements, 0, size);

}

}

}

上面的原理應該很簡單,假如堆疊加了10個元素,然後全部彈出來,雖然堆疊是空的,沒有我們要的東西,但是這是個物件是無法回收的,這個才符合了記憶體泄露的兩個條件:無用,無法回收。

但是就是存在這樣的東西也不一定會導致什麼樣的後果,如果這個堆疊用的比較少,也就浪費了幾個K記憶體而已,反正我們的記憶體都上G了,哪裏會有什麼影響,再說這個東西很快就會被回收的,有什麼關係。下面 下麪看兩個例子。

例子1

public class Bad{

public static Stack s=Stack();

static{

s.push(new Object());

s.pop(); //這裏有一個物件發生記憶體泄露

s.push(new Object()); //上面的物件可以被回收了,等於是自愈了

}

}

因爲是static,就一直存在到程式退出,但是我們也可以看到它有自愈功能,就是說如果你的Stack最多有100個物件,那麼最多也就只有100個物件無法被回收其實這個應該很容易理解,Stack內部持有100個參照,最壞的情況就是他們都是無用的,因爲我們一旦放新的進取,以前的參照自然消失!

記憶體泄露的另外一種情況:當一個物件被儲存進HashSet集閤中以後,就不能修改這個物件中的那些參與計算雜湊值的欄位了,否則,物件修改後的雜湊值與最初儲存進HashSet集閤中時的雜湊值就不同了,在這種情況下,即使在contains方法使用該物件的當前參照作爲的參數去HashSet集閤中檢索物件,也將返回找不到物件的結果,這也會導致無法從HashSet集閤中單獨刪除當前物件,造成記憶體泄露。

82、能不能自己寫個類,也叫java.lang.String?

可以,但在應用的時候,需要用自己的類載入器去載入,否則,系統的類載入器永遠只是去載入jre.jar包中的那個java.lang.String。由於在tomcat的web應用程式中,都是由webapp自己的類載入器先自己載入WEB-INF/classess目錄中的類,然後才委託上級的類載入器載入,如果我們在tomcat的web應用程式中寫一個java.lang.String,這時候Servlet程式載入的就是我們自己寫的java.lang.String,但是這麼幹就會出很多潛在的問題,原來所有用了java.lang.String類的都將出現問題。

雖然java提供了endorsed技術,可以覆蓋jdk中的某些類,具體做法是….。但是,能夠被覆蓋的類是有限制範圍,反正不包括java.lang這樣的包中的類。

(下面 下麪的例如主要是便於大家學習理解只用,不要作爲答案的一部分,否則,人家懷疑是題目泄露了)例如,執行下面 下麪的程式:

packagejava.lang;

publicclassString {

/**

*@paramargs

*/

public static voidmain(String[] args) {

//TODOAuto-generated method stub

System.out.println(「string」);

}

}

報告的錯誤如下:

java.lang.NoSuchMethodError:main

Exception inthread 「main」

這是因爲載入了jre自帶的java.lang.String,而該類中沒有main方法。

  1. Java程式碼查錯

abstract class Name {

private String name;

public abstract boolean isStupidName(String name) {}

}

大俠們,這有何錯誤?

答案: 錯。abstract method必須以分號結尾,且不帶花括號。

public class Something {

void doSomething () {

private String s = 「」;

int l = s.length();

}

}

有錯嗎?

答案: 錯。區域性變數前不能放置任何存取修飾符 (private,public,和protected)。final可以用來修飾區域性變數

(final如同abstract和strictfp,都是非存取修飾符,strictfp只能修飾class和method而非variable)。

abstract class Something {

private abstract String doSomething ();

}

這好像沒什麼錯吧?

答案: 錯。abstract的methods不能以private修飾。abstract的methods就是讓子類implement(實現)具體細節的,怎麼可以用private把abstract

method封鎖起來呢? (同理,abstract method前不能加final)。

public class Something {

public int addOne(final int x) {

return ++x;

}

}

這個比較明顯。

答案: 錯。int x被修飾成final,意味着x不能在addOne method中被修改。

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的reference

(比如: o = new Other()