Skip to content

面向对象高频面试题

面试中,面向对象是绕不开的话题。本章汇总了最常见的面试题,并给出深入的回答方向。

三大特性

Q: 面向对象四大特性是什么?

大多数人会说「封装、继承、多态」,但更完整的答案是四个:封装、继承、多态、抽象

特性核心思想解决的问题
封装把数据和方法包装在一起数据安全问题
继承子类复用父类的代码代码复用问题
多态同一个行为有不同的表现接口统一问题
抽象忽略细节,抓住本质复杂度控制问题

Q: 封装、继承、多态分别解决了什么问题?

  • 封装:让数据不被随意修改,隐藏内部实现细节,暴露受控的访问接口
  • 继承:让子类复用父类的代码,建立类层次结构
  • 多态:让同一段代码在不同对象上产生不同行为,提高可扩展性

重写 vs 重载

Q: 重写和重载的区别?

这是经典送分题,但很多人只背结论,不理解本质。

对比项重写 (Override)重载 (Overload)
发生位置父子类之间同一个类中
方法名必须相同必须相同
参数列表必须相同必须不同
返回类型必须相同或其子类可以不同
访问修饰符不能缩小无关
关系同一个方法的不同实现完全不同的方法
java
class Parent {
    void display(int x) { }  // 父类版本
}
class Child extends Parent {
    void display(int x) { }  // 重写:子类覆盖父类
    void display(String s) { }  // 重载:新的方法
}

Q: 重载遵循什么绑定机制?

重载在编译时确定,遵循静态绑定。编译器根据参数的静态类型选择调用哪个方法。

java
class Parent {
    void process(int a) { System.out.println("int"); }
    void process(double a) { System.out.println("double"); }
}
class Child extends Parent { }

Parent p = new Child();
p.process(10);  // 编译时确定:Parent.process(int)

Q: 重写遵循什么绑定机制?

重写在运行时确定,遵循动态绑定。JVM 根据对象的实际类型选择调用哪个方法。

java
class Parent {
    void display() { System.out.println("Parent"); }
}
class Child extends Parent {
    @Override
    void display() { System.out.println("Child"); }
}

Parent p = new Child();
p.display();  // 运行时确定:Child.display()

抽象类 vs 接口

Q: 抽象类和接口的区别?

这是被问得最多的问题之一。先看表格:

对比项抽象类接口
关键字abstract classinterface
继承单继承多实现
构造方法可以有不能有
方法抽象 + 普通都可以JDK 7 前只能是抽象方法
字段无限制只能是常量 (static final)
访问修饰符任意默认 public
静态方法可以有JDK 8+ 可以有
默认方法不能有JDK 8+ 可以有

Q: 什么时候用抽象类,什么时候用接口?

用一个字概括:抽象类是「是什么」,接口是「能做什么」

java
// 抽象类:建立 is-a 关系
abstract class Animal {
    String name;
    abstract void eat();  // 每种动物吃的方式不同
}
class Dog extends Animal { }  // Dog is an Animal

// 接口:建立 can-do 关系
interface Flyable {
    void fly();
}
class Bird implements Flyable { }  // Bird can fly

Q: 为什么 Java 不允许多继承?

多继承会引发「菱形继承」问题:

java
class A { void display() { } }
class B extends A { }
class C extends A { }
class D extends B, C { }  // ❌ D 有两个 display(),歧义!

Java 选择单继承 + 多实现来解决这个问题。接口没有状态,只有方法签名,不存在歧义。

this vs super

Q: this 和 super 的区别?

对比项thissuper
指向当前对象父类对象
调用属性当前类的属性(可能遮蔽)父类的属性
调用方法当前类的方法(可能遮蔽)父类的方法
调用构造当前类的其他构造方法父类的构造方法
静态方法中❌ 不能使用❌ 不能使用
java
class Parent {
    String name = "Parent";
}
class Child extends Parent {
    String name = "Child";

    void display() {
        System.out.println(this.name);      // Child
        System.out.println(super.name);     // Parent
    }
}

Q: 子类构造方法中,为什么 super() 必须在第一行?

因为子类需要先完成父类的初始化,才能进行自己的初始化。如果 super() 不在第一行,子类的代码可能在父类初始化之前运行,引发不可预期的错误。

java
class Parent {
    Parent() {
        System.out.println("Parent 初始化完成");
    }
}
class Child extends Parent {
    Child() {
        // super() 隐式调用,必须在第一行
        System.out.println("Child 初始化完成");
    }
}

多态

Q: 多态的实现原理?

运行时多态通过动态绑定实现:

  1. 编译时:编译器检查父类中是否有这个方法(只检查签名)
  2. 运行时:JVM 根据对象的实际类型,找到真正的方法地址并调用
java
class Animal {
    void eat() { System.out.println("Animal eat"); }
}
class Dog extends Animal {
    @Override
    void eat() { System.out.println("Dog eat"); }
}
class Cat extends Animal {
    @Override
    void eat() { System.out.println("Cat eat"); }
}

Animal a = new Dog();
a.eat();  // 输出: Dog eat

Q: 向上转型和向下转型的区别?

  • 向上转型:子类转父类,自动转换,安全
  • 向下转型:父类转子类,需要强制转换,有风险
java
Animal animal = new Dog();  // 向上转型:自动
Dog dog = (Dog) animal;    // 向下转型:需要检查

// 危险!
Animal a = new Animal();
Dog d = (Dog) a;  // ClassCastException!

所以向下转型前,先用 instanceof 检查。

Q: 为什么需要多态?

三个字:解耦合

java
// ❌ 没有多态:每种动物写一段代码
void feedAnimal(Dog dog) { dog.eat(); }
void feedAnimal(Cat cat) { cat.eat(); }

// ✅ 有多态:统一处理
void feedAnimal(Animal animal) {
    animal.eat();  // 不管是 Dog 还是 Cat,都能正确处理
}

final

Q: final 修饰的变量一定是常量吗?

分两种情况:

  • 基本类型:值不可变
  • 引用类型:引用地址不可变,但对象内容可以变
java
final int NUM = 10;
NUM = 20;  // ❌ 编译错误

final StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");  // ✅ 可以修改对象内容
sb = new StringBuilder();  // ❌ 引用不能改变

Q: final 修饰方法有什么意义?

防止子类重写,确保这个方法的行为永远不变。String 类的 trim() 方法就是 final 的,防止被篡改。

Q: 为什么 String 是 final 的?

三个原因:

  1. 安全:防止被继承后修改,导致字符串比较出现漏洞
  2. 性能:JVM 可以对 final 方法进行内联优化
  3. 字符串常量池:String 对象被广泛共享,如果可以被继承修改,会造成安全隐患

内部类

Q: 内部类有什么优势?

  1. 逻辑封装:一个类只被另一个类使用,逻辑上紧密相关
  2. 访问私有成员:内部类可以访问外部类的所有成员
  3. 命名空间:内部类属于外部类,不会与其他类的名字冲突
java
class Outer {
    private int data;
    class Inner {
        void accessOuter() {
            data = 10;  // ✅ 可以访问外部类的私有成员
        }
    }
}

Q: 为什么局部内部类和匿名内部类访问局部变量时必须是 final 或 effectively final?

因为局部变量在栈上,内部类对象在堆上。如果允许内部类修改局部变量,而局部变量已经出栈消失,就会出问题。所以 Java 要求局部变量是 final 或 effectively final(只赋值一次)。

JDK 8+ 自动为「只赋值一次」的局部变量加上 final 语义。

枚举

Q: 枚举为什么是线程安全的?

枚举值在类加载时就创建好了,JVM 保证 static final 字段在类加载阶段完成初始化,且只创建一次。这是 JVM 的内部机制保证的,不需要程序员额外处理。

总结

面向对象面试的核心在于理解本质,而不是背答案。面试官追问的往往是「为什么」。

记住几个关键问题的思考路径:

  • 多态的实现原理 → 动态绑定
  • 为什么不能多继承 → 菱形继承问题
  • 什么时候用抽象类 → is-a 关系
  • 什么时候用接口 → can-do 关系
  • 为什么 String 是 final → 安全 + 性能 + 字符串常量池

基于 VitePress 构建