Java 類與對象篇
1.上期面試題解析:
上文鏈接:https://blog.csdn.net/weixin_73492487/article/details/146607026
- 創建對象時的內存分配過程?
① 加載類 ② 堆內存分配空間 ③ 默認初始化 ④ 顯式初始化 ⑤ 構造器執行
- this和super能否同時出現?
不能,二者都必須位于構造器首行
- 以下代碼輸出什么?
class A {int i = 10;void print() { System.out.println(i); } }class B extends A {int i = 20;public static void main(String[] args) {new B().print(); } }
輸出10(方法看運行時類型,字段看編譯時類型)
方法看運行時類型:在 Java 中,方法的調用是動態綁定的,這意味著方法的調用是基于實際對象的類型來決定的,而不是對象的聲明類型。
字段看編譯時類型:而字段的訪問是靜態綁定的,即編譯時綁定。即使對象的實際類型發生了變化,編譯時類型決定了我們訪問的是哪個字段。也就是說,字段的訪問與對象的聲明類型相關,而與實際類型無關,不受運行時類型影響。
2.多態(Polymorphism)
2.1 實現條件
- 繼承體系:存在父子類關系
- 方法重寫:子類重寫父類方法
- 向上轉型:父類引用指向子類對象(向上轉型:低轉高,自動轉換)
Animal animal = new Dog(); // 向上轉型
2.2 動態綁定機制
class Animal {void sound() { System.out.println("Animal sound"); }
}class Dog extends Animal {@Overridevoid sound() { System.out.println("Woof!"); }
}public class Test {public static void main(String[] args) {Animal a = new Dog();a.sound(); // 輸出"Woof!"(運行時類型決定方法調用)}
}
核心特性:
- 編譯看左邊(檢查父類是否存在該方法,聲明/引用決定可以調用的方法,若不存在該方法,則編譯錯誤)
- 運行看右邊(實際執行子類重寫方法,引用指向的對象具體決定調用的方法)
- 成員變量無多態(編譯運行都看左邊)
3. instanceof 與類型轉換
3.1 類型檢查
Object obj = new String("Hello");
if(obj instanceof String) { // 返回trueSystem.out.println("是字符串類型");
}
分析:instanceof 用于判斷一個對象是否是某個類的實例,或者是否實現了某個接口,并且返回一個布爾值
2.2 類型轉換規則
轉換類型 | 語法 | 風險 |
---|---|---|
向上轉型 | 自動轉換 | 無風險 |
向下轉型 | 強制轉換 + 類型檢查 | 可能拋出ClassCastException |
分析:在進行類型轉換之前,可以使用 instanceof 來判斷是否可以安全地進行轉換,從而避免 ClassCastException。
安全轉換模式:
if(animal instanceof Dog) {Dog dog = (Dog)animal; // 安全轉換
}
父類與子類間的類型轉換:
//父類Animal
class Animal {public void eat() {System.out.println("Animal is eating");}
}//子類Dog繼承了父類的eat方法
class Dog extends Animal {public void bark() {System.out.println("Dog is barking");}
}public class Test {public static void main(String[] args) {//一個對象的實際類型是確定的,但可以指向的引用類型就不確定了//父類引用指向了子類Animal animal = new Dog();animal.eat(); // 可以調用 Animal 類中的方法// animal.bark(); // 編譯錯誤,Animal 類型沒有 bark 方法if (animal instanceof Dog) {Dog dog = (Dog) animal; // 強制類型轉換dog.bark(); // 可以調用 Dog 類中的 bark 方法}}
}
4. static 關鍵字詳解
4.1 靜態成員 vs 實例成員
維度 | 靜態成員 | 實例成員 |
---|---|---|
所屬層級 | 類級別 | 對象級別 |
內存分配 | 類加載時初始化 | 對象創建時分配 |
訪問方式 | 類名.成員 或 對象.成員 | 必須通過對象訪問 |
生命周期 | 與類共存亡 | 隨對象回收而銷毀 |
線程安全 | 需額外同步控制 | 實例隔離更安全 |
4.2 靜態代碼塊與初始化順序
class MyClass {static int staticVar; //靜態變量int instanceVar;// 靜態代碼塊(類加載時執行一次,只執行一次)static {staticVar = 100;System.out.println("靜態代碼塊");}// 實例代碼塊(每次new對象時執行,用于賦初始值){instanceVar = 50;System.out.println("實例代碼塊");}public MyClass(){System.out.println("構造方法");}
}public class Test{public static void main(String[] args){MyClass mc=new MyClass();System.out.println("==========");MyClass ms=new MyClass();}
}
初始化順序:
靜態變量/代碼塊 → 實例變量/代碼塊 → 構造器
輸出:
靜態代碼塊
實例代碼塊
構造方法
==========
實例代碼塊
構造方法
4.3 static修飾特性
靜態變量:
- 靜態變量被所有實例共享
- 靜態變量在內存中只有一份副本
- 靜態變量的生命周期與類的生命周期相同,即在類加載時分配內存,直到程序結束時才釋放(從屬于類)
靜態方法:
- 靜態方法只能訪問靜態變量和調用其他靜態方法
- 可以通過類名直接調用,且不能直接訪問實例變量和實例方法
- 不能使用this/super關鍵字
- 不能被重寫(但可隱藏)
class Parent {static void staticMethod() {System.out.println("Parent static method");}
}class Child extends Parent {//與父類方法相獨立static void staticMethod() {System.out.println("Child static method");}
}public class Test {public static void main(String[] args) {Parent p = new Parent();Parent c = new Child();Child d = new Child();p.staticMethod(); // 輸出 "Parent static method"c.staticMethod(); // 輸出 "Parent static method" (靜態方法調用是根據引用類型來決定的)d.staticMethod(); // 輸出 "Child static method" (靜態方法調用是根據引用類型來決定的)//靜態方法是屬于類的,而不是對象的。所以,當你調用靜態方法時,它是通過類名來解析的,而不是通過對象引用來解析//當子類定義了一個與父類中靜態方法相同的方法時,在子類中的靜態方法不會被真正地“重寫”,父類和子類的靜態方法仍然是相互獨立的。}
}
靜態代碼塊:
- 靜態代碼塊在類加載時自動執行,且只執行一次。
- 通常用于靜態變量的初始化。
靜態導入:
- 使用 import static 語句導入類的靜態成員,可以直接訪問類的靜態方法或靜態變量。
import static java.lang.Math.*;public class Test {public static void main(String[] args) {double result = sqrt(25); // 直接使用 Math 類中的 sqrt 方法,無需寫類名(Math.sqrt())System.out.println(result); // 輸出 5.0}
}
靜態內部類(由 static 修飾的內部類):
- 靜態內部類不依賴外部類的實例,可以直接創建
- 可以訪問外部類的靜態成員,但不能訪問外部類的實例成員和實例方法
class Outer {static int staticVar = 10;int var; //靜態內部類不可訪問//由static修飾的內部類(靜態內部類)static class Inner {void display() {System.out.println("Static Inner class, staticVar: " + staticVar);}}
}public class Test {public static void main(String[] args) {Outer.Inner inner = new Outer.Inner(); // 直接通過外部類創建靜態內部類的實例inner.display(); }
}
輸出:
Static Inner class, staticVar: 10
5. 抽象類(Abstract Class)
5.1 定義與特征
abstract class Animal {// 抽象方法abstract void sound();// 非抽象方法/具體方法(子類會繼承)void sleep() {System.out.println("Sleeping...");}
}class Dog extends Animal {// 實現抽象方法@Overridevoid sound() {System.out.println("Bark");}
}public class Test {public static void main(String[] args) {Animal animal = new Dog();animal.sound(); // 輸出 "Bark"animal.sleep(); // 輸出 "Sleeping..."}
}
定義:
抽象類是一種不能直接實例化的類。它可以包含抽象方法和非抽象方法。抽象方法是沒有實現的方法,只有方法的聲明,沒有方法體,子類需要實現這些抽象方法。抽象類通常用于定義一種通用的模板或接口,子類繼承它并提供具體的實現。
核心特性:
- 用
abstract
關鍵字修飾 - 不能實例化(只能被繼承)
- 可以包含抽象方法和具體方法(抽象方法必須在抽象類中)
- 抽象方法:沒有方法體,必須在子類中實現。
- 非抽象方法:有方法體,子類可以繼承并使用,也可以覆蓋(重寫)
- 子類必須實現(重寫)所有抽象方法(除非子類也是抽象類,那么就需要子類的子類去實現這些抽象方法)
- 可以包含構造方法:抽象類可以有構造方法,子類通過super()調用父類的構造方法。
- 可以包含成員變量(任意訪問修飾符)
abstract class Shape {//成員變量int x, y;// 抽象類的構造方法,初始化共同的屬性public Shape(int x, int y) {this.x = x;this.y = y;}//抽象方法,必須實現abstract void draw();
}class Circle extends Shape {int radius;public Circle(int x, int y, int radius) {super(x, y); // 調用父類構造方法this.radius = radius;}@Overridevoid draw() {System.out.println("Drawing a circle at (" + x + ", " + y + ") with radius " + radius);}
}public class Test {public static void main(String[] args) {Circle circle = new Circle(5, 10, 7);circle.draw(); // 輸出: Drawing a circle at (5, 10) with radius 7}
}
5.2 使用場景
- 代碼復用:多個子類共享部分實現
- 模板方法模式:定義算法框架,具體步驟由子類實現
- 強制規范:要求子類必須實現特定功能
6. 接口(Interface)
6.1 定義與演進
// Java 8+ 接口(是一種約束,只有抽象方法)
public interface Flyable {// 常量(默認 public static final)double MAX_SPEED = 1000.0;// 抽象方法(默認 public abstract)void fly();// 默認方法(Java 8+)默認方法有方法體,可以提供默認實現。//默認方法是 可選 的,接口的實現類可以選擇覆蓋它,也可以使用接口提供的默認實現。default void land() {System.out.println("Landing...");}// 靜態方法(Java 8+),可以通過接口直接調用,而不需要依賴于接口的實現類。//靜態方法 不能被實現類重寫,因為它屬于接口本身。static void showMaxSpeed() {System.out.println("Max speed: " + MAX_SPEED);}// 私有方法(Java 9+)只能在接口內部使用,不能被接口的實現類直接訪問。private void checkSpeed() {// 內部復用代碼}
}
定義:
接口是一個抽象類型,它是類與類之間的一種協議,用來指定類必須實現的方法。接口只包含 抽象方法(沒有方法體)和 常量,從 Java 8 開始,接口也可以包含 默認方法(有實現)和 靜態方法。
核心特性:
- 不能直接實例化: 你不能創建接口的對象,必須通過實現接口的類來實例化對象
- 沒有構造方法: 接口沒有構造方法,因此不能創建接口實例
implements
實現,支持多繼承、多實現(一個類可實現多個接口)- 所有方法默認
public abstract
(Java 8前) - 變量默認
public static final
- Java 8+ 支持默認方法和靜態方法
- Java 9+ 支持私有方法
示例:
interface Logger {private void log(String message) {System.out.println("Logging message: " + message);}default void info(String message) {log(message); // 接口調用自身私有方法}default void warn(String message) {log(message); // 接口調用自身私有方法}
}class MyLogger implements Logger {// 不需要實現 log() 方法,只需要調用 info() 或 warn() 方法,來提示登陸信息
}public class Test {public static void main(String[] args) {MyLogger logger = new MyLogger();logger.info("Info message");logger.warn("Warning message");// 輸出:// Logging message: Info message// Logging message: Warning message}
}
6.2 使用場景
- 多繼承行為:定義多個能力(Flyable, Runnable等)
- 策略模式:通過不同實現類改變算法
- API定義:解耦接口與實現(面向接口編程)
- 函數式接口(@FunctionalInterface):Lambda表達式基礎
7. 抽象類 vs 接口對比
維度 | 抽象類 | 接口(Java 8+) |
---|---|---|
實例化 | 不能 | 不能 |
方法實現 | 可包含具體方法 | 默認方法/靜態方法/私有方法 |
成員變量 | 任意類型和修飾符 | 只能是public static final常量 |
繼承/實現 | 單繼承(extends) | 多實現(implements) |
構造器 | 有 | 無 |
設計目的 | 代碼復用 + 規范約束 | 行為規范定義 |
訪問控制 | 支持各種訪問修飾符 | 默認public(Java 9支持private) |
? 高頻面試題
-
以下代碼輸出什么?
class A {static { System.out.print("1"); }{ System.out.print("2"); }public A() { System.out.print("3"); } }class B extends A {static { System.out.print("4"); }{ System.out.print("5"); }public B() { System.out.print("6"); } }public class Test {public static void main(String[] args) {new B(); } }
-
靜態方法能否調用非靜態方法?為什么?
-
如何實現線程安全的單例模式?
-
以下代碼是否合法?
class Test {static int a = b; static int b = 10; }
-
何時選擇抽象類?何時選擇接口?
-
以下代碼是否合法?為什么?
abstract class A {abstract void method1();final void method2() {} }interface B {default void method3() {}static void method4() {} }