1. 程式人生 > >Java面試系列總結 :JavaSE基礎(1) 面向物件/語法/異常

Java面試系列總結 :JavaSE基礎(1) 面向物件/語法/異常

1. 面向物件都有哪些特性以及你對這些特性的理解

  • 繼承:繼承是從已有類得到繼承資訊建立新類的過程。提供繼承資訊的類被稱為父類(超類、基類);得到繼承資訊的類被稱為子類(派生類)。繼承讓變化中的軟體系統有了一定的延續性,同時繼承也是封裝程式中可變因素的
    重要手段。
  • 封裝:通常認為封裝是把資料和操作資料的方法繫結起來,對資料的訪問只能通過已定義的介面。面向物件 的本質就是將現實世界描繪成一系列完全自治、封閉的物件。我們在類中編寫的方法就是對實現細節的一種封裝;我 們編寫一個類就是對資料和資料操作的封裝。可以說,封裝就是隱藏一切可隱藏的東西,只向外界提供最簡單的程式設計 介面。
  • 多型性:多型性是指允許不同子型別的物件對同一訊息作出不同的響應。簡單的說就是用同樣的物件引用呼叫同樣的方法但是做了不同的事情。多型性分為編譯時的多型性和執行時的多型性。如果將物件的方法視為物件向外 界提供的服務,那麼執行時的多型性可以解釋為:當 A 系統訪問 B 系統提供的服務時,B 系統有多種提供服務的方式, 但一切對 A 系統來說都是透明的。方法過載(overload)實現的是編譯時的多型性(也稱為前繫結),而方法重寫 (override)實現的是執行時的多型性(也稱為後繫結)。執行時的多型是面向物件最精髓的東西,要實現多型需要做兩件事:1. 方法重寫(子類繼承父類並重寫父類中已有的或抽象的方法);2. 物件造型(用父型別引用引用子型別對 象,這樣同樣的引用呼叫同樣的方法就會根據子類物件的不同而表現出不同的行為)。
  • 抽象:抽象是將一類物件的共同特徵總結出來構造類的過程,包括資料抽象和行為抽象兩方面。抽象只關注對 象有哪些屬性和行為,並不關注這些行為的細節是什麼。
注意 預設情況下面向物件有 3 大特性,封裝、繼承、多型,如果面試官問讓說出 4 大特性,那麼我們就把抽象加上去。

2. 訪問許可權修飾符 public、private、protected, 以及不寫(預設)時的區別

修飾符 當前類 同 包 子 類 其他包
public
protected ×
default × ×
private × × ×

3. 如何理解 clone 物件,為什麼要用 clone?

在實際程式設計過程中,我們常常要遇到這種情況:有一個物件 A,在某一時刻 A 中已經包含了一些有效值,此時可能會需要一個和 A 完全相同新物件 B,並且此後對 B 任何改動都不會影響到 A 中的值,也就是說,A 與 B 是兩個獨立的物件,但 B 的初始值是由 A 物件確定的。在 Java 語言中,用簡單的賦值語句是不能滿足這種需求的。要滿足這種需求雖然有很多途徑,但實現 clone()方法是其中最簡單,也是最高效的手段。

4. new 一個物件的過程和 clone 一個物件的過程區別

  • new 操作符的本意是分配記憶體。程式執行到 new 操作符時,首先去看 new 操作符後面的型別,因為知道了型別,才能知道要分配多大的記憶體空間。分配完記憶體之後,再呼叫建構函式,填充物件的各個域,這一步叫做物件的初始化,構造方法返回後,一個物件建立完畢,可以把他的引用(地址)釋出到外部,在外部就可以使用這個引用操縱這個物件。
  • clone 在第一步是和 new 相似的,都是分配記憶體,呼叫 clone 方法時,分配的記憶體和原物件(即呼叫 clone 方法的物件)相同,然後再使用原物件中對應的各個域,填充新物件的域,填充完成之後,clone 方法返回,一個新的相同的物件被建立,同樣可以把這個新物件的引用釋出到外部。

5. clone 物件的使用,複製物件和複製引用的區別

Person p = new Person(23, "zhang"); 
Person p1 = p;  
System.out.println(p); 
System.out.println(p1); 

當Person p1 = p;執行之後, 是建立了一個新的物件嗎? 首先看列印結果:

[email protected] 
[email protected] 

可以看出,列印的地址值是相同的,既然地址都是相同的,那麼肯定是同一個物件。p和p1只是引用而已,他們都指向了一個相同的物件Person(23, “zhang”) 。 可以把這種現象叫做引用的複製。上面程式碼執行完成之後, 記憶體中的情景如下圖所示: 在這裡插入圖片描述而下面的程式碼是真真正正的克隆了一個物件。

Person p = new Person(23, "zhang");   
Person p1 = (Person) p.clone();    
System.out.println(p);   
System.out.println(p1); 

從列印結果可以看出,兩個物件的地址是不同的,也就是說建立了新的物件, 而不是把原物件的地址賦給了一個新的引用變數:

[email protected] 
[email protected] 

以上程式碼執行完成後, 記憶體中的情景如下圖所示: 在這裡插入圖片描述

6. 深拷貝和淺拷貝

上面的示例程式碼中,Person 中有兩個成員變數,分別是 name 和 age, name 是 String 型別, age 是 int 型別。程式碼非常簡單,如下所示:

public class Person implements Cloneable{   
    privatint age ;   
    private String name;   
    public Person(int age, String name) {   
        this.age = age;   
        this.name = name;   
    }    
    public Person() {
    }    
    public int getAge() {   
        return age;   
    }    
    public String getName() {   
        return name;   
    }    
    @Override  
    protected Object clone() throws CloneNotSupportedException {   
        return (Person)super.clone();   
    }   
} 

由於age是基本資料型別, 那麼對它的拷貝沒有什麼疑議,直接將一個4位元組的整數值拷貝過來就行。但是name 是 String 型別的, 它只是一個引用, 指向一個真正的 String 物件,那麼對它的拷貝有兩種方式: 直接將原物件中 的 name 的引用值拷貝給新物件的 name 欄位, 或者是根據原 Person 物件中的 name 指向的字串物件建立一個 新的相同的字串物件,將這個新字串物件的引用賦給新拷貝的 Person 物件的 name 欄位。這兩種拷貝方式分別 叫做淺拷貝和深拷貝。深拷貝和淺拷貝的原理如下圖所示: 在這裡插入圖片描述

下面通過程式碼進行驗證。如果兩個 Person 物件的 name 的地址值相同, 說明兩個物件的 name 都指向同一個 String 物件,也就是淺拷貝, 而如果兩個物件的 name 的地址值不同, 那麼就說明指向不同的 String 物件, 也就 是在拷貝Person物件的時候, 同時拷貝了name引用的String物件, 也就是深拷貝。驗證程式碼如下:

Person p = new Person(23, "zhang"); 
Person p1 = (Person) p.clone(); 
String result = p.getName() == p1.getName()? "clone 是淺拷貝的" : "clone 是深拷貝的"; 
System.out.println(result); 

列印結果為:

clone 是淺拷貝的

所以,clone方法執行的是淺拷貝, 在編寫程式時要注意這個細節。

如何進行深拷貝:

由上面的內容可以得出如下結論:如果想要深拷貝一個物件,這個物件必須要實現Cloneable介面,實現clone方法,並且在 clone 方法內部,把該物件引用的其他物件也要 clone 一份,這就要求這個被引用的物件必須也要實現Cloneable介面並且實現clone方法。那麼,按照上面的結論,實現以下程式碼 Body類組合了Head類,要想深拷貝Body類,必須在Body類的clone方法中將Head類也要拷貝一份。程式碼如下:

static class Body implements Cloneable{   
    public Head head;   
    public Body() {
    }   
    public Body(Head head) {
        this.head = head;
    }   
    @Override  
    protected Object clone() throws CloneNotSupportedException {   
        Body newBody =  (Body) super.clone();   
        newBody.head = (Head) head.clone();   
        return newBody;   
    }   
}   
static class Head implements Cloneable{   
    public  Face face;    
    public Head() {
    }   
    @Override  
    protected Object clone() throws CloneNotSupportedException {   
        return super.clone();   
    }  
}      
public static void main(String[] args) throws CloneNotSupportedException {    
    Body body = new Body(new Head(new Face()));   
    Body body1 = (Body) body.clone();   
    System.out.println("body == body1 : " + (body == body1) );   
    System.out.println("body.head == body1.head : " +  (body.head == body1.head));   
} 

列印結果為:

body == body1 : false 
body.head == body1.head : false 

7. Java有沒有goto語句?

goto是Java 中的保留字,在目前版本的Java中沒有使用。根據 James Gosling(Java 之父)編寫的《The Java Programming Language》一書的附錄中給出了一個 Java 關鍵字列表,其中有 goto 和 const,但是這兩個是目前無法使用的關鍵字,因此有些地方將其稱之為保留字,其實保留字這個詞應該有更廣泛的意義,因為熟悉 C 語言的程式設計師都知道,在系統類庫中使用過的有特殊意義的單詞或單詞的組合都被視為保留字。

8. & 和 && 的區別

  • &運算子有兩種用法:(1)按位與;(2)邏輯與。
  • &&運算子是短路與運算。邏輯與跟短路與的差別是非常巨大的,雖然二者都要求運算子左右兩端的布林值都是 true 整個表示式的值才是 true。
  • &&之所以稱為短路運算是因為,如果&&左邊的表示式的值是 false,右邊的表示式會被直接短路掉,不會進行運算。很多時候我們可能都需要用&&而不是&,例如在驗證使用者登入時判定使用者名稱不是 null 而且不是空字串,應當寫為username != null &&!username.equals(""),二者的順序不能交換,更不能用&運算子,因為第一個條件如果不成立,根本不能進行字串的 equals 比較,否則會產生 NullPointerException 異常。注意:邏輯或運算子(|)和短路或運算子(||)的差別也是如此

9. 在Java中,如何跳出當前的多重巢狀迴圈

在最外層迴圈前加一個標記如 A,然後用 break A;可以跳出多重迴圈。(Java 中支援帶標籤的break和continue 語句,作用有點類似於 C 和 C++中的 goto 語句,但是就像要避免使用 goto 一樣,應該避免使用帶標籤的 break 和 continue,因為它不會讓你的程式變得更優雅,很多時候甚至有相反的作用)。

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

不對,如果兩個物件 x 和 y 滿足 x.equals(y) == true,它們的雜湊碼(hashCode)應當相同。

Java 對於eqauls 方法和 hashCode 方法是這樣規定的:(1)如果兩個物件相同(equals 方法返回 true),那麼它們的hashCode 值一定要相同;(2)如果兩個物件的 hashCode 相同,它們並不一定相同。當然,你未必要按照要求去做,但是如果你違背了上述原則就會發現在使用容器時,相同的物件可以出現在 Set 集合中,同時增加新元素的效率會大大下降(對於使用雜湊儲存的系統,如果雜湊碼頻繁的衝突將會造成存取效能急劇下降)。

關於equals 和 hashCode方法,很多Java程式設計師都知道,但很多人也就是僅僅知道而已,在 Joshua Bloch 的大作《Effective Java》(很多軟體公司,《Effective Java》、《Java 程式設計思想》以及《重構:改善既有程式碼質量》是 Java 程式設計師必看書籍,如果你還沒看過,那就趕緊去買一本吧)中是這樣介紹 equals 方法的。

首先 equals 方法必須滿足自反性(x.equals(x)必須返回 true)、對稱性(x.equals(y)返回 true 時,y.equals(x)也必須返回 true)、傳遞性(x.equals(y)和 y.equals(z)都返回 true 時,x.equals(z)也必須返回 true)和一致性(當x 和 y 引用的物件資訊沒有被修改時,多次呼叫 x.equals(y)應該得到同樣的返回值),而且對於任何非 null值的引用 x,x.equals(null)必須返回false。實現高質量的equals方法的訣竅包括:

  1. 使用==操作符檢查"引數是否為這個物件的引用";
  2. 使用 instanceof 操作符檢查"引數是否為正確的型別";
  3. 對於類中的關鍵屬性,檢查引數傳入物件的屬性是否與之相匹配;
  4. 編寫完 equals 方法後,問自己它是否滿足對稱性、傳遞性、一致性;
  5. 重寫 equals 時總是要重寫 hashCode;
  6. 不要將 equals 方法引數中的 Object 物件替換為其他的型別,在重寫時不要忘掉@Override 註解。

11. 是否可以繼承String

String類是final類,不可以被繼承。
繼承 String 本身就是一個錯誤的行為,對 String 型別最好的重用方式是關聯關係(Has-A)和依賴關係(UseA)而不是繼承關係(Is-A)

12. 當一個物件被當作引數傳遞到一個方法後,此方法可改變這個物件的屬性,並 可返回變化後的結果,那麼這裡到底是值傳遞還是引用傳遞?

是值傳遞。Java 語言的方法呼叫只支援引數的值傳遞。當一個物件例項作為一個引數被傳遞到方法中時,引數的值就是對該物件的引用。物件的屬性可以在被呼叫過程中被改變,但對物件引用的改變是不會影響到呼叫者的。C++和 C#中可以通過傳引用或傳輸出引數來改變傳入的引數的值。說明:Java 中沒有傳引用實在是非常的不方便,這一點在 Java 8 中仍然沒有得到改進,正是如此在 Java 編寫的程式碼中才會出現大量的 Wrapper 類(將需要通過方法呼叫修改的引用置於一個 Wrapper 類中,再將 Wrapper 物件傳入方法),這樣的做法只會讓程式碼變得臃腫,尤其是讓從 C 和 C++轉型為 Java 程式設計師的開發者無法容忍。

13. 過載(overload)和重寫(override)的區別?過載的方法能否根據返回型別進行區分?

方法的過載和重寫都是實現多型的方式,區別在於前者實現的是編譯時的多型性,而後者實現的是執行時的多型性。過載發生在一個類中,同名的方法如果有不同的引數列表(引數型別不同、引數個數不同或者二者都不同)則視為過載;重寫發生在子類與父類之間,重寫要求子類被重寫方法與父類被重寫方法有相同的返回型別,比父類被重寫方法更好訪問,不能比父類被重寫方法宣告更多的異常(里氏代換原則)。過載對返回型別沒有特殊的要求。

方法過載的規則:

  1. 方法名一致,引數列表中引數的順序,型別,個數不同。
  2. 過載與方法的返回值無關,存在於父類和子類,同類中。
  3. 可以丟擲不同的異常,可以有不同修飾符。

方法重寫的規則:

  1. 引數列表必須完全與被重寫方法的一致,返回型別必須完全與被重寫方法的返回型別一致。
  2. 構造方法不能被重寫,宣告為final的方法不能被重寫,宣告為static的方法不能被重寫,但是能夠被再次宣告。
  3. 訪問許可權不能比父類中被重寫的方法的訪問許可權更低。
  4. 重寫的方法能夠丟擲任何非強制異常(UncheckedException,也叫非執行時異常),無論被重寫的方法是否丟擲異常。但是,重寫的方法不能丟擲新的強制性異常,或者比被重寫方法宣告的更廣泛的強制性異常,反之則可以。

14. 為什麼函式不能根據返回型別來區分過載?

該道題來自華為面試題。
因為呼叫時不能指定型別資訊,編譯器不知道你要呼叫哪個函式。
例如:

float max(int a, int b); 
int max(int a, int b); 

當呼叫max(1, 2);時無法確定呼叫的是哪個,單從這一點上來說,僅返回值型別不同的過載是不應該允許的。 再比如對下面這兩個方法來說,雖然它們有同樣的名字和自變數,但其實是很容易區分的:

void f() {}  
int f() {} 

若編譯器可根據上下文(語境)明確判斷出含義,比如在 int x=f()中,那麼這樣做完全沒有問題。然而,我們也可能呼叫一個方法,同時忽略返回值;我們通常把這稱為“為它的副作用去呼叫一個方法”,因為我 們關心的不是返回值,而是方法呼叫的其他效果。所以假如我們像下面這樣呼叫方法: f(); Java 怎樣判斷f()的具體呼叫方式呢?而且別人如何識別並理解程式碼呢?由於存在這一類的問題,所以不能。
函式的返回值只是作為函式執行之後的一個“狀態”,他是保持方法的呼叫者與被呼叫者進行通訊的關鍵。並不能作為某個方法的“標識”。

15. char 型變數中能不能儲存一箇中文漢字,為什麼?

char 型別可以儲存一箇中文漢字,因為Java中使用的編碼是 Unicode(不選擇任何特定的編碼,直接 使用字元在字符集中的編號,這是統一的唯一方法),一個 char 型別佔 2 個位元組(16 位元),所以放一箇中文是沒問題的。

補充 使用 Unicode 意味著字元在 JVM 內部和外部有不同的表現形式,在 JVM 內部都是 Unicode,當這個字元被從 JVM 內部轉移到外部時(例如存入檔案系統中),需要進行編碼轉換。所以 Java 中有位元組流和字元流,以及在字元流和位元組流之間進行轉換的轉換流,如 InputStreamReader 和 OutputStreamReader,這兩個類是位元組流和字元流之間的介面卡類,承擔了編碼轉換的任務;對於 C 程式設計師來說,要完成這樣的編碼轉換恐怕要依賴於 union(聯合體/共用體)共享記憶體的特徵來實現了

16. 抽象類(abstract class)和介面(interface)有什麼異同?

不同:

抽象類

  1. 抽象類中可以定義構造器
  2. 可以有抽象方法和具體方法
  3. 介面中的成員全都是public的
  4. 抽象類中可以定義成員變數
  5. 有抽象方法的類必須被宣告為抽象類,而抽象類未必要有抽象方法
  6. 抽象類中可以包含靜態方法
  7. 一個類只能繼承一個抽象類

介面

  1. 介面中不能定義構造器
  2. 方法全部都是抽象方法
  3. 抽象類中的成員可以是 private、預設、protected、public
  4. 介面中定義的成員變數實際上都是常量
  5. 介面中不能有靜態方法
  6. 一個類可以實現多個介面

相同:

  1. 不能夠例項化
  2. 可以將抽象類和介面型別作為引用型別
  3. 一個類如果繼承了某個抽象類或者實現了某個介面都需要對其中的抽象方法全部進行實現,否則該類仍然需要被宣告為抽象類

17. 抽象的(abstract)方法是否可同時是靜態的(static), 是否可同時是本地方法 (native),是否可同時被synchronized

都不能。抽象方法需要子類重寫,而靜態的方法是無法被重寫的,因此二者是矛盾的。本地方法是由 原生代碼(如 C 程式碼)實現的方法,而抽象方法是沒有實現的,也是矛盾的。synchronized 和方法的實現細節有關,抽象方法不涉及實現細節,因此也是相互矛盾的。

18. 闡述靜態變數和例項變數的區別?

  • 靜態變數: 是被 static 修飾符修飾的變數,也稱為類變數,它屬於類,不屬於類的任何一個物件,一個類不管建立多少個物件,靜態變數在記憶體中有且僅有一個拷貝;
  • 例項變數: 必須依存於某一例項,需要先建立物件然後通過物件才能訪問到它。靜態變數可以實現讓多個物件共享記憶體。

19. ==和equals的區別?

equals和== 最大的區別是一個是方法一個是運算子。

  • ==:如果比較的物件是基本資料型別,則比較的是數值是否相等;如果比較的是引用資料型別,則比較的是物件的地址值是否相等。
  • equals():用來比較方法兩個物件的內容是否相等。
注意 equals 方法不能用於基本資料型別的變數,如果沒有對 equals 方法進行重寫,則比較的是引用型別的變數所指向的物件的地址。

20. break和continue的區別?

break和continue都是用來控制迴圈的語句。
break用於完全結束一個迴圈,跳出迴圈體執行迴圈後面的語句。
continue用於跳過本次迴圈,執行下次迴圈。

21. 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; 
    ... 
    s = "Initial Value"; 
    ... 
}

而非

s = new String("Initial Value");

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

上面的結論還基於這樣一個事實:對於字串常量,如果內容相同,Java認為它們代表同一個String物件。而用關鍵字new呼叫構造器,總是會建立一個新的物件,無論內容是否相同。 至於為什麼要把String類設計成不可變類,是它的用途決定的。其實不只String,很多Java標準類庫中的類都是不可變的。在開發一個系統的時候,我們有時候也需要設計不可變類,來傳遞一組相關的值,這也是面向物件思想的體現。不可變類有一些優點,比如因為它的物件是隻讀的,所以多執行緒併發訪問也不會有任何問題。當然也有一些缺點,比如每個不同的狀態都要一個物件來代表,可能會造成效能上的問題。所以Java標準類庫還提供了一個可變版本,即 StringBuffer。

22. Java中實現多型的機制是什麼?

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

23. Java中異常分為哪些種類

按照異常需要處理的時機分為編譯時異常(也叫強制性異常)也叫CheckedException和執行時異常(也叫非強制性異常)也叫 RuntimeException。只有java語言提供了Checked異常,Java認為Checked異常都是可以被處理的異常,所以 Java 程式必須顯式處理 Checked 異常。如果程式沒有處理 Checked 異常,該程式在編譯時就會發生錯誤無法編譯。這體現了Java的設計哲學:沒有完善錯誤處理的程式碼根本沒有機會被執行。對Checked異常處理方法有兩種:

  1. 當前方法知道如何處理該異常,則用try…catch塊來處理該異常。
  2. 當前方法不知道如何處理,則在定義該方法是宣告丟擲該異常。

執行時異常只有當代碼在執行時才發行的異常,編譯時不需要try catch。Runtime如除數是0和陣列下標越界等,其產生頻繁,處理麻煩,若顯示申明或者捕獲將會對程式的可讀性和執行效率影響很大。所以由系統自動檢測並將它們交給預設的異常處理程式。當然如果你有處理要求也可以顯示捕獲它們。

24. 呼叫下面的方法,得到的返回值是什麼?

public int getNum(){
    try { 
        int a = 1/0; 
        return 1; 
    } catch (Exception e) { 
        return 2; 
    }finally{ 
        return 3; 
    }

程式碼在走到第3行的時候遇到了一個MathException,這時第四行的程式碼就不會執行了,程式碼直接跳轉到catch語句中,走到第6行的時候,異常機制有這麼一個原則如果在catch中遇到了return或者異常等能使該函式終止的話那麼有 finally 就必須先執行完 finally 程式碼塊裡面的程式碼然後再返回值。因此程式碼又跳到第 8 行,可惜第 8 行是一個return語句,那麼這個時候方法就結束了,因此第6行的返回結果就無法被真正返回。如果finally僅僅是處理了一個釋放資源的操作,那麼該道題最終返回的結果就是2。因此上面返回值是3。

25. error和exception的區別?

Error類和Exception類的父類都是Throwable類,他們的區別如下。

Error類一般是指與虛擬機器相關的問題,如系統崩潰,虛擬機器錯誤,記憶體空間不足,方法呼叫棧溢位等。對於這類錯誤的導致的應用程式中斷,僅靠程式本身無法恢復和和預防,遇到這樣的錯誤,建議讓程式終止。

Exception類表示程式可以處理的異常,可以捕獲且可能恢復。遇到這類異常,應該儘可能處理異常,使程式恢復執行,而不應該隨意終止異常。

Exception 類又分為執行時異常(Runtime Exception)和受檢查的異常(Checked Exception ),執行時異常;ArithmaticException,IllegalArgumentException,編譯能通過,但是一執行就終止了,程式不會處理執行時異常,出現這類異常,程式會終止。而受檢查的異常,要麼用 try。。。catch 捕獲,要麼用 throws 字句宣告丟擲,交給它的父類處理,否則編譯不會通過。

26. java異常處理機制

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

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

27. 請寫出你最常見的5個RuntimeException

下面列舉幾個常見的RuntimeException。
1)java.lang.NullPointerException 空指標異常;出現原因:呼叫了未經初始化的物件或者是不存在的物件。
2)java.lang.ClassNotFoundException 指定的類找不到;出現原因:類的名稱和路徑載入錯誤;通常都是程式試圖通過字串來載入某個類時可能引發異常。
3)java.lang.NumberFormatException 字串轉換為數字異常;出現原因:字元型資料中包含非數字型字元。
4)java.lang.IndexOutOfBoundsException 陣列角標越界異常,常見於運算元組物件時發生。
5)java.lang.IllegalArgumentException 方法傳遞引數錯誤。
6)java.lang.ClassCastException 資料型別轉換異常。
7)java.lang.NoClassDefFoundException 未找到類定義錯誤。
8)SQLException SQL異常,常見於操作資料庫時的SQL語句錯誤。
9)java.lang.InstantiationException例項化異常。
10)java.lang.NoSuchMethodException 方法不存在異常

28. throw和throws的區別

throw:
1)throw語句用在方法體內,表示丟擲異常,由方法體內的語句處理。
2)throw是具體向外丟擲異常的動作,所以它丟擲的是一個異常例項,執行throw一定是丟擲了某種異常。
throws:
1)throws語句是用在方法聲明後面,表示如果丟擲異常,由該方法的呼叫者來進行異常的處理。
2)throws主要是宣告這個方法會丟擲某種型別的異常,讓它的使用者要知道需要捕獲的異常的型別。
3)throws表示出現異常的一種可能性,並不一定會發生這種異常。

29. final、finally、finalize的區別?

1)final:用於宣告屬性,方法和類,分別表示屬性不可變,方法不可覆蓋,被其修飾的類不可繼承。
2)finally:異常處理語句結構的一部分,表示總是執行。
3)finalize:Object類的一個方法,在垃圾回收器執行的時候會呼叫被回收物件的此方法,可以覆蓋此方法提供垃圾收集時的其他資源回收,例如關閉檔案等。該方法更像是一個物件生命週期的臨終方法,當該方法被系統呼叫則代表該物件即將“死亡”,但是需要注意的是,我們主動行為上去呼叫該方法並不會導致該物件“死亡”,這是一個被動的方法(其實就是回撥方法),不需要我們呼叫。

30. 簡述 Java 中的值傳遞和引用傳遞?

按值傳遞是指的是在方法呼叫時,傳遞的引數是按值的拷貝傳遞。
按值傳遞重要特點:傳遞的是值的拷貝,也就是說傳遞後就互不相關了

示例如下:

public class TempTest {   
    private void test1(int a){   
        a = 5;   
        System.out.println("test1 方法中的 a="+a);   
    }   
    public static void main(String[] args) {   
        TempTest t = new TempTest();   
        int a = 3;   
        t.test1(a);//傳遞後,test1 方法對變數值的改變不影響這裡的 a   
        System.out.println(”main 方法中的 a=+a);   
    }   
}

執行結果是:

test1 方法中的 a=5   
main 方法中的 a=3 

按引用傳遞是指的是在方法呼叫時,傳遞的引數是按引用進行傳遞,其實傳遞的引用的地址,也就是變數所對應的記憶體空間的地址。傳遞的是值的引用,也就是說傳遞前和傳遞後都指向同一個引用(也就是同一個記憶體空間)。

示例如下:

public class TempTest {  
    private void test1(A a){  
    }  
    public static void main(String[] args) {  
        TempTest t = new TempTest();  
        A a = new A();  
        t.test1(a); //這裡傳遞的引數a就是按引用傳遞  
    }  
}  
class A{  
    public int age = 0;  
}  

執行結果如下:

test1 方法中的 age=20   
main 方法中的 age=20 

31. switch 是否作用在 byte 上, 是否能作用在 long 上, 是否能作用在 String 上?

switch可作用於char byte short int;
switch可作用於char byte short int對應的包裝類;
switch不可作用於long double float boolean,包括他們的包裝類;

**32. 編寫一個工具類 StringUtil, 提供方法 int compare(char[] v1 ,char[] v2)方法,比較字串v1,v2 ,如果按照字元順序 v1>v2 則 return 1 ,v1=v2 則 return 0, v1<v2 則 return -1。 **

public class StringUtil{ 
    int compare(char[] v1,char[] v2) {    
        String str1 = new String(v1);    
        String str2 = new String(v2);    
        int result = str1.compareTo(str2);    
        return result == 0 ? 0 : (result > 0 ? 1 : -1); 
    } 
} 

33. 抽象類和介面有什麼不同點?

介面和抽象類的概念不一樣。介面是對動作的抽象,抽象類是對根源的抽象。

抽象類表示的是,這個物件是什麼。介面表示的是,這個物件能做什麼。比如,男人,女人,這兩個類(如果是類的話……),他們的抽象類是人。說明,他們都是人。

人可以吃東西,狗也可以吃東西,你可以把“吃東西”定義成一個介面,然後讓這些類去實現它.

不同點:

引數 抽象類 介面
預設的方法實現 它可以有預設的方法實現 介面完全是抽象的。它根本不存在方法的實現
實現 子類使用extends關鍵字來繼承抽象類。如果子類不是抽象類的話,它需要提供抽象類中所有宣告的方法的實現。 子類使用關鍵字implements來實現介面。它需要提供介面中所有宣告的方法的實現
構造器 抽象類可以有構造器 介面不能有構造器
與正常Java類的區別 除了你不能例項化抽象類之外,它和普通Java類沒有任何區別 介面是完全不同的型別
訪問修飾符 抽象方法可以有publicprotecteddefault這些修飾符 介面方法預設修飾符是public。你不可以使用其它修飾符。
main方法 抽象方法可以有main方法並且我們可以執行它 介面沒有main方法,因此我們不能執行它。
多繼承 抽象方法可以繼承一個類和實現多個介面 介面只可以繼承一個或多個其它介面
速度 它比介面速度要快 介面是稍微有點慢的,因為它需要時間去尋找在類中實現的方法。
新增新方法 如果你往抽象類中新增新的方法,你可以給它提供預設的實現。因此你不需要改變你現在的程式碼。 如果你往介面中新增方法,那麼你必須改變實現該介面的類。

34. 什麼時候使用抽象類和介面

如果你擁有一些方法並且想讓它們中的一些有預設實現,那麼使用抽象類吧。

如果你想實現多重繼承,那麼你必須使用介面。由於Java不支援多繼承,子類不能夠繼承多個類,但可以實現多個介面。因此你就可以使用介面來解決它。

如果基本功能在不斷改變,那麼就需要使用抽象類。如果不斷改變基本功能並且使用介面,那麼就需要改變所有實現了該介面的類。

歡迎關注作者的公眾號《Java程式設計生活》,每日記載Java程式猿工作中遇到的問題
在這裡插入圖片描述