Java面向对象编程(OOP)的三大核心特征是封装(Encapsulation)、继承(Inheritance) 和多态(Polymorphism)。它们共同构成了面向对象编程的基础,旨在提高代码的复用性、可维护性和扩展性。以下是对每个特征的详细解析,包含定义、作用、实现方式及代码示例。
一、封装(Encapsulation)
1. 定义
封装是指将对象的属性(数据)和方法(操作)捆绑在一起,并通过访问控制符限制外部对内部细节的直接访问,仅暴露必要的接口(方法)。
简单说:“隐藏内部实现,只留访问入口”。
2. 核心作用
- 数据安全:防止外部随意修改对象的内部状态(如年龄不能为负数、密码不能直接暴露)。
- 代码复用:将相关逻辑封装在类中,避免重复代码。
- 降低耦合:外部只需通过接口操作,无需关心内部实现,便于后续修改。
3. 实现方式
-
步骤1:用访问控制符修饰类的成员(属性和方法),限制访问权限。 Java的访问控制符从严格到宽松:private(仅本类可见)→ default(同包可见)→ protected(同包+子类可见)→ public(全局可见)。 核心:属性通常用 private 隐藏,方法用 public 暴露接口。
-
步骤2:提供公共的getter方法(获取属性值)和setter方法(修改属性值),在方法中可添加逻辑校验(如过滤非法值)。
4. 代码示例:封装一个“人”类
public class Person {
// 1. 用private隐藏属性(外部不能直接访问)
private String name; // 姓名
private int age; // 年龄(不能为负数)
private String idCard; // 身份证号(不能修改)
// 2. 构造方法:初始化属性(通过setter复用校验逻辑)
public Person(String name, int age, String idCard) {
setName(name); // 调用setter设置姓名
setAge(age); // 调用setter设置年龄(含校验)
this.idCard = idCard; // 身份证号直接赋值(不提供setter,禁止修改)
}
// 3. 姓名的getter和setter
public String getName() {
return name; // 允许外部获取姓名
}
public void setName(String name) {
// 校验:姓名不能为空
if (name == null || name.trim().isEmpty()) {
this.name = "未知姓名";
} else {
this.name = name;
}
}
// 4. 年龄的getter和setter(含校验)
public int getAge() {
return age;
}
public void setAge(int age) {
// 校验:年龄不能为负数
if (age < 0) {
this.age = 0; // 非法值修正为0
} else {
this.age = age;
}
}
// 5. 身份证号仅提供getter(禁止修改)
public String getIdCard() {
return idCard;
}
// 6. 暴露公共方法:打印个人信息
public void introduce() {
System.out.println("我叫" + name + ",年龄" + age + ",身份证号:" + idCard);
}
}
5. 测试封装效果
public class TestEncapsulation {
public static void main(String[] args) {
// 创建对象(触发构造方法)
Person p = new Person("", –5, "110101199001011234");
// 不能直接访问private属性(编译报错)
// p.age = -10;
// 通过setter修改属性(自动触发校验)
p.setAge(25); // 合法年龄
p.setName("张三"); // 合法姓名
// 通过getter获取属性
System.out.println("获取姓名:" + p.getName()); // 输出:张三
System.out.println("获取年龄:" + p.getAge()); // 输出:25
// 调用公共方法
p.introduce(); // 输出:我叫张三,年龄25,身份证号:110101199001011234
// 尝试修改身份证号(无setter,无法修改)
// p.idCard = "123"; // 编译报错
}
}
6. 封装的核心总结
- 核心逻辑:“属性私有,方法公开”。
- 关键:通过 private 隐藏数据,通过 getter/setter 控制访问,在方法中加入校验逻辑保证数据合法性。
二、继承(Inheritance)
1. 定义
继承是指让一个类(子类/派生类)直接拥有另一个类(父类/基类)的属性和方法,从而减少代码重复,实现“共性抽取,个性扩展”。
简单说:“子类站在父类的肩膀上,无需重复编写父类已有的代码”。
2. 核心作用
- 代码复用:父类定义共性(如“动物”都有姓名和年龄),子类继承后直接使用,无需重复定义。
- 扩展功能:子类可在父类基础上添加新属性/方法(如“猫”在“动物”基础上增加“抓老鼠”方法)。
- 多态基础:继承是多态的前提(后续多态依赖父类与子类的关系)。
3. 实现方式
- 用 extends 关键字声明继承关系:子类 extends 父类。
- 父类的非私有成员(public、protected、default)会被子类继承(private 成员无法继承,需通过父类的 public 方法访问)。
- Java只支持单继承(一个子类只能有一个直接父类),但支持多层继承(如 A extends B,B extends C)。
4. 关键概念
-
方法重写(Override):子类对父类的方法进行重新实现(方法名、参数列表、返回值必须与父类一致),用于修改或扩展父类行为。 规则:
- 子类方法的访问权限不能比父类更严格(如父类是 public,子类不能是 private)。
- 子类方法不能抛出比父类更多的异常。
-
super关键字:用于访问父类的成员(属性、方法、构造方法):
- super.属性 / super.方法():访问父类的属性或方法(解决与子类成员的命名冲突)。
- super(参数):调用父类的构造方法(必须放在子类构造方法的第一行)。
5. 代码示例:动物类的继承关系
// 父类:动物(抽取共性)
class Animal {
// 父类属性(非私有,可被子类继承)
protected String name; // 姓名
protected int age; // 年龄
// 父类构造方法
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
// 父类方法:动物都会叫
public void bark() {
System.out.println("动物在叫");
}
// 父类方法:获取信息
public void getInfo() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
// 子类:猫(继承自动物)
class Cat extends Animal {
// 子类新增属性(个性)
private String color; // 毛色
// 子类构造方法:必须先调用父类构造(super)
public Cat(String name, int age, String color) {
super(name, age); // 调用父类的构造方法初始化name和age
this.color = color;
}
// 方法重写:重写父类的bark(),定义猫的叫声
@Override // 注解:校验是否符合重写规则(可选但推荐)
public void bark() {
System.out.println(name + "(猫)喵喵叫");
}
// 子类新增方法(个性)
public void catchMouse() {
System.out.println(color + "的" + name + "在抓老鼠");
}
// 重写父类的getInfo(),添加毛色信息
@Override
public void getInfo() {
super.getInfo(); // 调用父类的getInfo()
System.out.println("毛色:" + color);
}
}
// 子类:狗(继承自动物)
class Dog extends Animal {
// 子类构造方法
public Dog(String name, int age) {
super(name, age); // 调用父类构造
}
// 方法重写:狗的叫声
@Override
public void bark() {
System.out.println(name + "(狗)汪汪叫");
}
}
6. 测试继承效果
public class TestInheritance {
public static void main(String[] args) {
// 创建Cat对象(子类)
Cat cat = new Cat("小花", 2, "白色");
cat.getInfo(); // 继承父类的方法,并重写扩展 → 输出:姓名:小花,年龄:2 毛色:白色
cat.bark(); // 重写父类的方法 → 输出:小花(猫)喵喵叫
cat.catchMouse(); // 子类新增方法 → 输出:白色的小花在抓老鼠
// 创建Dog对象(子类)
Dog dog = new Dog("旺财", 3);
dog.getInfo(); // 继承父类的方法 → 输出:姓名:旺财,年龄:3
dog.bark(); // 重写父类的方法 → 输出:旺财(狗)汪汪叫
}
}
7. 继承的核心总结
- 核心逻辑:“共性放父类,个性放子类”。
- 关键:通过 extends 实现继承,super 访问父类成员,@Override 重写父类方法。
- 限制:Java单继承,但可通过接口实现多继承类似的效果。
三、多态(Polymorphism)
1. 定义
多态是指同一行为(方法调用)在不同对象上表现出不同的实现。即“一个接口,多种实现”。
简单说:“同样的调用语句,因对象不同,执行结果不同”。
2. 核心作用
- 提高代码扩展性:新增子类时,无需修改原有代码(符合“开闭原则”)。
- 简化代码逻辑:用父类引用统一管理不同子类对象,减少分支判断。
3. 实现条件
多态的实现必须同时满足3个条件:
4. 关键概念
-
向上转型:父类引用指向子类对象(自动完成)。 例如:Animal animal = new Cat();(animal 是父类引用,指向子类 Cat 对象)。 特点:父类引用只能调用父类中定义的方法(或子类重写的方法),不能调用子类新增的方法。
-
向下转型:将父类引用强制转换为子类类型(需显式转换,且必须先通过 instanceof 校验,否则可能抛 ClassCastException)。 例如:if (animal instanceof Cat) { Cat cat = (Cat) animal; }
-
动态绑定:程序运行时,JVM会根据实际对象的类型(而非引用类型)调用对应的方法(即调用子类重写的方法)。
5. 代码示例:动物多态
基于前面的 Animal、Cat、Dog 类,演示多态:
public class TestPolymorphism {
public static void main(String[] args) {
// 1. 向上转型:父类引用指向子类对象(多态的核心)
Animal animal1 = new Cat("小花", 2, "白色"); // 父类引用指向Cat
Animal animal2 = new Dog("旺财", 3); // 父类引用指向Dog
// 2. 多态体现:同样的方法调用,因对象不同,结果不同
animal1.bark(); // 实际调用Cat的bark() → 输出:小花(猫)喵喵叫
animal2.bark(); // 实际调用Dog的bark() → 输出:旺财(狗)汪汪叫
// 3. 父类引用不能直接调用子类新增的方法(编译报错)
// animal1.catchMouse();
// 4. 向下转型:将父类引用转回子类类型(需先校验)
if (animal1 instanceof Cat) { // 校验animal1是否指向Cat对象
Cat cat = (Cat) animal1; // 强制转换
cat.catchMouse(); // 现在可以调用子类新增方法 → 输出:白色的小花在抓老鼠
}
// 5. 多态的扩展性:新增子类时,无需修改原有代码
Animal animal3 = new Bird("鹦鹉", 1); // 新增Bird类(继承Animal)
animal3.bark(); // 调用Bird重写的bark() → 输出:鹦鹉(鸟)吱吱叫
}
}
// 新增子类:鸟(继承自动物,演示扩展性)
class Bird extends Animal {
public Bird(String name, int age) {
super(name, age);
}
// 重写父类方法
@Override
public void bark() {
System.out.println(name + "(鸟)吱吱叫");
}
}
6. 多态的核心总结
- 核心逻辑:“父类引用指向子类对象,调用方法时执行子类重写的实现”。
- 本质:动态绑定(运行时确定调用哪个子类的方法)。
- 优势:新增子类时,只需重写父类方法,原有代码(如父类引用的调用)无需修改,大幅提高扩展性。
四、三大特征的联系与总结
三者协同工作,使Java代码更易于维护、扩展和复用,是面向对象编程的核心思想。
练习题
评论前必须登录!
注册