10年老兵带你学Java(第4课):面向对象三大特性 - 封装、继承、多态

1 阅读8分钟

一、封装:把数据藏起来

1.1 什么是封装?

封装就是四个字:**私有属性,公有方法。 **

把数据藏到类里面,不让外面直接访问,只能通过特定的方法来操作。

**打个比方: **

  • ATM机:钱在机器里面,你不能直接伸手进去拿,必须通过按键(方法)来操作

  • 类:数据在里面,外部不能直接改,必须通过方法

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) {

            System.out.println("年龄不合法!");

            return;

        }

        this.age = age;

    }

  


    // 获取年龄的方法

    public int getAge() {

        return age;

    }

}

1.3 封装的实现:private + getter/setter

**标准写法: **


public class Student {

    // 1. 属性私有化

    private String name;

    private int age;

    private double score;

  


    // 2. 无参构造

    public Student() {

    }

  


    // 3. 有参构造

    public Student(String name, int age, double score) {

        this.name = name;

        this.age = age;

        this.score = score;

    }

  


    // 4. getter和setter

    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) {

            System.out.println("年龄不合法!");

            return;

        }

        this.age = age;

    }

  


    public double getScore() {

        return score;

    }

  


    public void setScore(double score) {

        this.score = score;

    }

  


    // 5. toString方法(调试用)

    @Override

    public String toString() {

        return "Student{name='" + name + "', age=" + age + ", score=" + score + "}";

    }

}

**使用: **


public class Main {

    public static void main(String[] args) {

        Student s = new Student("张三", 18, 95.5);

  


        // 通过方法访问属性

        System.out.println(s.getName());   // 张三

        System.out.println(s.getAge());    // 18

  


        // 修改属性(会被校验)

        s.setAge(-100);  // 输出:年龄不合法!

        s.setAge(20);    // 成功

        System.out.println(s.getAge());   // 20

    }

}

1.4 IDEA快速生成getter/setter

不用一个个手写,IDEA一键生成:

  1. 把光标放在类里面

  2. Alt + Insert

  3. 选择 Getter and Setter

  4. 选择要生成的属性

  5. 回车


二、继承:子承父业

2.1 什么是继承?

继承就是子类继承父类的属性和方法。

**打个比方: **

  • 父亲有:眼睛、鼻子、会说话、会走路

  • 儿子继承了父亲的这些特征,所以也有眼睛、鼻子、会说话、会走路

2.2 继承的语法


// 父类

public class Animal {

    public String name;

    public int age;

  


    public void eat() {

        System.out.println(name + "在吃东西");

    }

  


    public void sleep() {

        System.out.println(name + "在睡觉");

    }

}

  


// 子类:用 extends 继承父类

public class Dog extends Animal {

    // Dog类自动拥有 name、age、eat()、sleep()

  


    // 子类独有的方法

    public void bark() {

        System.out.println(name + "汪汪叫");

    }

}

**使用: **


public class Main {

    public static void main(String[] args) {

        Dog dog = new Dog();

        dog.name = "旺财";

  


        // 从父类继承来的方法

        dog.eat();     // 旺财在吃东西

        dog.sleep();   // 旺财在睡觉

  


        // 子类独有的方法

        dog.bark();    // 旺财汪汪叫

    }

}

2.3 继承的特点

  1. 子类可以继承父类的属性和方法(但构造方法不能继承)

  2. 子类可以有自己的独特属性和方法

  3. 子类可以重写父类的方法(@Override)

  4. Java只支持单继承:一个类只能有一个父类,但父类可以有多个子类

2.4 方法重写(Override)

**子类可以重写父类的方法: **


public class Animal {

    public void sound() {

        System.out.println("动物发出声音");

    }

}

  


public class Cat extends Animal {

    // @Override 可加可不加,但加上更安全(写错方法名会报错)

    @Override

    public void sound() {

        System.out.println("喵喵喵");

    }

}

  


public class Dog extends Animal {

    @Override

    public void sound() {

        System.out.println("汪汪汪");

    }

}

**使用: **


Animal a1 = new Cat();

Animal a2 = new Dog();

  


a1.sound();  // 喵喵喵

a2.sound();  // 汪汪汪

2.5 super关键字

super 指的是父类,用来调用父类的方法或构造方法。


public class Animal {

    public String name;

  


    public Animal(String name) {

        this.name = name;

    }

  


    public void eat() {

        System.out.println(name + "在吃东西");

    }

}

  


public class Dog extends Animal {

    public String color;  // 子类独有的属性

  


    public Dog(String name, String color) {

        super(name);  // 调用父类的构造方法

        this.color = color;

    }

  


    @Override

    public void eat() {

        super.eat();  // 调用父类的方法

        System.out.println(name + "还在啃骨头");

    }

}

2.6 继承关系下的构造方法

**规则:子类构造方法的第一行,必须调用父类的构造方法。 **


public class Animal {

    public String name;

  


    // 父类无参构造

    public Animal() {

        System.out.println("Animal无参构造被调用");

    }

  


    // 父类有参构造

    public Animal(String name) {

        this.name = name;

        System.out.println("Animal有参构造被调用");

    }

}

  


public class Dog extends Animal {

    public String color;

  


    public Dog() {

        super();  // 默认调用父类无参构造

        System.out.println("Dog无参构造被调用");

    }

  


    public Dog(String name, String color) {

        super(name);  // 调用父类有参构造

        this.color = color;

        System.out.println("Dog有参构造被调用");

    }

}

**注意: **

  • 如果父类有无参构造,子类可以不写 super(),会自动调用

  • 如果父类没有无参构造,子类必须显式调用父类的有参构造


三、多态:同一个方法,不同的表现

3.1 什么是多态?

多态:同一个引用类型,不同的对象,执行不同的行为。

**打个比方: **

  • "发出声音"这个动作,猫执行是"喵喵喵",狗执行是"汪汪汪"

  • 同一句话,不同对象,效果不同

3.2 多态的前提

  1. 有继承关系(子类继承父类)

  2. 有方法重写(子类重写父类方法)

  3. 父类引用指向子类对象父类 变量 = new 子类()

3.3 多态的语法


// 父类

public class Animal {

    public void sound() {

        System.out.println("动物发出声音");

    }

}

  


// 子类1

public class Cat extends Animal {

    @Override

    public void sound() {

        System.out.println("喵喵喵");

    }

}

  


// 子类2

public class Dog extends Animal {

    @Override

    public void sound() {

        System.out.println("汪汪汪");

    }

}

**使用多态: **


public class Main {

    public static void main(String[] args) {

        // 父类引用指向子类对象

        Animal a1 = new Cat();  // 编译类型是Animal,运行类型是Cat

        Animal a2 = new Dog();  // 编译类型是Animal,运行类型是Dog

  


        a1.sound();  // 输出:喵喵喵

        a2.sound();  // 输出:汪汪汪

    }

}

3.4 多态的好处

**不用写很多if-else: **


// 不用多态:写很多if-else

public void doSound(Animal animal) {

    if (animal instanceof Cat) {

        System.out.println("喵喵喵");

    } else if (animal instanceof Dog) {

        System.out.println("汪汪汪");

    }

    // 还要加更多...

}

  


// 用多态:一行搞定

public void doSound(Animal animal) {

    animal.sound();  // 自动调用对应子类的重写方法

}

3.5 多态下的属性访问


public class Animal {

    public String name = "动物";

}

  


public class Cat extends Animal {

    public String name = "猫"// 子类独有的属性

}

  


Animal a = new Cat();

System.out.println(a.name);  // 输出:动物(编译时看左边类型)

**结论:多态情况下,属性访问看左边(编译类型),方法调用看右边(运行类型)。 **

3.6 引用类型转换

**向上转型(自动): **


Animal a = new Cat();  // 父类引用指向子类对象,小转大,自动

**向下转型(强制,需要判断): **


Animal a = new Cat();

  


// 向下转型,把Cat拿出来

Cat cat = (Cat) a;  // 大转小,强制

  


// 安全判断

if (a instanceof Cat) {

    Cat cat = (Cat) a;

}


四、综合练习:员工管理系统

4.1 需求

用继承和多态,实现一个员工管理系统:

  • 员工(Employee):有姓名、职位、基本工资

  • 程序员(Programmer):额外有加班费

  • 经理(Manager):额外有奖金

4.2 代码


// 父类:员工

public class Employee {

    protected String name;      // protected:子类可以访问

    protected String position;   // 职位

    protected double baseSalary; // 基本工资

  


    public Employee(String name, String position, double baseSalary) {

        this.name = name;

        this.position = position;

        this.baseSalary = baseSalary;

    }

  


    // 计算月薪(被子类重写)

    public double getMonthSalary() {

        return baseSalary;

    }

  


    // 显示信息

    public void showInfo() {

        System.out.println("姓名:" + name + ",职位:" + position + ",月薪:" + getMonthSalary());

    }

}

  


// 子类:程序员

public class Programmer extends Employee {

    private double overtimePay;  // 加班费

  


    public Programmer(String name, double baseSalary, double overtimePay) {

        super(name, "程序员", baseSalary);

        this.overtimePay = overtimePay;

    }

  


    @Override

    public double getMonthSalary() {

        return baseSalary + overtimePay;

    }

}

  


// 子类:经理

public class Manager extends Employee {

    private double bonus;  // 奖金

  


    public Manager(String name, double baseSalary, double bonus) {

        super(name, "经理", baseSalary);

        this.bonus = bonus;

    }

  


    @Override

    public double getMonthSalary() {

        return baseSalary + bonus;

    }

}

  


// 主程序

public class Main {

    public static void main(String[] args) {

        // 创建不同类型的员工

        Employee e1 = new Employee("张三", "普通员工", 5000);

        Programmer p = new Programmer("李四", 8000, 2000);

        Manager m = new Manager("王五", 10000, 5000);

  


        // 用多态统一管理

        Employee[] employees = {e1, p, m};

  


        System.out.println("=== 员工工资表 ===");

        for (Employee e : employees) {

            e.showInfo();

        }

  


        // 计算总工资

        double total = 0;

        for (Employee e : employees) {

            total += e.getMonthSalary();

        }

        System.out.println("总工资:" + total);

    }

}

4.3 运行结果


=== 员工工资表 ===

姓名:张三,职位:普通员工,月薪:5000.0

姓名:李四,职位:程序员,月薪:10000.0

姓名:王五,职位:经理,月薪:15000.0

总工资:30000.0


五、常见错误

❌ 错误1:把继承和组合搞混


// 错误:这不是继承,是组合

public class Car {

    private Engine engine = new Engine();  // 组合:有一个引擎

}

  


// 正确:继承是"是一个"的关系

public class Car extends Vehicle {

    // Car是一个Vehicle

}

❌ 错误2:忘记加 @Override


public class Cat extends Animal {

    // 没有@Override,容易写错方法名而不自知

    public void sound() {

        System.out.println("喵");

    }

}

❌ 错误3:强制转型没有判断


Animal a = new Cat();

  


// 错误!没有判断就转型,可能报错

Dog d = (Dog) a;  // ClassCastException

  


// 正确:先判断

if (a instanceof Dog) {

    Dog d = (Dog) a;

}

❌ 错误4:构造方法忘记调用super


public class Dog extends Animal {

    public Dog() {

        // 错误!如果父类没有无参构造,会报错

        // 应该写:super();

    }

}


六、本课总结

| 特性 | 关键字 | 作用 |

|------|--------|------|

| 封装 | private + getter/setter | 把数据藏起来,保护数据安全 |

| 继承 | extends | 子承父业,减少代码重复 |

| 多态 | 父类引用指向子类对象 | 同一个方法,不同表现 |

**记住: **

  • 封装:属性私有化,方法公开化

  • 继承:子类 extends 父类,单继承

  • 多态:编译看左边,运行看右边

  • 重写方法加 @Override 更安全


七、下节课预告

第5课:接口与抽象类

  • 抽象类: abstract class

  • 接口: interface

  • 两者的区别和使用场景

  • Java 8/9/11 接口新特性

学完这课,你就能写出规范的面向对象代码了。


**关注我,跟着老兵学Java,少走弯路。 **

💬 **评论区聊聊:封装、继承、多态,哪个最难理解? **