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() {} }