1. 程式人生 > >Java 面試寶典

Java 面試寶典

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(x==33 & ++y>0) y會增長,If(x==33 && ++y>0)不會增長

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

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

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

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

 1 ok:
 2 
 3 for(inti=0;i<10;i++)    {
 4 
 5            for(intj=0;j<10;j++)              {
 6 
 7                     System.out.println(“i=”+ i + “,j=” + j);
8 9 if(j == 5) break ok; 10 11 } 12 13 }

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

 1  int arr[][] = { { 1, 2, 3 }, { 4, 5, 6, 7 }, { 9 } };
 2 
 3  boolean found = false;
 4  for (int i = 0; i < arr.length && !found; i++) {
 5     for (int j = 0; j < arr[i].length; j++) {
 6         System.out.println("i=" + i + ",j=" + j);
 7         if (arr[i][j] == 5) {
 8             found = true;
 9             break;
10         }
11     }
12  }

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型別來做演示這種問題和現象的實驗,大家可以用下面一個使用整數做實驗的例子程式體驗一下:

1  inta = Integer.MAX_VALUE;
2 
3  intb = Integer.MAX_VALUE;
4 
5  intsum = a + b;
6 
7  System.out.println(“a=”+a+”,b=”+b+”,sum=”+sum);

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

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

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

(3)提供加減乘除的功能

 1 public class BigInteger {
 2     int sign;
 3     byte[] val;
 4 
 5     public Biginteger(String val) {
 6                     sign= ;
 7                      val= ;
 8             }
 9 
10     public BigInteger add(BigInteger other) {
11     }
12 
13     public BigInteger subtract(BigInteger other) {
14     }
15 
16     public BigInteger multiply(BigInteger other) {
17     }
18 
19     public BigInteger divide(BigInteger other) {
20     }
21 
22 }

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

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

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

1 final StringBuffer a=new StringBuffer("immutable");

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

1 a=new StringBuffer("");

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

1 a.append(" broken!");  

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

1 publicvoid method(final  StringBuffer  param){
2 
3 }

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

1  param.append("a");

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

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

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

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

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

1  String a=new String("foo");
2 
3  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方法的實現程式碼如下:

1  boolean equals(Object o){
2 
3  return this==o;
4 
5  }

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

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

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

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

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

 1 public class VariantTest {
 2 
 3     public static int staticVar = 0;
 4     public int instanceVar = 0;
 5 
 6     public VariantTest() {
 7         staticVar++;
 8         instanceVar++;
 9         System.out.println("staticVar=" + staticVar + ",instanceVar=" + instanceVar);
10     }
11 }

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

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 1.if(username.equals(“zxx”){}
2 
3 2.int x = 1;
4 
5   return x==1?true:false;

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

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

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

作用域 當前類 同一package 子孫類 其他package
public
protected ×
default × ×
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-封裝:

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

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

2-抽象:

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

1 classPerson{
2 
3         String name;
4 
5         int age;
6 
7 }

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

3-繼承:

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

4-多型:

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

1 UserDao userDao = daofactory.getDao(); 
2 
3 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型別。

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

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

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

 1 import java.io.IOException;
 2 import javax.servlet.ServletException;
 3 import javax.servlet.http.HttpServlet;
 4 import javax.servlet.http.HttpServletRequest;
 5 import javax.servlet.http.HttpServletResponse;
 6 
 7 public abstract class BaseServlet extends HttpServlet {
 8     public final void service(HttpServletRequest request, HttpServletResponse response)
 9             throws IOException, ServletException {
10         // 記錄訪問日誌
11         // 進行許可權判斷
12     }
13 
14     protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
15             throws IOException, ServletException;
16         // 注意訪問許可權定義成protected,顯得既專業,又嚴謹,因為它是專門給子類用的
17 }
18 
19 class MyServlet1 extends BaseServlet {
20     protected void doService(HttpServletRequest request, HttpServletResponse response)
21             throws IOException, ServletException {
22         // 本Servlet只處理的具體業務邏輯程式碼
23     }
24 }

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

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

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

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

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

private native void open(String name)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語言的全域性變數,而內部類通常是用於建立內部物件用的,所以,把“全域性變數”放在內部類中就是毫無意義的事情,既然是毫無意義的事情,就應該被禁止),內部類可以直接訪問外部類中的成員變數,內部類可以定義在外部類的方法外面,也可以定義在外部類的方法體中,如下所示:

 1 public class Outer {
 2 
 3     int out_x = 0;
 4 
 5     public void method() {
 6         Inner1 inner1 = new Inner1();
 7         class Inner2 // 在方法體內部定義的內部類
 8         {
 9             public void method() {
10                 out_x = 3;
11             }
12         }
13         Inner2 inner2 = new Inner2();
14     }
15 
16     public class Inner1 // 在方法體外面定義的內部類
17     {
18     }
19 }

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

1 Outer outer = new Outer();
2 
3 Outer.Inner1 inner1 = outer.newInnner1();

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

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

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

1 public class Outer 2
2 {
3     public void start() {
4         new Thread(new Runable() {
5             public void run() {
6             };
7         }).start();
8     }
9 }

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

1 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、內部類可以引用它的包含類的成員嗎?有沒有什麼限制?

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

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

1 class Outer {
2     static int x;
3 
4     static class Inner {
5         void test() {
6             System.out.println(x);
7         }
8     }
9 }

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

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

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

29、super.getClass()方法呼叫

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

 1 import java.util.Date;
 2 public class Testextends Date
 3 {
 4     public static void main(String[] args) {
 5         new Test().test();
 6     }
 7     public void test() {
 8         System.out.println(super.getClass().getName());
 9     }
10 }

很奇怪,結果是Test

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

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

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

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

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

1 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引用變數進行初始化,把它設定為初始值,應當這樣做:

1 public class Demo {
2 private String s;
3 ...
4 public Demo {
5 s = "Initial Value";
6 }
7 ...
8 }

而非

1 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(new StringBuffer(“abc”)的結果為false。

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

1 StringBuffer sbf =new StringBuffer(); 
2 for(inti=0;i<100;i++)
3 {
4    sbf.append(i);
5 }

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

1 String str = newString(); 
2 for(inti=0;i<100;i++)
3 {
4    str = str + i;
5 }

在講兩者區別時,應把迴圈的次數搞成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,”,”);

1 String [] result = newString[tokener .countTokens()];
2 Int i=0;
3 while(tokener.hasNext(){result[i++]=toker.nextToken();}

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

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

37、下面這條語句一共建立了多少個物件:Strings=”a”+”b”+”c”+”d”;

答:對於如下程式碼:

1 String s1= "a";
2 
3 String s2= s1 + "b";
4 
5 String s3= "a" + "b";
6 
7 System.out.println(s2== "ab");
8 
9 System.out.println(s3== "ab");

第一條語句列印的結果為false,第二條語句列印的結果為true,這說明javac編譯可以對字串常量直接相加的表示式進行優化,不必要等到執行期去進行加法運算處理,而是在編譯時去掉其中的加號,直接將其編譯成一個這些常量相連的結果。

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

1 String s = "a" +"b" + "c" + "d";
2 
3 System.out.println(s =="abcd");

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

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

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

 1 public class Test {
 2 
 3     public static void main(String[] args) {
 4         System.out.println(new Test().test());
 5     }
 6 
 7     static int test() {
 8         int x = 1;
 9         try {
10             return x;
11         } finally {
12             ++x;
13         }
14     }
15 }

———執行結果 ———

1

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

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

 1 public class smallT
 2 
 3 {
 4     public static void main(String args[]) {
 5         smallT t = new smallT();
 6         int b = t.get();
 7         System.out.println(b);
 8     }
 9 
10     public int get() {
11         try {
12             return 1;
13         } finally {
14             return 2;
15         }
16     }
17 }

返回的結果是2。

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

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

 1 public class Test {
 2 
 3     public static void main(String[] args) {
 4         System.out.println(newTest().test());
 5     }
 6 
 7     int test() {
 8         try {
 9             return func1();
10         } finally {
11             return func2();
12         }
13     }
14 
15     int func1() {
16         System.out.println("func1");
17         return 1;
18     }
19 
20     int func2() {
21         System.out.println("func2");
22         return 2;
23     }
24 }

———–執行結果—————–

1 func1
2 
3 func2
4 
5 2

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

40、final, finally, finalize的區別。

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

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

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

finalize是Obje