Java面向对象三大特性:封装、继承、多态(个人学习)
目录
前言
面向对象编程(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 多态的前提
三个条件缺一不可:
- 继承(或实现接口)
- 方法重写
- 父类引用指向子类对象(向上转型)
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)机制:
- 编译时:编译器看引用类型(Animal),检查方法是否存在
- 运行时: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 的作用 | 判断对象类型,用于安全的向下转型 |
更多推荐
所有评论(0)