Java 继承与多态

Java继承与多态的实现与应用

        在 Java 编程中,“面向对象” 不是一句空洞的口号,而是通过具体特性解决实际问题的思路。比如我们想描述 “狗” 和 “猫” 这两种动物时,会发现它们都有名字、年龄,都会吃饭、睡觉 —— 这些重复的代码如果每次都手动编写,不仅效率低,后续修改也会变得繁琐。而继承多态,正是 Java 为解决这类问题提供的两大核心特性。今天我们就从实际场景出发,一步步拆解这两个概念的用法与设计思想。

一、继承

1. 为什么需要继承?

        如果分别定义DogCat类,会发现大量重复逻辑:

//狗类
class Dog {
    String name;
    int age;
    //吃饭
    public void eat() {
        System.out.println(name + "啃着骨头吃饭");
    }
    //睡觉
    public void sleep() {
        System.out.println(name + "蜷缩着睡觉");
    }
    //狗特有:叫
    public void bark() {
        System.out.println(name + "汪汪叫个不停");
    }
}

//猫类
class Cat {
    String name; //重复
    int age;     //重复
    //吃饭(重复逻辑)
    public void eat() {
        System.out.println(name + "舔着猫粮吃饭");
    }
    //睡觉(重复逻辑)
    public void sleep() {
        System.out.println(name + "蜷在沙发上睡觉");
    }
    //猫特有:叫
    public void mew() {
        System.out.println(name + "喵喵叫");
    }
}

   nameage属性,eat()sleep()方法在两个类中完全重复。如果后续要加 “体重” 属性,就得同时修改两个类 —— 这显然不符合 “一次修改,处处生效” 的原则。

        这时候,继承就派上用场了:我们可以抽取共性,定义一个 “父类”(比如Animal),让DogCat作为 “子类” 继承父类的共性,再专注于自己的特有逻辑。

2. 继承的核心语法与概念

Java 中用extends关键字表示继承关系,核心概念如下:

  • 父类(超类 / 基类):抽取共性的类(如Animal);
  • 子类(派生类):继承父类的类(如DogCat);
  • 继承效果:子类自动拥有父类的非私有成员(属性和方法),只需编写自己的特有成员。

用继承重构上面的代码,会简洁很多:

//父类:动物
class Animal {
    String name;
    int age;
    //共性方法:吃饭
    public void eat() {
        System.out.println(name + "正在吃饭");
    }
    //共性方法:睡觉
    public void sleep() {
        System.out.println(name + "正在睡觉");
    }
}

//子类:狗(继承Animal)
class Dog extends Animal {
    //特有方法:叫
    public void bark() {
        System.out.println(name + "汪汪叫个不停");
    }
}

//子类:猫(继承Animal)
class Cat extends Animal {
    //特有方法:叫
    public void mew() {
        System.out.println(name + "喵喵叫");
    }
}

        测试一下效果:子类可以直接使用父类的成员,无需重复定义:

public class TestInherit {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "小七"; //父类属性
        dog.eat(); //父类方法:小七正在吃饭
        dog.bark(); //子类特有方法:小七汪汪叫个不停

        Cat cat = new Cat();
        cat.name = "元宝"; //父类属性
        cat.sleep(); //父类方法:元宝正在睡觉
        cat.mew(); //子类特有方法:元宝喵喵叫
    }
}

3. 继承中的关键细节

(1)父类成员的访问规则

子类访问父类成员时,遵循 “就近原则”:

  • 如果子类没有与父类同名的成员,直接访问父类的;
  • 如果子类有与父类同名的成员,优先访问子类自己的;
  • 如果想强制访问父类的同名成员,用super关键字。

比如子类和父类都有a属性,用super.a访问父类的a

class Base {
    int a = 10; //父类的a
}

class Derived extends Base {
    int a = 20; //子类的a
    public void show() {
        System.out.println(a); //优先子类:20
        System.out.println(super.a); //强制父类:10
    }
}
(2)子类构造方法:先有父,再有子

        创建子类对象时,会先执行父类的构造方法,再执行子类的构造方法—— 因为子类的成员依赖父类的共性成员,必须先初始化父类。

  • 如果父类有默认无参构造(没写构造方法时,Java 会自动生成),子类构造会默认调用super()(即使不写,编译器也会加);
  • 如果父类只有带参构造,子类必须显式用super(参数)调用父类的带参构造,否则编译报错。

例子:父类Animal有带参构造,子类Dog必须显式调用:

class Animal {
    String name;
    int age;
    //父类带参构造(无默认无参构造)
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

class Dog extends Animal {
    //子类构造必须显式调用父类带参构造
    public Dog(String name, int age) {
        super(name, age); //必须放在子类构造第一行
    }
}
(3)访问权限:protected 的特殊作用

继承中,父类成员的访问权限会影响子类的可见性。其中protected是为继承设计的权限:

  • 同一包内:子类可直接访问父类的protected成员;
  • 不同包内:子类仍可访问父类的protected成员,但非子类(普通类)不能访问。

        比如父类Animal在包 A,子类Dog在包 B,Dog可以访问Animalprotected属性,但包 B 的普通类Test不能访问。

(4)Java 不支持多继承,但支持多层继承

        Java 的继承是 “单继承” 的 —— 一个子类只能有一个直接父类(比如Dog不能同时继承AnimalPet),但支持 “多层继承”(比如Dog继承AnimalHusky继承Dog)。

        如果想限制一个类被继承,可以用final关键字修饰类(比如 Java 的String类就是final的,不能被继承):

final class Animal { //被final修饰,不能被继承
    //...
}

//编译报错:无法从最终类Animal继承
class Dog extends Animal { }

4. 继承 vs 组合:该怎么选?

        除了继承,组合也是实现代码复用的方式 —— 它表示 “has-a”(有一个)的关系,而继承是 “is-a”(是一个)的关系。比如 “汽车” 和 “发动机”:汽车发动机(组合),而不是汽车发动机(继承);“奔驰”汽车(继承),而不是奔驰汽车(组合)。

        组合的实现很简单:把一个类的实例作为另一个类的属性:

//发动机类
class Engine {
    public void start() {
        System.out.println("发动机启动");
    }
}

//汽车类(组合发动机)
class Car {
    private Engine engine = new Engine(); //组合Engine
    public void startCar() {
        engine.start(); //复用Engine的方法
        System.out.println("汽车启动");
    }
}

//奔驰类(继承Car)
class Benz extends Car { }

        设计建议:能用组合尽量用组合。因为继承会让子类和父类耦合度太高(父类修改会影响所有子类),而组合的耦合度更低,更灵活。

二、多态:让代码 “灵活扩展” 的底层逻辑

1. 什么是多态?

通俗来说,多态就是 “同一行为,不同对象有不同表现”。比如 “吃饭” 这个行为:

  • 狗吃饭是 “啃骨头”;
  • 猫吃饭是 “舔猫粮”;
  • 人吃饭是 “用筷子夹菜”。

在代码中,多态的核心是:父类引用指向子类对象,调用方法时会执行子类的实现

2. 多态的实现条件

要实现多态,必须满足三个条件,缺一不可:

  1. 存在继承关系(子类继承父类);
  2. 子类重写父类的方法;
  3. 通过父类的引用调用重写的方法。

我们用一个完整例子演示:

//1. 父类:Animal
class Animal {
    String name;
    public Animal(String name) {
        this.name = name;
    }
    //父类方法:吃饭(将被重写)
    public void eat() {
        System.out.println(name + "正在吃饭");
    }
}

//2. 子类:Dog(继承Animal,重写eat)
class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
    //重写父类的eat方法
    @Override
    public void eat() {
        System.out.println(name + "啃着骨头吃饭");
    }
}

//2. 子类:Cat(继承Animal,重写eat)
class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
    //重写父类的eat方法
    @Override
    public void eat() {
        System.out.println(name + "舔着猫粮吃饭");
    }
}

//3. 测试:父类引用指向子类对象
public class TestPolymorphism {
    //方法参数是父类引用(Animal)
    public static void feed(Animal animal) {
        animal.eat(); //调用重写的方法
    }

    public static void main(String[] args) {
        Animal dog = new Dog("小七"); //父类引用指向Dog对象
        Animal cat = new Cat("元宝"); //父类引用指向Cat对象

        feed(dog); //输出:小七啃着骨头吃饭
        feed(cat); //输出:元宝舔着猫粮吃饭
    }
}

        这里feed方法的参数是Animal(父类),但传入DogCat(子类)对象时,会自动执行子类的eat方法 —— 这就是多态的魔力。

3. 关键:方法重写(Override)

多态的核心是 “重写”—— 子类对父类的方法进行重新实现,规则如下:

  1. 方法签名必须完全一致:方法名、参数列表、返回值类型(返回值可以是父类返回值的子类,比如父类返回Animal,子类返回Dog);
  2. 访问权限:子类重写的方法权限不能比父类更严格(比如父类是public,子类不能是protected);
  3. 不能重写的方法:static方法、private方法、final方法(final修饰的方法不能被重写);
  4. 建议加@Override注解:编译器会校验是否符合重写规则,避免拼写错误(比如把eat写成eet)。
重写 vs 重载(Overload)

很多人会混淆重写和重载,这里用表格清晰区分:

对比维度重写(Override)重载(Overload)
作用范围父子类之间同一类内部
方法签名必须完全一致必须不同(参数列表不同)
返回值必须一致(或父子关系)可任意修改
访问权限不能更严格可任意修改
与多态的关系多态的核心(父子类多态)类内多态(同一类的不同表现)

4. 向上转型与向下转型

多态中,父类引用指向子类对象的过程叫 “向上转型”,反之叫 “向下转型”。

(1)向上转型(安全)

        “向上转型” 是自动的,比如Animal dog = new Dog("小七")—— 相当于 “把小范围的Dog,放到大范围的Animal中”,是安全的。

常见场景

  • 直接赋值:Animal dog = new Dog("小七")
  • 方法传参:feed(new Dog("小七"))feed参数是Animal);
  • 方法返回值:public static Animal getAnimal() { return new Dog("小七"); }

优点:代码更灵活,无需为每个子类写单独的方法;
缺点:父类引用不能调用子类的特有方法(比如dog.bark()会编译报错,因为Animal类没有bark方法)。

(2)向下转型(不安全,需谨慎)

        如果想调用子类的特有方法,就需要 “向下转型”—— 把父类引用强制转回子类类型。但这种转型是不安全的,比如把指向DogAnimal引用转成Cat,会抛出ClassCastException(类型转换异常)。

解决办法:用instanceof关键字先判断类型,再转型:

public class TestCast {
    public static void main(String[] args) {
        Animal animal = new Dog("小七"); //向上转型

        //错误:直接转型,编译通过但运行报错
        //Cat cat = (Cat) animal; 

        //正确:先用instanceof判断
        if (animal instanceof Dog) {
            Dog dog = (Dog) animal; //安全转型
            dog.bark(); //调用子类特有方法:小七汪汪叫个不停
        }

        if (animal instanceof Cat) {
            Cat cat = (Cat) animal;
            cat.mew();
        }
    }
}

instanceof会判断 “父类引用指向的对象是否是某个子类类型”,返回true才转型,避免异常。

5. 多态的优缺点

优点:

        1.降低圈复杂度,减少 if-else:比如要打印多种图形(圆形、矩形、花朵),不用多态的话需要大量if-else判断类型;用多态只需遍历父类数组,调用统一方法。

//用多态的简洁代码
Shape[] shapes = {new Circle(), new Rect(), new Flower()};
for (Shape s : shapes) {
    s.draw(); //自动调用子类的draw方法
}

        2.可扩展性强:新增一个子类(比如Triangle),只需重写draw方法,无需修改原有代码(符合 “开闭原则”:对扩展开放,对修改关闭)。

缺点:
  1. 运行效率略低:多态依赖 “动态绑定”(运行时才确定调用哪个方法),比静态绑定(编译时确定)慢一点;
  2. 属性没有多态性:如果父类和子类有同名属性,父类引用只能访问父类的属性(比如Animalage=1Dogage=2Animal dog = new Dog()dog.age是 1);
  3. 构造方法没有多态性:且不要在构造方法中调用重写的方法(会触发动态绑定,此时子类对象还没初始化,可能出现奇怪问题)。

6. 避坑:构造方法中不要调用重写的方法

class Parent {
    public Parent() {
        func(); //构造方法中调用func
    }
    public void func() {
        System.out.println("Parent.func()");
    }
}

class Child extends Parent {
    private int num = 1; //子类属性
    @Override
    public void func() {
        System.out.println("Child.func() " + num);
    }
}

public class TestPit {
    public static void main(String[] args) {
        new Child(); //输出什么?
    }
}

输出结果Child.func() 0

        原因:创建Child对象时,会先执行Parent的构造方法,Parentfunc()会触发动态绑定,调用Childfunc()。但此时Childnum还没初始化(Java 中成员变量默认值是 0),所以输出0

        结论:构造方法中尽量只做简单的初始化(比如给属性赋值),不要调用实例方法(尤其是可能被重写的方法)。

三、总结:继承与多态的设计思想

  • 继承:核心是 “代码复用”,通过抽取共性减少重复逻辑,但要注意耦合度(尽量用组合替代继承);
  • 多态:核心是 “灵活扩展”,通过父类引用指向子类对象,让代码适应变化(新增子类无需修改原有逻辑);
  • 底层逻辑:多态依赖 “动态绑定”(运行时确定调用的方法),而继承是多态的基础。

        掌握继承与多态,不仅能写出更简洁、灵活的代码,更能理解面向对象设计的核心思想 ——封装(隐藏细节)、继承(复用代码)、多态(扩展能力),这三大特性共同构成了 Java 面向对象的基石。希望这篇文章能帮你理清继承与多态的逻辑,下次写代码时,不妨多思考:“这里用继承还是组合?能不能用多态让代码更灵活?”—— 好的设计,都是在不断思考中打磨出来的。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值