1. 程式人生 > 其它 >Java面試基礎題

Java面試基礎題

1.在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 < arr.length && !found; i++) {
        for (int j = 0; j < arr[i].length; j++) {
            System.out.println("i = " + i + ", j = " + j);
            if (arr[i][j] == 5) {
                found = true;
                break;
            }
        }
    }

敲黑板:建議使用第二種,第一種已經被業界淘汰了。

2.switch語句能否作用在byte上,能否作用在long上,能否作用在String上?

在switch(expr1)中,expr1只能是一個整數表示式或者列舉常量(更大字型),整數表示式可以是int基本型別或Integer包裝型別,由於,byte,short,char都可以隱式轉換為int,所以,這些型別以及這些型別的包裝型別也是可以的。

  • switch 不支援 long 型別;
  • 從 java1.7開始 switch 開始支援 String,這是 Java 的語法糖。

3.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編譯器會對它進行特殊處理,因此可以正確編譯。

4.char型變數中能不能存貯一箇中文漢字?為什麼?

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

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

2 << 4

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

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

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

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

a=new StringBuffer("");

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

a.append(" broken!");

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

public void method(final StringBuffer param){}

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

param.append("a");

7."=="和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方法,由你自己寫程式碼來決定在什麼情況即可認為兩個物件的內容是相同的。

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

不可以。

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

9.Math.round(11.5)等於多少? Math.round(-11.5)等於多少?

Math類中提供了三個與取整有關的方法:ceil、floor、round,這些方法的作用與它們的英文名稱的含義相對應,

round方法,它表示“四捨五入”,內部原理為Math.floor(x+0.5),即將原來的數字加上0.5後再向下取整,所以,Math.round(11.5)的結果為12,Math.round(-11.5)的結果為-11。

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

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

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

作用域 當前類 同一package 子孫類 其他package
public
protected [√] ×
default [×] ×
private [×] × ×

備註:多注意一點【子孫類】和【同一package】這兩個就很容易記住這個表了。

11.方法過載時返回值型別是否可以不同?

如果方法的引數列表不同,可以;

如果方法的引數列表相同,不可以,因為呼叫的時候傳入方法的引數相同,編譯器無法確定用的是哪個方法。

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

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

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

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

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

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

14.抽象類特徵

  1. 含有abstract修飾符的class即為抽象類,abstract 類不能建立的例項物件。
  2. 含有abstract方法的類必須定義為抽象類,但抽象類中的方法可以【不全是】抽象的。
  3. abstract class類中定義抽象方法必須在具體(Concrete)子類中實現,所以,不能有抽象構造方法(因為不能建立抽象類的例項)或抽象靜態方法(因為沒有具體的實現)。
  4. 如果的子類沒有實現抽象父類中的所有抽象方法,那麼子類也必須定義為abstract型別。

注意:

  1. 抽象類可以沒有沒有抽象方法,但有抽象方法時一定要宣告為抽象類;
  2. 構造方法、類方法可以存在於抽象類中,但構造方法和類方法不能是抽象的
  3. 生成子類物件前,會先呼叫抽象父類的構造方法(如果沒有過載構造方法,預設是無參那個)

15.介面interface

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

16.抽象類與介面區別

兩者的語法區別

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

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

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

4) 抽象類中的抽象方法的訪問型別可以是public,protected和(預設型別,雖然eclipse下不報錯,但應該也不行),但介面中的抽象方法只能是public型別的,並且預設即為public abstract型別。

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

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

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

應用上的區別

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

public abstract class BaseServlet extends HttpServlet {

    public final void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        // 記錄訪問日誌, 進行許可權判斷
        if (具有許可權) {
            try {
                doService(request, response);
            } catch (Exception e) {
                // todo:記錄異常資訊
            }
        }
    }

    protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        //注意訪問許可權定義成protected,顯得既專業,又嚴謹,因為它是專門給子類用的
    }
}
public class MyServlet1 extends BaseServlet {
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws IOExcetion, ServletException {
        // 本Servlet只處理的具體業務邏輯程式碼
    }
}

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

17.內部類可以引用它的外部類的成員嗎?有沒有什麼限制?

完全可以。除非是靜態內部類(因為靜態不能訪問非靜態的物件),靜態內部類只能訪問靜態物件。

18.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。

在test方法中,直接呼叫getClass().getName()方法,返回的是Test類名。由於getClass()在Object類中定義成了final,子類不能覆蓋該方法,所以,在test方法中呼叫getClass().getName()方法,其實就是在呼叫從父類繼承的getClass()方法,等效於呼叫super.getClass().getName()方法,所以,super.getClass().getName()方法返回的也應該是Test。

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

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

19.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。

20.String s = new String("xyz");建立了幾個String Object? 二者之間有什麼區別?

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

21.下面這條語句一共建立了多少個物件? 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。

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

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

public class Test {

    public static void main(String[] args) {
        System.out.println(Test.test());
    }

    static int test() {
        int x = 1;
        try {
            return x;
        } finally {
            ++x;
        }
    }
}

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

執行結果是1,為什麼呢?主函式呼叫子函式並得到結果的過程,好比主函式準備一個空罐子,當子函式要返回結果時,先把結果放在罐子裡,然後再將程式邏輯返回到主函式。

所謂返回,就是子函式說,我不運行了,你主函式繼續執行吧,這沒什麼結果可言,結果是在說這話之前放進罐子裡的。

------持續更新中------