Java面向对象进阶:封装、继承、多态的实现逻辑与实战案例

⭐️个体主页:Kidd

📚所属栏目:java

在上一篇《Java面向对象基础:类与对象的本质、创建方式与构造方法》中,我们掌握了类与对象的基础概念、对象创建流程及构造方法的核心用法,这是理解面向对象编程的基石。而"封装、继承、多态"作为Java面向对象的三大核心特性,是实现代码复用、扩展与解耦的关键。本文将从"是什么-为什么-怎么用"的逻辑出发,深度解析三大特性的底层逻辑,结合丰富的代码示例拆解实现细节,并通过综合实战案例巩固知识点,帮助读者从"基础入门"迈向"进阶应用"。

一、封装:数据安全与代码隐藏的核心手段

在基础篇的案例中,我们直接使用public修饰成员变量,允许外部通过"对象名.成员变量"直接修改数据。这种方式虽然简单,但会导致数据安全性问题(比如给年龄赋值为负数),同时也会让代码耦合度极高(外部直接依赖对象的内部结构)。而"封装"正是为解决这些问题而生。

1.1 封装的定义与核心思想

封装(Encapsulation)是指将对象的属性(成员变量)和操作属性的方法(成员方法)绑定在一起,同时隐藏对象的内部实现细节,只对外提供统一的访问接口。其核心思想是"隐藏细节、暴露接口",目的是:

  • 保证数据安全性:防止外部直接修改对象的核心数据,通过方法对数据的访问和修改进行控制(比如校验数据合法性)。

  • 降低代码耦合度:外部只需调用提供的接口,无需关注对象内部的实现逻辑,后续内部逻辑修改时,不影响外部调用。

  • 提高代码可维护性:将数据和操作集中在类内部,便于统一管理和修改。

1.2 封装的实现步骤

在Java中,封装的实现主要依赖"访问修饰符"和"getter/setter方法",具体步骤如下:

  1. 私有化成员变量:使用private访问修饰符修饰成员变量,禁止外部直接访问。

  2. 提供公共访问接口:定义公共的(public)getter方法和setter方法,用于外部访问和修改成员变量。

    • getter方法:用于获取成员变量的值,命名规范为get+成员变量名(首字母大写)(如getName());如果成员变量是boolean类型,getter方法命名可改为is+成员变量名(首字母大写)(如isStudent())。

    • setter方法:用于修改成员变量的值,命名规范为set+成员变量名(首字母大写)(如setAge()),方法参数为要设置的值,且在方法内部可添加数据校验逻辑。

1.3 封装的实现示例:Person类的封装改造

我们将基础篇中public修饰的Person类进行封装改造,添加数据校验逻辑:

java 复制代码
public class Person {
    // 1. 私有化成员变量(private修饰,外部无法直接访问)
    private String name;
    private int age;
    private double height;
    private boolean isStudent;

    // 2. 提供公共的getter方法(获取成员变量值)
    public String getName() {
        return name;
    }

    // 3. 提供公共的setter方法(修改成员变量值,添加数据校验)
    public void setName(String name) {
        // 校验:姓名不能为空且长度不超过20
        if (name != null && name.length() <= 20) {
            this.name = name;
        } else {
            System.out.println("姓名不合法!");
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        // 校验:年龄必须在0-150之间
        if (age >= 0 && age <= 150) {
            this.age = age;
        } else {
            System.out.println("年龄不合法!");
        }
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        // 校验:身高必须在0.5-2.5米之间
        if (height >= 0.5 && height <= 2.5) {
            this.height = height;
        } else {
            System.out.println("身高不合法!");
        }
    }

    // boolean类型的getter方法,命名为isXXX
    public boolean isStudent() {
        return isStudent;
    }

    public void setStudent(boolean student) {
        isStudent = student;
    }

    // 成员方法:介绍自己
    public void introduce() {
        String studentStr = isStudent ? "是" : "不是";
        System.out.println("大家好,我叫" + name + ",今年" + age + "岁,身高" + height + "米," + studentStr + "学生。");
    }

    // 测试封装效果
    public static void main(String[] args) {
        Person zhangSan = new Person();
        
        // 错误:无法直接访问private成员变量
        // zhangSan.name = "张三"; 
        // zhangSan.age = -25; // 非法年龄
        
        // 通过setter方法设置值(会触发数据校验)
        zhangSan.setName("张三");
        zhangSan.setAge(-25); // 输出:年龄不合法!
        zhangSan.setAge(25);
        zhangSan.setHeight(1.75);
        zhangSan.setStudent(true);
        
        // 通过getter方法获取值
        System.out.println("姓名:" + zhangSan.getName()); // 输出:张三
        System.out.println("年龄:" + zhangSan.getAge()); // 输出:25
        
        zhangSan.introduce(); // 输出:大家好,我叫张三,今年25岁,身高1.75米,是学生。
    }
}

1.4 访问修饰符的完整解析

封装的实现依赖访问修饰符,Java提供了4种访问修饰符,用于控制类、成员变量、成员方法的访问权限,从严格到宽松依次为:private < 默认(无修饰符) < protected < public。其访问范围如下表所示:

访问修饰符 本类内部 同一包内 不同包的子类 任意位置
private(私有) 可访问 不可访问 不可访问 不可访问
默认(无修饰符) 可访问 可访问 不可访问 不可访问
protected(受保护) 可访问 可访问 可访问 不可访问
public(公共) 可访问 可访问 可访问 可访问
使用建议:
  • 成员变量:优先使用private修饰,通过getter/setter暴露访问接口。

  • 成员方法:根据需求选择修饰符,对外提供的接口用public,仅本类内部使用的用private,仅同一包内或子类使用的用protected或默认。

  • 类:只能使用public或默认修饰(外部类),内部类可使用任意修饰符。

二、继承:代码复用与体系构建的核心机制

在实际开发中,多个类可能会存在相同的属性和方法(比如Student类和Teacher类都有姓名、年龄属性,都有吃饭、睡觉方法)。如果每个类都重复定义这些内容,会导致代码冗余、维护成本高。而"继承"可以让一个类(子类)继承另一个类(父类)的属性和方法,实现代码复用,同时建立类之间的层级关系。

2.1 继承的定义与核心概念

继承(Inheritance)是指子类(Subclass)继承父类(Superclass)的非私有属性和方法,同时子类可以添加自己独有的属性和方法,或重写父类的方法。其核心概念:

  • 父类:也叫超类、基类,是被继承的类,通常是多个子类的抽象共性(如Person类是Student、Teacher类的父类)。

  • 子类:也叫派生类,是继承父类的类,具有父类的共性,同时拥有自身的特性(如Student类有学号、成绩属性,Teacher类有工号、薪资属性)。

  • 继承的关键字:Java中通过extends关键字实现继承,语法为class 子类名 extends 父类名 {}

继承的优势:

  1. 代码复用:无需重复定义父类已有的属性和方法。

  2. 代码扩展:子类可在父类基础上添加新的属性和方法,实现功能扩展。

  3. 建立类体系:通过继承构建类的层级关系(如Person→Student→Undergraduate),便于代码管理和理解。

2.2 继承的实现示例:Student类继承Person类

基于前面封装后的Person类,实现Student类和Teacher类的继承:

java 复制代码
// 父类:Person(已封装)
public class Person {
    private String name;
    private int age;
    private double height;

    // getter/setter方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        if (name != null && name.length() <= 20) {
            this.name = name;
        } else {
            System.out.println("姓名不合法!");
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age >= 0 && age <= 150) {
            this.age = age;
        } else {
            System.out.println("年龄不合法!");
        }
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        if (height >= 0.5 && height <= 2.5) {
            this.height = height;
        } else {
            System.out.println("身高不合法!");
        }
    }

    // 父类的普通方法
    public void eat() {
        System.out.println(name + "正在吃饭...");
    }

    public void sleep() {
        System.out.println(name + "正在睡觉...");
    }
}

// 子类:Student 继承 Person(extends关键字)
public class Student extends Person {
    // 子类独有的属性(父类没有的)
    private String studentId; // 学号
    private double score; // 成绩

    // 子类独有的getter/setter方法
    public String getStudentId() {
        return studentId;
    }

    public void setStudentId(String studentId) {
        this.studentId = studentId;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        if (score >= 0 && score <= 100) {
            this.score = score;
        } else {
            System.out.println("成绩不合法!");
        }
    }

    // 子类独有的方法(父类没有的)
    public void study() {
        System.out.println(getName() + "(学号:" + studentId + ")正在学习...");
    }

    // 测试继承效果
    public static void main(String[] args) {
        Student zhangSan = new Student();
        
        // 调用父类的setter方法设置属性(继承的方法)
        zhangSan.setName("张三");
        zhangSan.setAge(20);
        zhangSan.setHeight(1.75);
        
        // 调用子类的setter方法设置独有属性
        zhangSan.setStudentId("2024001");
        zhangSan.setScore(92.5);
        
        // 调用父类的getter方法获取属性(继承的方法)
        System.out.println("姓名:" + zhangSan.getName() + ",年龄:" + zhangSan.getAge());
        
        // 调用父类的普通方法(继承的方法)
        zhangSan.eat();
        zhangSan.sleep();
        
        // 调用子类的独有方法
        zhangSan.study();
    }
}

运行结果:

Plain 复制代码
姓名:张三,年龄:20
张三正在吃饭...
张三正在睡觉...
张三(学号:2024001)正在学习...

关键说明:

  • 子类继承父类后,可直接使用父类的publicprotected属性/方法(private属性/方法无法直接访问,需通过父类的public getter/setter访问)。

  • 子类可添加独有的属性和方法,实现功能扩展(如Student类的studentId、score属性和study方法)。

  • Java是单继承语言:一个子类只能直接继承一个父类(即extends后面只能跟一个类名),但一个父类可以有多个子类,子类也可以被其他类继承(形成多层继承,如Person→Student→Undergraduate)。

2.3 子类的构造方法与super关键字

在继承关系中,子类的构造方法有一个重要规则:子类的构造方法在执行时,会先默认调用父类的无参构造方法 。这是因为子类需要先初始化父类的属性和方法,才能初始化自身的属性和方法。如果父类没有无参构造方法(比如只定义了带参构造),则子类必须在构造方法的第一行通过super()显式调用父类的带参构造方法,否则编译错误。

核心知识点:super关键字的用法

super关键字用于访问父类的属性、方法和构造方法,主要有3种用法:

  1. super():调用父类的构造方法,必须放在子类构造方法的第一行。如果不写,JVM会默认添加super()(调用父类无参构造)。

  2. super.属性名:访问父类的非私有属性(当子类和父类有同名属性时,用于区分)。

  3. super.方法名():调用父类的非私有方法(当子类重写父类方法时,用于调用父类的原方法)。

示例:子类构造方法与super的使用
java 复制代码
// 父类:Person(定义带参构造,无无参构造)
public class Person {
    private String name;
    private int age;

    // 父类的带参构造方法(无无参构造)
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("父类带参构造方法被调用");
    }

    // getter方法
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

// 子类:Student 继承 Person
public class Student extends Person {
    private String studentId;
    private double score;

    // 子类的带参构造方法:必须显式调用父类的带参构造
    public Student(String name, int age, String studentId, double score) {
        super(name, age); // 调用父类的带参构造,必须放在第一行
        this.studentId = studentId;
        this.score = score;
        System.out.println("子类带参构造方法被调用");
    }

    // 子类独有的方法
    public void introduce() {
        System.out.println("姓名:" + getName() + ",年龄:" + getAge() + ",学号:" + studentId + ",成绩:" + score);
    }

    public static void main(String[] args) {
        Student zhangSan = new Student("张三", 20, "2024001", 92.5);
        zhangSan.introduce();
    }
}

运行结果:

Plain 复制代码
父类带参构造方法被调用
子类带参构造方法被调用
姓名:张三,年龄:20,学号:2024001,成绩:92.5

注意事项:

  • super()this()不能同时出现在子类构造方法中(因为两者都必须放在第一行)。

  • 如果父类只有带参构造,子类必须显式调用父类的带参构造,否则编译错误(JVM无法找到父类的无参构造)。

2.4 方法重写(Override):子类对父类方法的改造

方法重写是指子类定义一个与父类同名、同参数列表、同返回值类型(或子类返回值类型是父类返回值类型的子类)的方法,用于覆盖父类的方法实现。其目的是:子类根据自身需求,对父类的方法进行"改造",实现个性化功能。

方法重写的规则(必须遵守,否则不是重写)
  1. 方法名必须完全相同。

  2. 参数列表必须完全相同(参数个数、类型、顺序都相同)。

  3. 返回值类型:

    • 父类方法返回值类型是基本数据类型:子类重写方法的返回值类型必须与父类完全相同。

    • 父类方法返回值类型是引用数据类型:子类重写方法的返回值类型可以是父类返回值类型的子类(称为"协变返回类型")。

  4. 访问修饰符:子类重写方法的访问权限不能低于父类方法的访问权限(如父类方法是protected,子类可以是protectedpublic,但不能是private)。

  5. 异常抛出:子类重写方法抛出的异常不能多于父类方法抛出的异常(后续异常章节详细讲解)。

  6. 父类的private方法不能被重写(因为子类无法访问父类的private方法)。

  7. 静态方法不能被重写(静态方法属于类,不属于对象,子类定义同名静态方法只是"隐藏"父类方法,不是重写)。

示例:Student类重写Person类的eat方法
java 复制代码
// 父类:Person
public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    // 父类的eat方法
    public void eat() {
        System.out.println(name + "正在吃家常菜...");
    }

    public String getName() {
        return name;
    }
}

// 子类:Student 重写eat方法
public class Student extends Person {
    public Student(String name) {
        super(name);
    }

    // 方法重写:重写父类的eat方法
    @Override // 注解:用于校验是否是合法的重写(可选,但推荐添加)
    public void eat() {
        System.out.println(getName() + "正在吃食堂饭菜...");
    }

    // 测试重写效果
    public static void main(String[] args) {
        Person person = new Person("张三");
        person.eat(); // 调用父类的eat方法:张三正在吃家常菜...

        Student student = new Student("张三");
        student.eat(); // 调用子类重写的eat方法:张三正在吃食堂饭菜...
    }
}

关键说明:

  • @Override注解:是可选的,但推荐添加。它的作用是告诉编译器"这是一个重写方法",如果不符合重写规则,编译器会报错(避免误写)。

  • 重写的核心是"多态的基础":当子类重写父类方法后,调用该方法时,会根据对象的实际类型(而非引用类型)执行对应的方法实现(后续多态章节详细讲解)。

三、多态:代码灵活扩展与解耦的终极武器

多态(Polymorphism)是面向对象三大特性的核心,它允许不同类的对象对同一消息做出不同的响应。简单来说:父类引用指向子类对象,调用方法时,根据子类对象的实际类型执行对应的方法实现。多态的核心价值是"解耦"------让代码不依赖于具体的子类类型,而是依赖于抽象的父类类型,从而实现代码的灵活扩展。

3.1 多态的实现条件

要实现多态,必须同时满足以下3个条件:

  1. 存在继承关系(子类继承父类,或实现接口------后续接口章节讲解)。

  2. 子类重写父类的方法(多态的核心是方法重写)。

  3. 父类引用指向子类对象(核心语法:父类名 引用变量名 = new 子类名();)。

3.2 多态的实现示例:父类引用指向不同子类对象

基于前面的Person、Student、Teacher类,实现多态:

java 复制代码
// 父类:Person
public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    // 父类的方法(将被子类重写)
    public void work() {
        System.out.println(name + "正在工作...");
    }

    public String getName() {
        return name;
    }
}

// 子类1:Student 继承 Person,重写work方法
public class Student extends Person {
    public Student(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(getName() + "正在学习(学生的工作)...");
    }
}

// 子类2:Teacher 继承 Person,重写work方法
public class Teacher extends Person {
    public Teacher(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(getName() + "正在讲课(老师的工作)...");
    }
}

// 测试多态
public class PolymorphismDemo {
    public static void main(String[] args) {
        // 1. 父类引用指向Student对象(多态核心语法)
        Person zhangSan = new Student("张三");
        zhangSan.work(); // 执行Student的work方法:张三正在学习(学生的工作)...

        // 2. 父类引用指向Teacher对象
        Person liSi = new Teacher("李四");
        liSi.work(); // 执行Teacher的work方法:李四正在讲课(老师的工作)...

        // 3. 父类引用指向父类对象(非多态,执行父类方法)
        Person wangWu = new Person("王五");
        wangWu.work(); // 执行Person的work方法:王五正在工作...

        // 4. 多态的灵活调用:定义方法接收父类引用,可传入任意子类对象
        doWork(zhangSan); // 传入Student对象
        doWork(liSi); // 传入Teacher对象
        doWork(wangWu); // 传入Person对象
    }

    // 方法参数为父类类型,可接收任意子类对象(多态的核心价值:解耦)
    public static void doWork(Person person) {
        person.work();
    }
}

运行结果:

Plain 复制代码
张三正在学习(学生的工作)...
李四正在讲课(老师的工作)...
王五正在工作...
张三正在学习(学生的工作)...
李四正在讲课(老师的工作)...
王五正在工作...

关键分析:

  • 多态的核心表现:Person zhangSan = new Student("张三"); 中,引用变量zhangSan的类型是Person(父类),但指向的对象是Student(子类)。当调用zhangSan.work()时,执行的是Student类重写的work方法,而非Person类的work方法。

  • 多态的价值:doWork方法的参数是Person类型,无需为Student和Teacher分别定义方法,即可接收任意子类对象并调用对应的work方法。如果后续新增"Doctor""Engineer"等子类,只需让它们继承Person并重写work方法,无需修改doWork方法的代码,实现了"对扩展开放,对修改关闭"(开闭原则)。

3.3 多态中的类型转换:向上转型与向下转型

在多态中,父类引用指向子类对象时,存在"向上转型"和"向下转型"两种类型转换方式,这是多态应用中的重要知识点。

1. 向上转型(自动转型)

向上转型是指将子类对象赋值给父类引用变量,即父类名 引用 = new 子类名();。这是多态的核心语法,可以自动完成,无需手动转换

特点:

  • 安全:因为子类是父类的"子集",子类对象一定具备父类的属性和方法。

  • 限制:向上转型后,父类引用只能访问父类的属性和方法,不能访问子类独有的属性和方法(因为编译器认为引用指向的是父类对象)。

java 复制代码
Person zhangSan = new Student("张三"); // 向上转型(自动)
zhangSan.work(); // 可调用父类的方法(子类重写的)
// zhangSan.study(); // 错误:父类引用不能访问子类独有的study方法
2. 向下转型(强制转型)

向下转型是指将父类引用(原本指向子类对象)转换为子类引用变量,即子类名 引用 = (子类名) 父类引用;。这是强制转换,需要手动添加转型运算符

使用场景:当需要通过父类引用访问子类独有的属性和方法时,需要进行向下转型。

特点:

  • 有风险:如果父类引用指向的不是当前子类的对象,而是其他子类或父类的对象,强制转型会抛出ClassCastException(类型转换异常)。

  • 安全转型:可以通过instanceof关键字先判断父类引用指向的对象是否是目标子类的实例,再进行转型(避免异常)。

示例:向下转型的使用与安全校验
java 复制代码
public class CastDemo {
    public static void main(String[] args) {
        // 1. 向上转型
        Person zhangSan = new Student("张三");
        Person liSi = new Teacher("李四");

        // 2. 向下转型:访问子类独有方法
        // 安全转型:先通过instanceof判断
        if (zhangSan instanceof Student) {
            Student student = (Student) zhangSan; // 强制转型
            student.study(); // 访问子类独有的study方法:张三正在学习...
        }

        // 不安全转型:父类引用指向Teacher对象,转型为Student
        if (liSi instanceof Student) {
            Student student = (Student) liSi;
            student.study();
        } else {
            System.out.println("liSi不是Student的实例,无法转型");
        }

        // 3. 错误转型(运行时异常)
        Person wangWu = new Person("王五");
        // if (wangWu instanceof Student) { // 条件不成立,不会执行
        //     Student student = (Student) wangWu; // 运行时抛出ClassCastException
        // }
    }
}

// Student类的study方法(独有)
class Student extends Person {
    public Student(String name) {
        super(name);
    }

    public void study() {
        System.out.println(getName() + "正在学习...");
    }

    @Override
    public void work() {
        // 重写方法
    }
}

运行结果:

Plain 复制代码
张三正在学习...
liSi不是Student的实例,无法转型

关键说明:

  • instanceof关键字:用于判断一个对象是否是某个类(或接口)的实例,语法为对象 instanceof 类名,返回值为boolean类型。

  • 向下转型的核心原则:只有当父类引用原本指向的是目标子类的对象时,向下转型才安全 。通过instanceof提前判断,可以避免类型转换异常。

3.4 多态的应用场景:工厂模式(简单实现)

多态的典型应用场景是"工厂模式",通过工厂类创建对象,客户端无需关注对象的具体创建细节,只需通过父类引用接收对象,实现代码解耦。下面通过简单工厂模式示例演示多态的实际价值:

java 复制代码
// 父类:Product(产品抽象类)
public abstract class Product {
    public abstract void produce(); // 抽象方法(子类必须重写)
}

// 子类1:Phone产品
public class Phone extends Product {
    @Override
    public void produce() {
        System.out.println("生产手机...");
    }
}

// 子类2:Computer产品
public class Computer extends Product {
    @Override
    public void produce() {
        System.out.println("生产电脑...");
    }
}

// 工厂类:ProductFactory(创建产品对象)
public class ProductFactory {
    // 根据类型创建不同的产品对象(多态:返回父类引用)
    public static Product createProduct(String type) {
        if ("phone".equals(type)) {
            return new Phone(); // 向上转型
        } else if ("computer".equals(type)) {
            return new Computer(); // 向上转型
        } else {
            throw new RuntimeException("不支持的产品类型");
        }
    }
}

// 客户端:使用产品(无需关注创建细节)
public class Client {
    public static void main(String[] args) {
        // 通过工厂创建产品对象(父类引用接收)
        Product phone = ProductFactory.createProduct("phone");
        Product computer = ProductFactory.createProduct("computer");

        // 调用方法(多态:执行子类重写的方法)
        phone.produce(); // 输出:生产手机...
        computer.produce(); // 输出:生产电脑...

        // 新增产品时,只需添加子类和修改工厂类,客户端代码无需修改(开闭原则)
        // 示例:新增TV产品
        Product tv = ProductFactory.createProduct("tv");
        tv.produce();
    }
}

// 新增的TV产品(扩展)
class TV extends Product {
    @Override
    public void produce() {
        System.out.println("生产电视...");
    }
}

核心价值:

  • 解耦:客户端(Client)无需直接创建PhoneComputer对象,只需调用工厂类的createProduct方法,降低了客户端与具体产品类的耦合。

  • 扩展灵活:新增产品(如TV)时,只需创建TV类继承Product,并重写produce方法,再修改工厂类的判断逻辑即可,客户端代码无需任何修改,符合"开闭原则"。

四、综合实战:基于三大特性的学生管理系统优化

结合封装、继承、多态三大特性,对基础篇的"学生管理系统"进行优化,实现以下功能:

  1. 定义User抽象父类(封装用户共性:编号、姓名、年龄)。

  2. 定义Student、Teacher子类继承User,添加独有属性和方法,重写父类方法。

  3. 定义管理类(UserManager),使用多态实现对不同用户的统一管理(添加、查询、显示)。

实战代码实现

java 复制代码
// 1. 抽象父类:User(封装共性)
abstract class User {
    private String id; // 编号
    private String name; // 姓名
    private int age; // 年龄

    // 构造方法
    public User(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    // 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 getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age >= 0 && age <= 150) {
            this.age = age;
        } else {
            System.out.println("年龄不合法!");
        }
    }

    // 抽象方法:显示用户信息(子类必须重写)
    public abstract void showInfo();
}

// 2. 子类:Student(继承User)
class Student extends User {
    private double score; // 独有属性:成绩

    // 构造方法
    public Student(String id, String name, int age, double score) {
        super(id, name, age); // 调用父类构造
        this.score = score;
    }

    // getter/setter(封装)
    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        if (score >= 0 && score <= 100) {
            this.score = score;
        } else {
            System.out.println("成绩不合法!");
        }
    }

    // 重写父类抽象方法
    @Override
    public void showInfo() {
        System.out.println("【学生信息】");
        System.out.println("编号:" + getId());
        System.out.println("姓名:" + getName());
        System.out.println("年龄:" + getAge());
        System.out.println("成绩:" + getScore());
        System.out.println("------------------------");
    }

    // 独有方法:学习
    public void study() {
        System.out.println(getName() + "正在学习...");
    }
}

// 3. 子类:Teacher(继承User)
class Teacher extends User {
    private double salary; // 独有属性:薪资

    // 构造方法
    public Teacher(String id, String name, int age, double salary) {
        super(id, name, age);
        this.salary = salary;
    }

    // getter/setter(封装)
    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        if (salary >= 0) {
            this.salary = salary;
        } else {
            System.out.println("薪资不合法!");
        }
    }

    // 重写父类抽象方法
    @Override
    public void showInfo() {
        System.out.println("【教师信息】");
        System.out.println("编号:" + getId());
        System.out.println("姓名:" + getName());
        System.out.println("年龄:" + getAge());
        System.out.println("薪资:" + getSalary());
        System.out.println("------------------------");
    }

    // 独有方法:讲课
    public void teach() {
        System.out.println(getName() + "正在讲课...");
    }
}

// 4. 管理类:UserManager(多态实现统一管理)
class UserManager {
    // 存储用户(多态:父类引用存储子类对象)
    private List<User> userList = new ArrayList<>();

    // 添加用户(多态:接收任意User子类对象)
    public void addUser(User user) {
        userList.add(user);
        System.out.println("添加" + (user instanceof Student ? "学生" : "教师") + "成功!");
    }

    // 根据编号查询用户
    public User findUserById(String id) {
        for (User user : userList) {
            if (user.getId().equals(id)) {
                return user;
            }
        }
        return null;
    }

    // 显示所有用户信息(多态:调用子类重写的showInfo方法)
    public void showAllUsers() {
        System.out.println("【所有用户信息】");
        for (User user : userList) {
            user.showInfo(); // 多态核心:根据对象实际类型执行对应方法
        }
    }
}

// 5. 测试类
public class StudentManagementSystem {
    public static void main(String[] args) {
        // 创建管理对象
        UserManager userManager = new UserManager();

        // 添加学生和教师(多态:父类引用指向子类对象)
        User student1 = new Student("2024001", "张三", 20, 92.5);
        User teacher1 = new Teacher("T2024001", "李四", 35, 8000.0);
        userManager.addUser(student1);
        userManager.addUser(teacher1);

        // 显示所有用户信息
        userManager.showAllUsers();

        // 查询用户并调用独有方法(向下转型)
        User findUser = userManager.findUserById("2024001");
        if (findUser instanceof Student) {
            Student student = (Student) findUser;
            student.study(); // 调用学生独有方法
        }

        User findTeacher = userManager.findUserById("T2024001");
        if (findTeacher instanceof Teacher) {
            Teacher teacher = (Teacher) findTeacher;
            teacher.teach(); // 调用教师独有方法
        }
    }
}

运行结果

Plain 复制代码
添加学生成功!
添加教师成功!
【所有用户信息】
【学生信息】
编号:2024001
姓名:张三
年龄:20
成绩:92.5
------------------------
【教师信息】
编号:T2024001
姓名:李四
年龄:35
薪资:8000.0
------------------------
张三正在学习...
李四正在讲课...

实战总结

本实战案例充分体现了三大特性的价值:

  • 封装:通过private修饰成员变量,getter/setter控制访问,保证数据安全。

  • 继承:Student和Teacher继承User,复用了id、name、age等共性属性和方法,减少代码冗余。

  • 多态:UserManager的addUser、showAllUsers方法接收User类型参数,可处理任意子类对象;调用showInfo方法时,自动执行子类重写的实现,实现了统一管理和灵活扩展。

五、三大特性核心脉络总结

封装、继承、多态是Java面向对象编程的核心,三者相互依赖、相辅相成,共同实现代码的安全、复用与扩展:

  1. 封装是基础:通过访问修饰符隐藏内部细节,暴露统一接口,保证数据安全和代码可维护性。

  2. 继承是前提:让子类复用父类代码,同时扩展自身功能,为多态提供了继承关系基础。

  3. 多态是核心:基于继承和方法重写,实现父类引用指向子类对象,让代码不依赖具体类型,实现灵活扩展和解耦,是面向对象编程的终极目标。

相关推荐
XR技术研习社2 小时前
四种安装特定版本Package的方法
unity·ar·xr·vr
colman wang2 小时前
Java期末
java·开发语言
千里马-horse2 小时前
AsyncContext
开发语言·前端·javascript·c++·napi·asynccontext
Coder_Boy_2 小时前
基于MQ实现秒杀订单系统的异步化架构
java·开发语言·架构
勇往直前plus2 小时前
Jackson 反序列化首字母大写字段映射失败的底层原因与解决方案
java·开发语言·前端
坐吃山猪2 小时前
Python命令行工具Click
linux·开发语言·python
宠..2 小时前
为单选按钮绑定事件
运维·服务器·开发语言·数据库·c++·qt·microsoft
业精于勤的牙2 小时前
最长特殊序列(一)
java·javascript·数据结构