面向对象(OOP)
面向对象编程(Object-Oriented Programming)是 Java 的核心思想,其三大特性是封装、继承、多态。掌握这三大特性是理解 Java 面向对象编程的基础。
一、封装(Encapsulation)
1.1 什么是封装
封装是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。它是面向对象编程的基础特性,核心思想是"高内聚、低耦合"。
┌─────────────────────────────────────┐
│ 外部调用者 │
│ ↓ │
│ ┌───────────────────────┐ │
│ │ public 方法(接口) │ │
│ └───────────────────────┘ │
│ ↑ │
│ ┌───────────────────────┐ │
│ │ private 属性(隐藏) │ │
│ └───────────────────────┘ │
└─────────────────────────────────────┘1.2 封装的实现方式
使用访问修饰符
| 修饰符 | 同一类 | 同一包 | 子类(不同包) | 其他类 |
|---|---|---|---|---|
public | ✅ | ✅ | ✅ | ✅ |
protected | ✅ | ✅ | ✅ | ❌ |
default | ✅ | ✅ | ❌ | ❌ |
private | ✅ | ❌ | ❌ | ❌ |
标准封装模式:JavaBean
public class Person {
// 1. 私有化属性
private String name;
private int age;
// 2. 无参构造器
public Person() {}
// 3. 有参构造器
public Person(String name, int age) {
this.name = name;
this.setAge(age); // 调用 setter 进行校验
}
// 4. getter 方法
public String getName() {
return name;
}
// 5. setter 方法(包含校验逻辑)
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 在 setter 中进行数据校验
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄必须在 0-150 之间");
}
this.age = age;
}
}1.3 封装的优势
| 优势 | 说明 |
|---|---|
| 数据安全 | 隐藏敏感数据,防止外部随意修改 |
| 数据校验 | 在 setter 中添加校验逻辑,保证数据合法性 |
| 灵活性 | 内部实现可自由修改,不影响外部调用 |
| 可维护性 | 降低代码耦合度,便于维护和扩展 |
1.4 常见面试题
Q1: 为什么属性要设为 private?
A:
- 安全性:防止外部直接修改属性值,避免非法数据
- 封装性:隐藏实现细节,只暴露必要的访问接口
- 可控性:可以在 getter/setter 中添加校验、日志、权限控制等逻辑
Q2: 什么是 JavaBean?有什么要求?
A: JavaBean 是符合特定规范的 Java 类:
- 所有属性私有化(
private) - 提供无参构造器
- 提供 public 的 getter/setter 方法
- 实现
Serializable接口(可选,用于序列化)
二、继承(Inheritance)
2.1 什么是继承
继承是指子类继承父类的属性和方法,并可以扩展新的功能。它是代码复用的重要手段,使用 extends 关键字实现。
┌───────────┐
│ Animal │ (父类/基类/超类)
│ + name │
│ + eat() │
└─────┬─────┘
│ extends
┌─────┴─────┐
┌────┴────┐ ┌────┴────┐
│ Dog │ │ Cat │ (子类/派生类)
│ + bark()│ │ + meow()│
└─────────┘ └─────────┘2.2 继承的特点
// 父类
public class Animal {
protected String name;
public void eat() {
System.out.println(name + " 正在吃东西");
}
}
// 子类继承父类
public class Dog extends Animal {
public Dog(String name) {
this.name = name; // 继承自父类的属性
}
public void bark() {
System.out.println(name + " 正在汪汪叫");
}
// 重写父类方法
@Override
public void eat() {
System.out.println(name + " 正在啃骨头");
}
}2.3 继承的规则
| 规则 | 说明 |
|---|---|
| 单继承 | 一个类只能有一个直接父类(但可以多层继承) |
| 构造器不继承 | 子类不继承父类的构造器,必须调用 super() |
| 私有成员不继承 | private 成员不能被子类访问 |
| 访问权限不能更严格 | 重写方法时,访问权限不能比父类更严格 |
2.4 super 关键字
super 用于在子类中访问父类的成员:
public class Child extends Parent {
private String name = "子类名称";
public void show() {
System.out.println(this.name); // 子类的 name
System.out.println(super.name); // 父类的 name
super.method(); // 调用父类的方法
}
public Child() {
super(); // 调用父类构造器(必须在第一行)
}
}2.5 方法重写(Override)
重写是子类重新定义父类的方法,需满足以下条件:
| 条件 | 说明 |
|---|---|
| 方法签名相同 | 方法名、参数列表必须完全一致 |
| 返回类型兼容 | 返回类型相同或是父类方法返回类型的子类 |
| 访问权限放宽 | 子类方法的访问权限不能更严格 |
| 不能重写 final | final 方法不能被重写 |
| 不能重写 static | static 方法属于类,不存在重写 |
public class Animal {
public Animal create() {
return new Animal();
}
}
public class Dog extends Animal {
// 协变返回类型:返回父类方法返回类型的子类
@Override
public Dog create() {
return new Dog();
}
}2.6 常见面试题
Q1: Java 为什么是单继承?
A:
- 避免歧义:如果多继承,两个父类有同名方法时,子类不知道调用哪个
- 简化设计:单继承使类层次结构更清晰,降低复杂度
- 解决方案:可以通过接口多实现来实现类似多继承的效果
Q2: 重写(Override)和重载(Overload)的区别?
A:
| 对比项 | 重写(Override) | 重载(Overload) |
|---|---|---|
| 发生位置 | 父子类之间 | 同一个类中 |
| 方法签名 | 必须相同 | 必须不同(参数列表) |
| 返回类型 | 相同或协变 | 无关 |
| 访问权限 | 不能更严格 | 无关 |
| 多态性 | 运行时多态 | 编译时多态 |
Q3: 构造器能被重写吗?
A: 不能。构造器不属于普通方法,不参与继承,因此不能被重写。但子类可以通过 super() 调用父类构造器。
三、多态(Polymorphism)
3.1 什么是多态
多态是指同一个行为具有多个不同表现形式或形态的能力。在 Java 中,多态是同一个接口,使用不同的实例而执行不同操作。
┌───────────┐
│ Animal │
│ + shout()│
└─────┬─────┘
│
┌─────────┼─────────┐
│ │ │
┌───┴───┐ ┌───┴───┐ ┌───┴───┐
│ Dog │ │ Cat │ │ Pig │
│shout()│ │shout()│ │shout()│
│ 汪汪 │ │ 喵喵 │ │ 哼哼 │
└───────┘ └───────┘ └───────┘
Animal a = new Dog(); a.shout(); // 汪汪
Animal a = new Cat(); a.shout(); // 喵喵
Animal a = new Pig(); a.shout(); // 哼哼3.2 多态的前提
- 继承关系:必须存在父子类关系
- 方法重写:子类必须重写父类的方法
- 向上转型:父类引用指向子类对象
3.3 多态的实现
// 父类
public abstract class Animal {
public abstract void shout();
}
// 子类
public class Dog extends Animal {
@Override
public void shout() {
System.out.println("汪汪汪!");
}
}
public class Cat extends Animal {
@Override
public void shout() {
System.out.println("喵喵喵!");
}
}
// 多态使用
public class Test {
public static void main(String[] args) {
// 向上转型:父类引用指向子类对象
Animal dog = new Dog();
Animal cat = new Cat();
// 同一个方法调用,不同的执行结果
dog.shout(); // 汪汪汪!
cat.shout(); // 喵喵喵!
// 作为参数传递
animalShout(new Dog()); // 汪汪汪!
animalShout(new Cat()); // 喵喵喵!
}
// 多态的应用:参数类型使用父类
public static void animalShout(Animal animal) {
animal.shout();
}
}3.4 向上转型与向下转型
Animal animal = new Dog(); // 向上转型(自动)
// 向下转型(需要强转,可能抛出 ClassCastException)
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 安全转型
dog.shout();
}| 转型类型 | 说明 | 安全性 |
|---|---|---|
| 向上转型 | 子类 → 父类(自动) | ✅ 安全 |
| 向下转型 | 父类 → 子类(强转) | ⚠️ 可能不安全 |
3.5 instanceof 关键字
instanceof 用于判断对象是否是特定类的实例:
Animal animal = new Dog();
System.out.println(animal instanceof Dog); // true
System.out.println(animal instanceof Animal); // true
System.out.println(animal instanceof Cat); // false
// Java 14+ 模式匹配
if (animal instanceof Dog dog) {
dog.shout(); // 自动转型
}3.6 多态的应用场景
| 场景 | 示例 |
|---|---|
| 方法参数 | void feed(Animal a) 可以接收任何动物子类 |
| 集合存储 | List<Animal> animals 可以存储各种动物 |
| 工厂模式 | 根据类型返回不同的子类实例 |
| 策略模式 | 不同策略实现同一接口 |
3.7 成员变量不具备多态性
重要:多态只针对方法,成员变量不具备多态性:
public class Parent {
public String name = "父类";
public void show() {
System.out.println("父类方法");
}
}
public class Child extends Parent {
public String name = "子类"; // 隐藏父类属性
@Override
public void show() {
System.out.println("子类方法");
}
}
public class Test {
public static void main(String[] args) {
Parent p = new Child();
System.out.println(p.name); // "父类" - 变量看编译类型
p.show(); // "子类方法" - 方法看运行类型
}
}3.8 常见面试题
Q1: 多态的底层原理是什么?
A: 多态通过**动态绑定(运行时绑定)**实现:
- 编译时:检查父类是否有该方法,确定方法签名
- 运行时:根据对象的实际类型,从方法表中找到真正要执行的方法
Q2: static 方法能体现多态吗?
A: 不能。static 方法属于类,不参与多态:
static方法在编译时就确定了调用哪个方法(静态绑定)- 即使子类有同名
static方法,也不是重写,而是隐藏
Parent p = new Child();
p.staticMethod(); // 调用 Parent 的静态方法(看编译类型)Q3: private 方法能被重写吗?
A: 不能。private 方法对子类不可见,不存在重写。如果子类定义了同名方法,只是普通的新方法,不是重写。
四、三大特性关系
┌─────────────────────────────────────────────────────┐
│ 面向对象三大特性 │
├─────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 封装 │ ───────→ │ 继承 │ │
│ │ (安全性) │ 基础 │ (复用性) │ │
│ └──────────┘ └────┬─────┘ │
│ │ │
│ ↓ │
│ ┌──────────┐ │
│ │ 多态 │ │
│ │ (扩展性) │ │
│ └──────────┘ │
│ │
│ 封装 → 继承 → 多态:层层递进,相辅相成 │
└─────────────────────────────────────────────────────┘| 特性 | 核心作用 | 关键字/机制 |
|---|---|---|
| 封装 | 隐藏细节,保护数据 | private、getter/setter |
| 继承 | 代码复用,建立关系 | extends、super |
| 多态 | 统一接口,灵活扩展 | 向上转型、重写 |
五、综合面试题
Q1: 谈谈你对面向对象三大特性的理解?
A:
| 特性 | 理解 |
|---|---|
| 封装 | 将数据和操作数据的方法绑定在一起,通过访问修饰符隐藏内部实现,只暴露必要接口。好处是安全性、可维护性、灵活性 |
| 继承 | 子类继承父类的属性和方法,实现代码复用。好处是减少重复代码,建立类之间的层次关系。Java 是单继承 |
| 多态 | 父类引用指向子类对象,同一方法调用有不同实现。好处是提高代码扩展性和灵活性,是开闭原则的基础 |
Q2: 以下代码输出什么?
public class A {
public String name = "A";
public void print() { System.out.println("A"); }
}
public class B extends A {
public String name = "B";
@Override
public void print() { System.out.println("B"); }
}
public class Test {
public static void main(String[] args) {
A a = new B();
System.out.println(a.name);
a.print();
}
}A:
A // 成员变量不具备多态性,看编译类型
B // 方法具有多态性,看运行类型Q3: 如何设计一个不能被继承的类?
A:
// 方法1:使用 final 修饰类
public final class CannotBeExtended {
// ...
}
// 方法2:私有化构造器 + 静态工厂(可以防止继承,但不能实例化)
public class CannotBeExtended {
private CannotBeExtended() {}
public static CannotBeExtended create() {
return new CannotBeExtended();
}
}六、最佳实践
6.1 封装原则
- 所有属性设为
private(除非有特殊原因) - 提供 public 的 getter/setter(按需提供)
- 在 setter 中添加数据校验逻辑
6.2 继承原则
- 遵循 IS-A 关系(Dog IS-A Animal ✅)
- 优先使用组合而非继承(Has-A 优先于 Is-A)
- 父类设计要考虑扩展点(模板方法模式)
6.3 多态原则
- 面向接口编程,而非面向实现编程
- 多用向上转型,少用向下转型
- 合理使用
instanceof进行类型判断
七、抽象类与接口
7.1 抽象类(Abstract Class)
什么是抽象类
抽象类是用 abstract 修饰的类,不能被实例化,主要用于被继承。它可以包含抽象方法和具体方法。
// 抽象类
public abstract class Animal {
protected String name;
// 具体方法(有实现)
public void sleep() {
System.out.println(name + " 正在睡觉");
}
// 抽象方法(无实现,子类必须重写)
public abstract void makeSound();
// 构造方法(供子类调用)
public Animal(String name) {
this.name = name;
}
}
// 子类必须实现所有抽象方法
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + " 汪汪叫");
}
}抽象类的特点
| 特点 | 说明 |
|---|---|
| 不能实例化 | new Animal() ❌ 编译错误 |
| 可以有构造器 | 供子类调用 super() |
| 可以有抽象方法 | 子类必须实现 |
| 可以有具体方法 | 子类可以直接继承使用 |
| 可以有成员变量 | 包括普通变量和常量 |
| 单继承 | 一个类只能继承一个抽象类 |
抽象方法
// 抽象方法:只有声明,没有实现
public abstract void draw();
// 规则:
// 1. 抽象方法必须在抽象类中
// 2. 抽象方法不能是 private(子类无法重写)
// 3. 抽象方法不能是 static(属于实例)
// 4. 抽象方法不能是 final(需要被重写)7.2 接口(Interface)
什么是接口
接口是一种完全抽象的类型,定义了一组行为规范。JDK 8 之前,接口只能包含常量和抽象方法。
// 接口定义
public interface Flyable {
// 常量(默认 public static final)
int MAX_HEIGHT = 10000;
// 抽象方法(默认 public abstract)
void fly();
}
// 实现接口
public class Bird implements Flyable {
@Override
public void fly() {
System.out.println("鸟儿飞翔,最高 " + MAX_HEIGHT + " 米");
}
}
// 多实现
public class Superman implements Flyable, Runnable {
@Override
public void fly() {
System.out.println("超人飞行");
}
@Override
public void run() {
System.out.println("超人奔跑");
}
}JDK 8 新特性:默认方法与静态方法
public interface MyInterface {
// 抽象方法
void abstractMethod();
// 默认方法(JDK 8+):有实现,子类可选择重写
default void defaultMethod() {
System.out.println("接口默认方法");
}
// 静态方法(JDK 8+):属于接口,只能通过接口名调用
static void staticMethod() {
System.out.println("接口静态方法");
}
}
// 使用
public class MyClass implements MyInterface {
@Override
public void abstractMethod() {
System.out.println("实现抽象方法");
}
// defaultMethod() 可以不重写,直接使用
}
// 调用
MyClass obj = new MyClass();
obj.abstractMethod(); // 实现抽象方法
obj.defaultMethod(); // 接口默认方法(可重写)
MyInterface.staticMethod(); // 接口静态方法JDK 9 新特性:私有方法
public interface MyInterface {
default void method1() {
commonLogic(); // 调用私有方法
System.out.println("方法1");
}
default void method2() {
commonLogic(); // 复用私有方法
System.out.println("方法2");
}
// 私有方法(JDK 9+):供默认方法复用
private void commonLogic() {
System.out.println("公共逻辑");
}
// 私有静态方法
private static void staticCommonLogic() {
System.out.println("静态公共逻辑");
}
}接口的特点
| 特点 | 说明 |
|---|---|
| 不能实例化 | new Flyable() ❌ 编译错误 |
| 没有构造器 | 接口没有构造方法 |
| 多实现 | 一个类可以实现多个接口 |
| 多继承 | 接口可以继承多个接口 |
| 方法默认 public | 接口方法默认是 public abstract |
| 变量默认常量 | 变量默认是 public static final |
7.3 抽象类 vs 接口
| 对比项 | 抽象类 | 接口 |
|---|---|---|
| 关键字 | abstract class | interface |
| 继承 | 单继承(extends) | 多实现(implements) |
| 构造器 | ✅ 可以有 | ❌ 不能有 |
| 成员变量 | 任意类型 | 只能是 public static final |
| 抽象方法 | ✅ 可以有 | ✅ 默认都是 |
| 具体方法 | ✅ 可以有 | ✅ JDK 8+ 默认方法 |
| 静态方法 | ✅ 可以有 | ✅ JDK 8+ 支持 |
| 私有方法 | ✅ 可以有 | ✅ JDK 9+ 支持 |
| 设计目的 | 代码复用,模板设计 | 定义行为规范,解耦 |
7.4 使用场景
什么时候用抽象类?
- 需要共享代码:多个子类有共同实现
- 需要成员变量:保存状态
- 需要非 public 成员:控制访问
- IS-A 关系强:如 Dog IS-A Animal
// 抽象类:模板方法模式
public abstract class AbstractProcessor {
// 模板方法:定义算法骨架
public final void process() {
validate(); // 公共逻辑
doProcess(); // 子类实现
log(); // 公共逻辑
}
private void validate() {
System.out.println("参数校验");
}
// 抽象方法:子类实现
protected abstract void doProcess();
private void log() {
System.out.println("记录日志");
}
}什么时候用接口?
- 需要多实现:一个类需要多种能力
- 定义行为规范:不关心具体实现
- 解耦合:面向接口编程
- 标记接口:如
Serializable、Cloneable
// 接口:能力/行为定义
public interface Flyable { void fly(); }
public interface Swimmable { void swim(); }
public interface Runnable { void run(); }
// 一个类可以有多种能力
public class Duck implements Flyable, Swimmable, Runnable {
@Override
public void fly() { System.out.println("鸭子飞"); }
@Override
public void swim() { System.out.println("鸭子游泳"); }
@Override
public void run() { System.out.println("鸭子跑"); }
}7.5 接口继承接口
public interface A {
void methodA();
}
public interface B {
void methodB();
}
// 接口多继承
public interface C extends A, B {
void methodC();
}
// 实现 C 需要实现所有方法
public class Impl implements C {
@Override
public void methodA() { }
@Override
public void methodB() { }
@Override
public void methodC() { }
}7.6 默认方法冲突解决
当一个类实现多个接口,且这些接口有同名的默认方法时:
public interface A {
default void show() {
System.out.println("A");
}
}
public interface B {
default void show() {
System.out.println("B");
}
}
// 解决方案1:必须重写
public class C implements A, B {
@Override
public void show() {
A.super.show(); // 调用 A 的默认方法
// 或 B.super.show();
// 或自己实现
}
}
// 解决方案2:继承优先于接口
public class D extends BaseClass implements A {
// 如果 BaseClass 有 show(),则优先使用
// 如果没有,则使用 A 的默认方法
}7.7 常见面试题
Q1: 抽象类可以没有抽象方法吗?
A: 可以。抽象类不一定必须有抽象方法,但有抽象方法的类必须是抽象类。
// 合法:抽象类没有抽象方法
public abstract class MyClass {
public void normalMethod() {
System.out.println("普通方法");
}
}Q2: 接口能继承抽象类吗?
A: 不能。接口只能继承接口,不能继承类(包括抽象类)。
Q3: 以下代码是否正确?
public abstract class Test {
public static void main(String[] args) {
System.out.println("Hello");
}
}A: 正确。抽象类可以有 main 方法,因为 main 是静态方法,不依赖实例。
Q4: 抽象类和接口的设计理念有什么不同?
A:
| 设计理念 | 抽象类 | 接口 |
|---|---|---|
| 本质 | 是什么(IS-A) | 能做什么(CAN-DO) |
| 关注点 | 代码复用 | 行为规范 |
| 耦合度 | 紧耦合(继承关系) | 松耦合(实现关系) |
| 扩展性 | 单继承限制 | 多实现灵活 |
Q5: 如何实现多继承的效果?
A: Java 通过接口多实现来实现类似多继承的效果:
// 一个类可以同时拥有多种能力
public class Superman implements Flyable, Swimmable, Runnable {
// 同时实现多个接口
}八、总结
| 特性 | 一句话总结 | 面试关键词 |
|---|---|---|
| 封装 | 藏起来,留接口 | private、getter/setter、JavaBean |
| 继承 | 拿过来,加功能 | extends、super、重写、单继承 |
| 多态 | 同接口,不同实现 | 向上转型、动态绑定、instanceof |
| 抽象类 | 半成品模板 | abstract、不能实例化、单继承 |
| 接口 | 行为契约规范 | interface、多实现、默认方法 |
最后更新:2026年3月2日