三大特性
三大特性
概述
面向对象编程(OOP)的三大核心特性是封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。这些特性共同构成了面向对象设计的基础,帮助开发者构建模块化、可维护和可扩展的代码。
知识要点
2.1 封装
封装是指将对象的状态(属性)和行为(方法)捆绑在一起,并通过访问控制修饰符限制对内部实现的直接访问,仅暴露必要的接口。
2.1.1 访问控制修饰符
Java提供四种访问控制级别,从严格到宽松依次为:
- private:仅本类可见
- 默认(package-private):本包可见
- protected:本包及子类可见
- public:全局可见
public class User {
    // 私有属性,仅本类可直接访问
    private String username;
    private int age;
    // 公共getter方法,控制属性访问
    public String getUsername() {
        return username;
    }
    // 公共setter方法,控制属性修改
    public void setUsername(String username) {
        // 可以添加验证逻辑
        if (username != null && username.length() <= 20) {
            this.username = username;
        }
    }
    // 省略age的getter和setter
}2.1.2 JavaBean规范
JavaBean是遵循特定规范的Java类,是封装的典型应用:
- 类必须是公共的(public)
- 属性私有化(private)
- 提供公共的getter/setter方法
- 提供无参构造方法
public class Student implements Serializable {
    private String id;
    private String name;
    private int score;
    // 无参构造方法
    public Student() {}
    // 有参构造方法
    public Student(String id, String name) {
        this.id = id;
        this.name = name;
    }
    // getter和setter方法
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getScore() { return score; }
    public void setScore(int score) { this.score = score; }
}2.2 继承
继承允许一个类(子类)继承另一个类(父类)的属性和方法,实现代码复用和层次化设计。Java中使用extends关键字实现单继承。
2.2.1 继承的基本语法
// 父类
public class Animal {
    protected String name;
    protected int age;
    public void eat() {
        System.out.println(name + "正在进食");
    }
    public void sleep() {
        System.out.println(name + "正在睡觉");
    }
}
// 子类继承父类
public class Dog extends Animal {
    // 子类特有属性
    private String breed;
    // 子类特有方法
    public void bark() {
        System.out.println(name + "汪汪叫");
    }
    // 重写父类方法
    @Override
    public void eat() {
        System.out.println(name + "正在吃骨头");
    }
}2.2.2 方法重写(Override)
子类可以重写父类的方法,以提供特定实现。重写需满足:
- 方法名、参数列表必须与父类一致
- 返回类型必须与父类兼容(JDK7+允许协变返回类型)
- 访问修饰符不能严于父类
- 不能抛出比父类更多的 checked 异常
public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println(name + "正在吃鱼");
    }
    // 错误示例:参数列表与父类不一致,这是重载而非重写
    // @Override
    // public void eat(String food) {
    //     System.out.println(name + "正在吃" + food);
    // }
}2.2.3 super关键字
super关键字用于访问父类的成员和构造方法:
public class Bird extends Animal {
    private String color;
    public Bird(String name, int age, String color) {
        // 调用父类构造方法
        super(name, age);
        this.color = color;
    }
    @Override
    public void eat() {
        // 调用父类方法
        super.eat();
        System.out.println("这只" + color + "的鸟吃得很开心");
    }
}2.3 多态
多态是指同一操作作用于不同对象时,会产生不同的执行结果。Java通过方法重写和方法重载实现多态。
2.3.1 向上转型
子类对象可以赋值给父类引用,这是实现多态的基础:
Animal animal1 = new Dog(); // 向上转型
Animal animal2 = new Cat(); // 向上转型
Animal animal3 = new Bird(); // 向上转型
// 调用相同方法,表现不同行为
animal1.eat(); // 输出:正在吃骨头
animal2.eat(); // 输出:正在吃鱼
animal3.eat(); // 输出:正在进食2.3.2 方法重载(Overload)
在同一个类中,允许存在多个同名方法,只要它们的参数列表不同(参数类型、个数或顺序):
public class Calculator {
    // 两个整数相加
    public int add(int a, int b) {
        return a + b;
    }
    // 三个整数相加(参数个数不同)
    public int add(int a, int b, int c) {
        return a + b + c;
    }
    // 两个浮点数相加(参数类型不同)
    public double add(double a, double b) {
        return a + b;
    }
    // 注意:返回类型不同不足以构成重载
    // public double add(int a, int b) {
    //     return a + b;
    // }
}2.3.3 接口与多态
接口是实现多态的重要方式,不同类可以实现相同接口并提供不同实现:
// 定义接口
public interface Shape {
    double calculateArea();
    double calculatePerimeter();
}
// 圆形实现
public class Circle implements Shape {
    private double radius;
    public Circle(double radius) {
        this.radius = radius;
    }
    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
    @Override
    public double calculatePerimeter() {
        return 2 * Math.PI * radius;
    }
}
// 矩形实现
public class Rectangle implements Shape {
    private double length;
    private double width;
    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }
    @Override
    public double calculateArea() {
        return length * width;
    }
    @Override
    public double calculatePerimeter() {
        return 2 * (length + width);
    }
}
// 使用多态
public class ShapeCalculator {
    public static void printShapeInfo(Shape shape) {
        System.out.println("面积: " + shape.calculateArea());
        System.out.println("周长: " + shape.calculatePerimeter());
    }
    public static void main(String[] args) {
        Shape circle = new Circle(5);
        Shape rectangle = new Rectangle(4, 6);
        printShapeInfo(circle);    // 输出圆形的面积和周长
        printShapeInfo(rectangle); // 输出矩形的面积和周长
    }
}知识扩展
3.1 设计思想
3.1.1 里氏替换原则
任何父类出现的地方,都可以用子类替换,且不会影响程序的正确性。这要求子类不能改变父类的预期行为。
反例:
public class Rectangle {
    protected int width;
    protected int height;
    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }
    public int getArea() { return width * height; }
}
// 违反里氏替换原则的正方形类
public class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width; // 修改了父类的行为
    }
    @Override
    public void setHeight(int height) {
        this.width = height;
        this.height = height; // 修改了父类的行为
    }
}解决方案:使用组合而非继承,或重新设计类层次结构。
3.1.2 开闭原则
软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。通过多态可以很好地实现这一原则。
// 符合开闭原则的设计
public interface Payment {
    void pay(double amount);
}
public class Alipay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("支付宝支付: " + amount + "元");
    }
}
public class WechatPay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("微信支付: " + amount + "元");
    }
}
// 新增支付方式时无需修改现有代码
public class UnionPay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("银联支付: " + amount + "元");
    }
}3.2 避坑指南
3.2.1 继承滥用
问题:过度使用继承导致类层次结构复杂,耦合度高。 解决方案:优先考虑组合而非继承。组合是"有一个"关系,继承是"是一个"关系。
// 继承方式(可能不合适)
public class Bird extends Animal {
    private Flyable flyable;
    // ...
}
// 组合方式(更灵活)
public class Bird extends Animal {
    private Flyable flyable;
    
    public Bird(Flyable flyable) {
        this.flyable = flyable;
    }
    
    public void fly() {
        flyable.fly();
    }
}
public interface Flyable {
    void fly();
}
public class HighFly implements Flyable {
    @Override
    public void fly() {
        System.out.println("高空飞行");
    }
}
public class LowFly implements Flyable {
    @Override
    public void fly() {
        System.out.println("低空飞行");
    }
}3.2.2 多态误用
问题:向下转型不安全,可能导致ClassCastException。 解决方案:使用instanceof关键字进行类型检查。
public void doSomething(Animal animal) {
    if (animal instanceof Dog) {
        Dog dog = (Dog) animal; // 安全的向下转型
        dog.bark();
    } else if (animal instanceof Cat) {
        Cat cat = (Cat) animal;
        cat.meow();
    }
}3.2.3 封装过度
问题:将所有属性都私有化并提供简单的getter/setter,实际上破坏了封装。 解决方案:暴露行为而非状态,将业务逻辑封装在对象内部。
// 不好的设计
public class BankAccount {
    private double balance;
    
    public double getBalance() { return balance; }
    public void setBalance(double balance) { this.balance = balance; }
}
// 好的设计
public class BankAccount {
    private double balance;
    
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }
    
    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
        }
    }
    
    public double getBalance() { return balance; }
}3.3 深度思考题
思考题1:重载和重写的区别是什么?
回答:
- 重载(Overload):发生在同一个类中,方法名相同,参数列表不同(类型、个数、顺序),与返回类型和访问修饰符无关。是编译时多态。
- 重写(Override):发生在父子类中,方法名、参数列表、返回类型必须相同,访问修饰符不能严于父类,不能抛出更多checked异常。是运行时多态。
思考题2:Java为什么不支持多继承?
回答: Java不支持类的多继承,主要是为了避免菱形继承问题(钻石问题)。当一个类同时继承两个父类,而这两个父类又继承自同一个祖父类时,如果祖父类的方法被子类重写,子类将无法确定应该继承哪个父类的方法。
Java通过接口实现了多继承的功能,一个类可以实现多个接口,从而避免了菱形继承问题,因为接口只定义方法签名,没有具体实现。
思考题3:如何理解"多态是封装和继承的延伸"?
回答: 封装隐藏了对象的内部实现,继承实现了代码复用和类层次结构,而多态则在这两者的基础上实现了接口的统一和行为的多样化。
多态依赖于继承(或接口实现)来建立类型关系,依赖于封装来隐藏具体实现细节。通过多态,我们可以编写不依赖于具体类型的代码,而是依赖于抽象类型,从而提高代码的灵活性和可扩展性。
例如,通过面向接口编程,我们可以在不修改现有代码的情况下,添加新的实现类,这正是多态带来的好处,也是封装和继承无法单独实现的。
