目录

前言

一、封装(Encapsulation)

1.1 什么是封装

1.2 为什么需要封装

1.3 访问修饰符

二、继承(Inheritance)

2.1 什么是继承

2.2 继承的规则

2.3 super 关键字

2.4 方法重写(Override)

2.5 继承的局限

三、多态(Polymorphism)

3.1 什么是多态

3.2 多态的前提

3.3 向上转型与向下转型

3.4 多态的底层原理——动态绑定

3.5 多态的经典应用场景

3.6 多态的限制

四、三者的关系

五、面试高频题总结


前言

面向对象编程(OOP)的三大特性是 Java 的基石。不管框架用得多溜,如果这三个概念没吃透,面试一问就露馅。本文从本质出发,结合代码和面试题,把这三块讲清楚。


一、封装(Encapsulation)

1.1 什么是封装

封装的本质是隐藏实现细节,暴露有限接口

把数据(属性)和操作数据的方法捆绑在一个类里,外部不能直接访问内部数据,只能通过提供的方法来操作。

1.2 为什么需要封装

// 反面教材:不封装
public class Student {
    public int age;
}

Student s = new Student();
s.age = -100;  // 合法但不合理,没有任何保护
// 正确做法:封装
public class Student {
    private int age;

    public void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("年龄不合法");
        }
        this.age = age;
    }

    public int getAge() {
        return this.age;
    }
}

封装的好处:

  • 数据安全:通过 setter 做校验,防止非法数据
  • 灵活扩展:内部实现可以改,外部调用不受影响
  • 降低耦合:调用者不需要知道内部逻辑

1.3 访问修饰符

修饰符同类同包子类不同包
private
default(不写)
protected
public

面试题private 修饰的属性能被子类访问吗?
答:不能直接访问,但可以通过 public 的 getter/setter 间接访问。


二、继承(Inheritance)

2.1 什么是继承

继承是子类复用父类的属性和方法,并在此基础上扩展。

public class Animal {
    protected String name;

    public void eat() {
        System.out.println(name + "在吃东西");
    }
}

public class Dog extends Animal {
    public void bark() {
        System.out.println(name + "在汪汪叫");
    }
}

// 使用
Dog dog = new Dog();
dog.name = "旺财";  // 继承自 Animal
dog.eat();           // 继承自 Animal
dog.bark();          // Dog 自己的方法

2.2 继承的规则

  • Java 单继承:一个类只能有一个父类(extends 后面只能跟一个类)
  • 多层继承:A extends B,B extends C,A 间接继承 C
  • 所有类默认继承 Object
  • final 修饰的类不能被继承(如 String
  • 构造方法不能被继承,但子类构造方法会隐式调用父类无参构造

2.3 super 关键字

public class Dog extends Animal {
    private String breed;

    public Dog(String name, String breed) {
        super.name = name;    // 调用父类属性
        this.breed = breed;
    }

    @Override
    public void eat() {
        super.eat();          // 调用父类方法
        System.out.println("还啃了骨头");
    }
}

super 的两种用法:

  • super.属性 / super.方法:访问父类的属性或方法
  • super():调用父类构造方法,必须放在子类构造方法的第一行

2.4 方法重写(Override)

子类重新定义父类中已有的方法:

public class Animal {
    public void makeSound() {
        System.out.println("动物发出声音");
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("喵喵喵");
    }
}

重写的规则(两同两小一大):

  • 两同:方法名相同、参数列表相同
  • 两小:返回值类型相同或更小(协变返回类型)、抛出异常相同或更小
  • 一大:访问修饰符相同或更大

面试题:重写(Override)和重载(Overload)的区别?

对比项重写 Override重载 Overload
发生位置子类与父类之间同一个类中
方法名必须相同必须相同
参数列表必须相同必须不同
返回值相同或协变无要求
访问修饰符不能更严格无要求
绑定时机运行时(动态绑定)编译时(静态绑定)

2.5 继承的局限

// 问题:Java 不支持多继承
public class A extends B, C { }  // 编译错误!

解决方案:

  • 接口:一个类可以实现多个接口
  • 组合:在类中持有其他类的引用

面试题:为什么不支持多继承?
答:避免菱形继承(Diamond Problem)带来的二义性。如果 B 和 C 都继承 A,且都重写了某个方法,D 同时继承 B 和 C,调用该方法时无法确定用哪个版本。


三、多态(Polymorphism)

3.1 什么是多态

多态的含义是同一行为,不同表现。具体到 Java 中:父类引用指向子类对象,调用同名方法时执行的是子类的实现。

Animal animal1 = new Dog();
Animal animal2 = new Cat();

animal1.makeSound();  // 输出:汪汪汪
animal2.makeSound();  // 输出:喵喵喵

同一段代码 animal.makeSound(),因为实际对象不同,执行结果不同——这就是多态。

3.2 多态的前提

三个条件缺一不可:

  1. 继承(或实现接口)
  2. 方法重写
  3. 父类引用指向子类对象(向上转型)

3.3 向上转型与向下转型

// 向上转型:子类 → 父类(自动,安全)
Animal animal = new Dog();

// 向下转型:父类 → 子类(强制,有风险)
Dog dog = (Dog) animal;       // 正确,animal 实际是 Dog
Cat cat = (Cat) animal;       // ClassCastException!animal 不是 Cat

安全的向下转型:先用 instanceof 判断

if (animal instanceof Cat) {
    Cat cat = (Cat) animal;
    cat.meow();
}

面试题instanceof 的作用?什么时候用?
答:判断一个对象是否是某个类的实例。在向下转型前使用,避免 ClassCastException

3.4 多态的底层原理——动态绑定

Animal animal = new Dog();
animal.makeSound();  // 执行 Dog 的 makeSound,还是 Animal 的?

答案是 Dog 的。这背后是动态绑定(Dynamic Binding)机制:

  1. 编译时:编译器看引用类型(Animal),检查方法是否存在
  2. 运行时:JVM 看实际对象类型(Dog),在 Dog 的方法表中查找 makeSound
编译时:animal 的类型是 Animal → 检查 Animal 有没有 makeSound → 有,编译通过
运行时:animal 实际指向 Dog 对象 → 执行 Dog.makeSound()

这就是为什么:

  • 父类引用只能调用父类中定义过的方法(编译看左边)
  • 实际执行的是子类重写后的方法(运行看右边)

3.5 多态的经典应用场景

// 场景:统一处理不同类型的对象
public void feed(Animal animal) {
    animal.eat();  // 不关心具体是什么动物
}

feed(new Dog());   // Dog 吃东西
feed(new Cat());   // Cat 吃东西
feed(new Bird());  // Bird 吃东西

好处:方法参数用父类类型,新增子类时不需要修改方法——对扩展开放,对修改关闭(开闭原则)。

3.6 多态的限制

Animal animal = new Dog();

// 可以调用:父类中定义的方法(如果被子类重写,执行子类版本)
animal.eat();       // ✅

// 不能调用:子类独有的方法(编译器只看引用类型 Animal)
animal.bark();      // ❌ 编译错误,Animal 中没有 bark()

面试题:多态中,成员变量和静态方法是什么行为?
答:成员变量看左边(引用类型),静态方法看左边(引用类型),普通方法看右边(实际对象类型)。

public class Parent {
    int x = 10;
    static void staticMethod() { System.out.println("Parent static"); }
    void normalMethod() { System.out.println("Parent normal"); }
}

public class Child extends Parent {
    int x = 20;
    static void staticMethod() { System.out.println("Child static"); }
    void normalMethod() { System.out.println("Child normal"); }
}

Parent p = new Child();
System.out.println(p.x);           // 10(变量看左边)
p.staticMethod();                  // Parent static(静态方法看左边)
p.normalMethod();                  // Child normal(普通方法看右边)

四、三者的关系

封装 → 把数据藏起来,通过方法访问
  ↓
继承 → 子类复用父类的封装
  ↓
多态 → 父类引用调用方法,实际执行子类实现
  • 封装是基础:没有封装,继承和多态就失去了意义
  • 继承是桥梁:没有继承,就没有父子关系,多态无从谈起
  • 多态是目的:让代码更灵活,更易扩展

五、面试高频题总结

问题核心答案
面向对象三大特性封装、继承、多态
重写 vs 重载重写是子类覆盖父类(运行时绑定),重载是同类方法不同参数(编译时绑定)
多态的前提继承 + 重写 + 父类引用指向子类对象
多态中变量看谁成员变量和静态方法看左边,普通方法看右边
Java 为什么单继承避免菱形继承的二义性
instanceof 的作用判断对象类型,用于安全的向下转型

更多推荐