面向对象_封装、继承、多态

67 阅读10分钟

封装

封装是 Java 面向对象编程的核心特性之一,它通过限制类成员的访问权限,保护数据安全性,并通过特定方法控制数据的读写,确保代码的安全性和可维护性。

访问修饰符(控制访问范围)

Java 提供四种访问修饰符,用于控制类、成员变量和方法的可访问范围,从宽松到严格依次为:
修饰符同一类中同一包中不同包的子类中不同包的非子类中典型应用场景
public完全公开的类、方法
protected允许子类访问的成员
默认(不写)包内共享的类或成员
private类的私有成员变量

关键规则:

  1. 类的修饰符:只能是 public 或默认(default)。
  • public 类:整个项目可见,一个 .java 文件只能有一个 public 类,且类名必须与文件名一致。
  • 默认类:仅同一包内可见,适合包内工具类。
  1. 成员的访问控制:遵循 “最小权限原则”,即尽可能使用严格的修饰符(如能用 private 就不用 public)。

封装的实现:`private` + `getter/setter` 方法

封装的核心是将类的属性(成员变量)用 private 修饰,隐藏内部数据,再通过 publicgetter(读取)和 setter(修改)方法对外提供访问接口,并在方法中加入数据校验逻辑。

示例:封装学生类

public class Student {
    // 私有成员变量(外部无法直接访问)
    private String name;  // 姓名
    private int age;      // 年龄
    private double score; // 成绩

    // 1. name的getter和setter
    public String getName() {
        return name; // 提供读取权限
    }

    public void setName(String name) {
        // 校验:姓名不能为空
        if (name == null || name.trim().isEmpty()) {
            System.out.println("错误:姓名不能为空!");
            return;
        }
        this.name = name; // 符合规则则赋值
    }

    // 2. age的getter和setter
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        // 校验:年龄需在0-150之间
        if (age < 0 || age > 150) {
            System.out.println("错误:年龄必须在0-150之间!");
            return;
        }
        this.age = age;
    }

    // 3. score的getter和setter
    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        // 校验:成绩需在0-100之间
        if (score < 0 || score > 100) {
            System.out.println("错误:成绩必须在0-100之间!");
            return;
        }
        this.score = score;
    }

    // 展示学生信息的方法
    public void showInfo() {
        System.out.println("姓名:" + name + ",年龄:" + age + ",成绩:" + score);
    }
}

示例:调用

public class TestStudent {
    public static void main(String[] args) {
        Student stu = new Student();

        // 尝试直接访问private变量(编译报错)
        // stu.name = "张三"; 

        // 通过setter方法设置值(自动触发校验)
        stu.setName(""); // 触发错误提示:姓名不能为空
        stu.setName("张三"); // 合法赋值

        stu.setAge(-5); // 触发错误提示:年龄不合法
        stu.setAge(20); // 合法赋值

        stu.setScore(150); // 触发错误提示:成绩不合法
        stu.setScore(95.5); // 合法赋值

        // 通过getter方法获取值
        System.out.println("学生姓名:" + stu.getName()); // 输出:张三

        // 调用方法展示信息
        stu.showInfo(); // 输出:姓名:张三,年龄:20,成绩:95.5
    }
}

`getter/setter` 方法的规范

命名规则:

  • getter 方法:非布尔类型变量命名为 getXxx(),如 getName();布尔类型变量通常命名为 isXxx(),如 isPassed()
  • setter 方法:统一命名为 setXxx(参数),参数类型与变量一致,如 setAge(int age)

作用

  • getter:提供读取私有变量的接口,外部通过该方法获取值。
  • setter:提供修改私有变量的接口,可在方法内添加数据校验、日志记录等逻辑。

封装的优势

  1. 数据安全:防止外部随意修改数据(如年龄为负数、成绩超过 100 等不合理值)。
  2. 代码可控:修改数据规则时,只需调整 setter 方法,无需修改所有使用该变量的地方(低耦合)。
  3. 隐藏实现细节:外部只需知道 “如何用”,无需关心 “如何实现”(如变量可能加密存储,但 getter 可返回解密后的值)。

继承

继承是 Java 面向对象编程的三大特性之一,它允许一个类(子类)继承另一个类(父类)的属性和方法,从而实现代码复用和扩展

继承的基本概念

  • 父类(超类 / 基类):被继承的类,包含多个子类共有的属性和方法。
  • 子类(派生类):继承父类的类,可拥有父类的成员,还能添加自己的特有成员或修改父类方法。
  • 关系:子类与父类是 “is-a” 关系(如 DogAnimal 的一种)。

继承的实现:`extends` 关键字

使用 extends 关键字声明子类继承父类,语法:

class 子类名 extends 父类名 {
    // 子类的成员(新增属性和方法)
}

示例:定义Animal父类和子类Dog

// 父类:动物
public class Animal {
    // 父类属性
    protected String name; // 受保护的属性,子类可访问
    
    // 父类方法
    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 + "在汪汪叫");
    }
    
    // 子类构造方法
    public Dog(String name, String breed) {
        this.name = name; // 直接访问父类的protected属性
        this.breed = breed;
    }
}

public class TestInheritance {
    public static void main(String[] args) {
        // 创建子类对象
        Dog dog = new Dog("旺财", "金毛");
        
        // 调用父类继承的方法
        dog.eat();   // 输出:旺财在吃东西
        dog.sleep(); // 输出:旺财在睡觉
        
        // 调用子类特有方法
        dog.bark();  // 输出:旺财在汪汪叫
    }
}

继承的特性

  1. 单继承:Java 只支持单继承(一个子类只能有一个直接父类),但可通过多层继承实现多代扩展(如 DogAnimalObject)。
class A {}
class B extends A {} // 正确:单继承
// class C extends A, B {} // 错误:多继承不允许
  1. 传递性:子类继承父类,同时也继承父类的父类(所有祖先类)的成员。

例如:Object 是所有类的根父类,任何类都间接继承 Object 的方法(如 toString()equals())。

  1. 访问控制:子类只能访问父类中 publicprotected 和同包 default 修饰的成员,private 成员不可直接访问(需通过父类的 getter 访问)。

方法重写(`Override`)

子类可重写父类的方法,以实现子类特有的逻辑。重写需满足以下规则

  • 方法签名必须完全相同:方法名、参数列表(类型、顺序、个数)必须与父类一致。
  • 返回值类型:子类返回值类型需与父类相同,或为父类返回值类型的子类(协变返回类型)。
  • 访问权限:子类方法的访问权限不能严于父类(如父类是 protected,子类可改为 public,但不能改为 private)。
  • 异常声明:子类方法不能抛出比父类更多或更宽泛的异常。
  • **@Override **注解:可选但推荐添加,用于编译器校验是否符合重写规则(避免误写)。

示例:重写 Animal 类的 eat() 方法:

public class Dog extends Animal {
    // 重写父类的eat()方法
    @Override
    public void eat() {
        System.out.println(name + "在啃骨头"); // 子类特有实现
    }
}

调用时,子类对象会优先执行重写后的方法:

Dog dog = new Dog("旺财", "金毛");
dog.eat(); // 输出:旺财在啃骨头(执行子类重写的方法)

继承的优势与注意事项

  • 优势
    1. 代码复用:避免重复定义相同属性和方法。
    2. 扩展性:子类可在父类基础上新增功能。
    3. 多态基础:为后续多态特性提供支持。

  • 注意事项
    1. 避免过度继承(如继承层次过深),否则会增加代码耦合度。
    2. 父类的 private 成员无法被重写,也不能直接访问。
    3. 构造方法不能被继承,但子类构造方法需通过 super() 调用父类构造方法。

多态

多态是 Java 面向对象编程的三大特性之一,核心思想是 “同一行为,不同实现”,即通过父类(或接口)的引用,调用不同子类(或实现类)的重写方法,呈现出不同的效果。多态的实现依赖于继承(或接口实现)、方法重写和引用转型。

多态的前提条件

实现多态必须满足的三个条件:

  1. 继承或实现:子类继承父类,或类实现接口(本质是 “接口继承”)。
  2. 方法重写:子类(或实现类)重写父类(或接口)的方法。
  3. 父类引用指向子类对象:通过父类(或接口)类型的变量,引用子类(或实现类)的对象。

向上转型(多态的核心形式)

向上转型指将子类对象赋值给父类类型的变量(自动转换,无需强制类型转换),是多态的典型体现。

特点:

  • 安全(子类是父类的 “一种”,如 DogAnimal 的一种)。
  • 只能访问父类中定义的成员(包括方法和变量),不能直接访问子类特有成员。
  • 调用方法时,会执行子类重写后的方法(核心:“编译看父类,运行看子类”)。

示例:

public class PolymorphismDemo {
    public static void main(String[] args) {
        // 向上转型:父类引用指向子类对象
        Animal animal1 = new Dog(); 
        Animal animal2 = new Cat(); 

        // 调用方法时,执行子类重写的实现
        animal1.makeSound(); // 输出:狗叫:汪汪汪
        animal2.makeSound(); // 输出:猫叫:喵喵喵

        // 不能直接访问子类特有方法(编译报错)
        // animal1.wagTail(); // 错误:Animal类中无wagTail()方法
    }
}

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

// 子类Dog
class Dog extends Animal {
    // 重写父类方法
    @Override
    public void makeSound() {
        System.out.println("狗叫:汪汪汪");
    }

    // 子类特有方法
    public void wagTail() {
        System.out.println("狗摇尾巴");
    }
}

// 子类Cat
class Cat extends Animal {
    // 重写父类方法
    @Override
    public void makeSound() {
        System.out.println("猫叫:喵喵喵");
    }
}

向下转型(强制类型转换)

向下转型指将父类类型的变量转换为子类类型(需强制转换,格式:子类类型 变量名 = (子类类型) 父类引用)。

特点:

  • 不安全(可能转型失败,如将 Animal 引用的 Cat 对象转为 Dog 类型)。
  • 目的是:通过父类引用访问子类的特有成员(方法或变量)。
  • 必须先通过 instanceof 关键字判断父类引用的实际对象类型,避免 ClassCastException(类型转换异常)。

示例:向下转型的正确与错误用法

public class DowncastingDemo {
    public static void main(String[] args) {
        Animal animal = new Dog(); // 向上转型(实际是Dog对象)

        // 错误:未判断类型直接强转,若实际对象不符会报错
        // Cat cat = (Cat) animal; // 运行时抛出ClassCastException

        // 正确:先用instanceof判断
        if (animal instanceof Dog) {
            // 向下转型:访问Dog的特有方法
            Dog dog = (Dog) animal; 
            dog.wagTail(); // 输出:狗摇尾巴
        }

        if (animal instanceof Cat) {
            Cat cat = (Cat) animal;
            // 实际是Dog对象,此代码块不会执行
        }
    }
}

接口与抽象类实现多态

多态不仅适用于类的继承,也适用于接口的实现(接口是更抽象的 “父类”)。

抽象类实现多态

抽象类不能实例化,但可通过抽象类引用指向其子类对象,调用子类实现的抽象方法。

示例:

// 抽象父类
abstract class Shape {
    public abstract void draw(); // 抽象方法(无实现)
}

// 子类1:圆形
class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("画圆形");
    }
}

// 子类2:矩形
class Rectangle extends Shape {
    @Override
    public void draw() {
        System.out.println("画矩形");
    }
}

// 测试多态
public class AbstractDemo {
    public static void main(String[] args) {
        Shape shape1 = new Circle();  // 向上转型
        Shape shape2 = new Rectangle();

        shape1.draw(); // 输出:画圆形(执行Circle的实现)
        shape2.draw(); // 输出:画矩形(执行Rectangle的实现)
    }
}

接口实现多态

接口中定义抽象方法,类通过 implements 实现接口后,接口引用可指向实现类对象,调用其实现的方法。

示例:

// 接口
interface Flyable {
    void fly(); // 接口方法(默认public abstract)
}

// 实现类1:鸟
class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("鸟用翅膀飞");
    }
}

// 实现类2:飞机
class Plane implements Flyable {
    @Override
    public void fly() {
        System.out.println("飞机用引擎飞");
    }
}

// 测试多态
public class InterfaceDemo {
    public static void main(String[] args) {
        Flyable f1 = new Bird();   // 接口引用指向实现类对象
        Flyable f2 = new Plane();

        f1.fly(); // 输出:鸟用翅膀飞(执行Bird的实现)
        f2.fly(); // 输出:飞机用引擎飞(执行Plane的实现)
    }
}

多态的优势

  • 代码灵活性:同一方法(如 makeSound()fly())可适配不同对象,无需为每个子类单独编写逻辑。
  • 可扩展性:新增子类(如 Pig 继承 Animal)时,无需修改调用方代码(如 animal.makeSound()),符合 “开闭原则”。
  • 解耦:调用方只需依赖父类(或接口),无需关心具体子类,降低代码耦合度(如框架中的依赖注入)。