java面試基礎知識總結(二)
五、Object 通用方法
equals()方法
- 等價關係
Ⅰ 自反性
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;
- 等價與相等
對於基本型別,== 判斷兩個值是否相等,基本型別沒有 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
- 實現
檢查是否為同一個物件的引用,如果是直接返回 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()方法
- 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();
}
}
- 淺拷貝
拷貝物件和原始物件的引用型別引用同一個物件。
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
- 深拷貝
拷貝物件和原始物件的引用型別引用不同物件。
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
- 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關鍵字
- 資料
宣告資料為常量,可以是編譯時常量,也可以是在執行時被初始化後不能被改變的常量。
- 對於基本型別,final 使數值不變;
- 對於引用型別,final 使引用不變,也就不能引用其它物件,但是被引用的物件本身是可以修改的。
final int x = 1;
// x = 2; // cannot assign value to final variable 'x'
final A y = new A();
y.a = 1;
- 方法
宣告方法不能被子類重寫。
private 方法隱式地被指定為 final,如果在子類中定義的方法和基類中的一個 private 方法簽名相同,此時子類的方法不是重寫基類方法,而是在子類中定義了一個新的方法。
- 類
宣告類不允許被繼承。
static關鍵字
- 靜態變數
靜態變數:又稱為類變數,也就是說這個變數屬於類的,類所有的例項都共享靜態變數,可以直接通過類名來訪問它。靜態變數在記憶體中只存在一份。
例項變數:每建立一個例項就會產生一個例項變數,它與該例項同生共死。
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;
}
}
- 靜態方法
靜態方法在類載入的時候就存在了,它不依賴於任何例項。所以靜態方法必須有實現,也就是說它不能是抽象方法。
只能訪問所屬類的靜態欄位和靜態方法,方法中不能有 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
}
}
- 靜態語句塊
靜態語句塊在類初始化時執行一次。
public class A {
static {
System.out.println("123");
}
public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
}
}
123
- 靜態內部類
非靜態內部類依賴於外部類的例項,而靜態內部類不需要。
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();
}
}
靜態內部類不能訪問外部類的非靜態的變數和方法。
- 靜態導包
在使用靜態變數和方法時不用再指明 ClassName,從而簡化程式碼,但可讀性大大降低。
import static com.xxx.ClassName.*
- 初始化順序
靜態變數和靜態語句塊優先於例項變數和普通語句塊,靜態變數和靜態語句塊的初始化順序取決於它們在程式碼中的順序。
存在繼承的情況下,初始化順序為:
父類(靜態變數、靜態語句塊)
子類(靜態變數、靜態語句塊)
父類(例項變數、普通語句塊)
父類(建構函式)
子類(例項變數、普通語句塊)
子類(建構函式)
七、反射
每個類都有一個 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 與 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