1. 程式人生 > >java面試基礎知識總結(二)

java面試基礎知識總結(二)

五、Object 通用方法

equals()方法

  1. 等價關係

Ⅰ 自反性

x.equals(x); // true

Ⅱ 對稱性

x.equals(y) == y.equals(x); // true

Ⅲ 傳遞性

if (x.equals(y) && y.equals(z))
    x.equals(z); // true;

Ⅳ 一致性
多次呼叫 equals() 方法結果不變

x.equals(y) == x.equals(y); // true

Ⅴ 與 null 的比較

對任何不是 null 的物件 x 呼叫 x.equals(null) 結果都為 false

x.equals(null); // false;
  1. 等價與相等
    對於基本型別,== 判斷兩個值是否相等,基本型別沒有 equals() 方法。
    對於引用型別,== 判斷兩個變數是否引用同一個物件,而 equals() 判斷引用的物件是否等價。
Integer x = new Integer(1);
Integer y = new Integer(1);
System.out.println(x.equals(y)); // true
System.out.println(x == y);      // false
  1. 實現

檢查是否為同一個物件的引用,如果是直接返回 true;
檢查是否是同一個型別,如果不是,直接返回 false;
將 Object 物件進行轉型;
判斷每個關鍵域是否相等。

public class EqualExample {
    private int x;
    private int y;
    private int z;
    public EqualExample(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        EqualExample that = (EqualExample) o;

        if (x != that.x) return false;
        if (y != that.y) return false;
        return z == that.z;
    }
}

hashCode()方法

hashCode() 返回雜湊值,而 equals() 是用來判斷兩個物件是否等價。等價的兩個物件雜湊值一定相同,但是雜湊值相同的兩個物件不一定等價。

在覆蓋 equals() 方法時應當總是覆蓋 hashCode() 方法,保證等價的兩個物件雜湊值也相等。

下面的程式碼中,新建了兩個等價的物件,並將它們新增到 HashSet 中。我們希望將這兩個物件當成一樣的,只在集合中新增一個物件,但是因為 EqualExample 沒有實現 hasCode() 方法,因此這兩個物件的雜湊值是不同的,最終導致集合添加了兩個等價的物件。

EqualExample e1 = new EqualExample(1, 1, 1);
EqualExample e2 = new EqualExample(1, 1, 1);
System.out.println(e1.equals(e2)); // true
HashSet<EqualExample> set = new HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size());   // 2

理想的雜湊函式應當具有均勻性,即不相等的物件應當均勻分佈到所有可能的雜湊值上。這就要求了雜湊函式要把所有域的值都考慮進來。可以將每個域都當成 R 進位制的某一位,然後組成一個 R 進位制的整數。R 一般取 31,因為它是一個奇素數,如果是偶數的話,當出現乘法溢位,資訊就會丟失,因為與 2 相乘相當於向左移一位。

一個數與 31 相乘可以轉換成移位和減法:31*x == (x<<5)-x,編譯器會自動進行這個優化。

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + x;
    result = 31 * result + y;
    result = 31 * result + z;
    return result;
}

toString()方法

預設返回 [email protected] 這種形式,其中 @ 後面的數值為雜湊碼的無符號十六進位制表示。

public class ToStringExample {
    private int number;

    public ToStringExample(int number) {
        this.number = number;
    }
}
ToStringExample example = new ToStringExample(123);
System.out.println(example.toString());
[email protected]

clone()方法

  1. cloneable

clone() 是 Object 的 protected 方法,它不是 public,一個類不顯式去重寫 clone(),其它類就不能直接去呼叫該類例項的 clone() 方法。

public class CloneExample {
    private int a;
    private int b;
}
CloneExample e1 = new CloneExample();
// CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object'

重寫 clone() 得到以下實現:

public class CloneExample {
    private int a;
    private int b;

    @Override
    public CloneExample clone() throws CloneNotSupportedException {
        return (CloneExample)super.clone();
    }
}
CloneExample e1 = new CloneExample();
try {
    CloneExample e2 = e1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
java.lang.CloneNotSupportedException: CloneExample

以上丟擲了 CloneNotSupportedException,這是因為 CloneExample 沒有實現 Cloneable 介面。

應該注意的是,clone() 方法並不是 Cloneable 介面的方法,而是 Object 的一個 protected 方法。Cloneable 介面只是規定,如果一個類沒有實現 Cloneable 介面又呼叫了 clone() 方法,就會丟擲 CloneNotSupportedException。

public class CloneExample implements Cloneable {
    private int a;
    private int b;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
  1. 淺拷貝

拷貝物件和原始物件的引用型別引用同一個物件。

public class ShallowCloneExample implements Cloneable {
    private int[] arr;

    public ShallowCloneExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }

    public void set(int index, int value) {
        arr[index] = value;
    }

    public int get(int index) {
        return arr[index];
    }

    @Override
    protected ShallowCloneExample clone() throws CloneNotSupportedException {
        return (ShallowCloneExample) super.clone();
    }
}
ShallowCloneExample e1 = new ShallowCloneExample();
ShallowCloneExample e2 = null;
try {
    e2 = e1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 222
  1. 深拷貝

拷貝物件和原始物件的引用型別引用不同物件。

public class DeepCloneExample implements Cloneable {
    private int[] arr;

    public DeepCloneExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }

    public void set(int index, int value) {
        arr[index] = value;
    }

    public int get(int index) {
        return arr[index];
    }

    @Override
    protected DeepCloneExample clone() throws CloneNotSupportedException {
        DeepCloneExample result = (DeepCloneExample) super.clone();
        result.arr = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            result.arr[i] = arr[i];
        }
        return result;
    }
}
DeepCloneExample e1 = new DeepCloneExample();
DeepCloneExample e2 = null;
try {
    e2 = e1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 2
  1. clone() 的替代方案

使用 clone() 方法來拷貝一個物件即複雜又有風險,它會丟擲異常,並且還需要型別轉換。Effective Java 書上講到,最好不要去使用 clone(),可以使用拷貝建構函式或者拷貝工廠來拷貝一個物件。

public class CloneConstructorExample {
    private int[] arr;

    public CloneConstructorExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }

    public CloneConstructorExample(CloneConstructorExample original) {
        arr = new int[original.arr.length];
        for (int i = 0; i < original.arr.length; i++) {
            arr[i] = original.arr[i];
        }
    }

    public void set(int index, int value) {
        arr[index] = value;
    }

    public int get(int index) {
        return arr[index];
    }
}
CloneConstructorExample e1 = new CloneConstructorExample();
CloneConstructorExample e2 = new CloneConstructorExample(e1);
e1.set(2, 222);
System.out.println(e2.get(2)); // 2

六、關鍵字

final關鍵字

  1. 資料

宣告資料為常量,可以是編譯時常量,也可以是在執行時被初始化後不能被改變的常量。

  • 對於基本型別,final 使數值不變;
  • 對於引用型別,final 使引用不變,也就不能引用其它物件,但是被引用的物件本身是可以修改的。
final int x = 1;
// x = 2;  // cannot assign value to final variable 'x'
final A y = new A();
y.a = 1;
  1. 方法

宣告方法不能被子類重寫。

private 方法隱式地被指定為 final,如果在子類中定義的方法和基類中的一個 private 方法簽名相同,此時子類的方法不是重寫基類方法,而是在子類中定義了一個新的方法。

宣告類不允許被繼承。

static關鍵字

  1. 靜態變數

靜態變數:又稱為類變數,也就是說這個變數屬於類的,類所有的例項都共享靜態變數,可以直接通過類名來訪問它。靜態變數在記憶體中只存在一份。
例項變數:每建立一個例項就會產生一個例項變數,它與該例項同生共死。

public class A {
    private int x;         // 例項變數
    private static int y;  // 靜態變數

    public static void main(String[] args) {
        // int x = A.x;  // Non-static field 'x' cannot be referenced from a static context
        A a = new A();
        int x = a.x;
        int y = A.y;
    }
}
  1. 靜態方法

靜態方法在類載入的時候就存在了,它不依賴於任何例項。所以靜態方法必須有實現,也就是說它不能是抽象方法。
只能訪問所屬類的靜態欄位和靜態方法,方法中不能有 this 和 super 關鍵字。

public class A {
    private static int x;
    private int y;

    public static void func1(){
        int a = x;
        // int b = y;  // Non-static field 'y' cannot be referenced from a static context
        // int b = this.y;     // 'A.this' cannot be referenced from a static context
    }
}
  1. 靜態語句塊

靜態語句塊在類初始化時執行一次。

public class A {
    static {
        System.out.println("123");
    }

    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new A();
    }
}
123
  1. 靜態內部類

非靜態內部類依賴於外部類的例項,而靜態內部類不需要。

public class OuterClass {
    class InnerClass {
    }

    static class StaticInnerClass {
    }

    public static void main(String[] args) {
        // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
        OuterClass outerClass = new OuterClass();
        InnerClass innerClass = outerClass.new InnerClass();
        StaticInnerClass staticInnerClass = new StaticInnerClass();
    }
}

靜態內部類不能訪問外部類的非靜態的變數和方法。

  1. 靜態導包

在使用靜態變數和方法時不用再指明 ClassName,從而簡化程式碼,但可讀性大大降低。

import static com.xxx.ClassName.*
  1. 初始化順序

靜態變數和靜態語句塊優先於例項變數和普通語句塊,靜態變數和靜態語句塊的初始化順序取決於它們在程式碼中的順序。

存在繼承的情況下,初始化順序為:

父類(靜態變數、靜態語句塊)
子類(靜態變數、靜態語句塊)
父類(例項變數、普通語句塊)
父類(建構函式)
子類(例項變數、普通語句塊)
子類(建構函式)

七、反射

每個類都有一個 Class 物件,包含了與類有關的資訊。當編譯一個新類時,會產生一個同名的 .class 檔案,該檔案內容儲存著 Class 物件。

類載入相當於 Class 物件的載入,類在第一次使用時才動態載入到 JVM 中。也可以使用 Class.forName(“com.mysql.jdbc.Driver”) 這種方式來控制類的載入,該方法會返回一個 Class 物件。

反射可以提供執行時的類資訊,並且這個類可以在執行時才載入進來,甚至在編譯時期該類的 .class 不存在也可以載入進來。

Class 和 java.lang.reflect 一起對反射提供了支援,java.lang.reflect 類庫主要包含了以下三個類:

Field :可以使用 get() 和 set() 方法讀取和修改 Field 物件關聯的欄位;
Method :可以使用 invoke() 方法呼叫與 Method 物件關聯的方法;
Constructor :可以用 Constructor 建立新的物件。

八、異常

Throwable 可以用來表示任何可以作為異常丟擲的類,分為兩種: Error 和 Exception。其中 Error 用來表示 JVM 無法處理的錯誤,Exception 分為兩種:

受檢異常 :需要用 try…catch… 語句捕獲並進行處理,並且可以從異常中恢復;
非受檢異常 :是程式執行時錯誤,例如除 0 會引發 Arithmetic Exception,此時程式崩潰並且無法恢復。

九、泛型

java泛型詳解

十、註解

Java 註解是附加在程式碼中的一些元資訊,用於一些工具在編譯、執行時進行解析和使用,起到說明、配置的功能。註解不會也不能影響程式碼的實際邏輯,僅僅起到輔助性的作用。
註解實現原理與自定義註解例子

十一、Java 與 C++ 的區別

1、Java 是純粹的面嚮物件語言,所有的物件都繼承自 java.lang.Object,C++ 為了相容 C 即支援面向物件也支援面向過程。
2、Java 通過虛擬機器從而實現跨平臺特性,但是 C++ 依賴於特定的平臺。
3、Java 沒有指標,它的引用可以理解為安全指標,而 C++ 具有和 C 一樣的指標。
4、Java 支援自動垃圾回收,而 C++ 需要手動回收。
5、Java 不支援多重繼承,只能通過實現多個介面來達到相同目的,而 C++ 支援多重繼承。
6、Java 不支援操作符過載,雖然可以對兩個 String 物件執行加法運算,但7、是這是語言內建支援的操作,不屬於操作符過載,而 C++ 可以。
8、Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。
9、Java 不支援條件編譯,C++ 通過 #ifdef #ifndef 等預處理命令從而實現條件編譯。

大概就是這些了!!!

參考

https://github.com/CyC2018/CS-Notes/blob/master/notes/Java 基礎.md