java学习 -04 继承和多态

Java作为一种面向对象的编程语言,提供了丰富的特性来支持代码的复用与组织。在本文中,我们将重点探讨Java中的继承机制及构造方法的执行顺序,并通过实例代码加以说明。

一、继承

1. 继承的定义

  • 继承(Inheritance)是一种面向对象的编程机制,允许一个类(子类)继承另一个类(父类)的属性和方法,从而实现代码复用。
  • 通过继承,子类可以扩展父类的功能,增加新的特性。
  • 继承主要解决的问题是:共性的抽取,实现代码复用。

2. 继承的基本语法

使用 extends 关键字定义继承关系。

public class 子类 extends 父类 {
    // 子类特有的成员
}

3. 继承方式

访问权限
  • private 成员变量在子类中不可直接访问,但仍然被继承。
  • public 成员变量可以在不同包的子类中直接访问。
  • 推荐使用严格的访问权限以实现封装,隐藏内部实现细节。
继承的优势
  • 代码复用:通过继承,子类可以重用父类的代码,减少重复。
  • 多态性:继承为实现多态提供了基础,使得相同的方法在不同对象上有不同的表现。
注意事项
  • 不建议创建过于复杂的继承层次,通常不超过三层。
  • 使用 final 关键字可以限制类的继承,防止被子类化。

4. 示例

public class Animal {
    String name;
    int age;
    public void eat() {
        System.out.println(name + "正在吃饭");
    }
}

public class Dog extends Animal {
    void bark() {
        System.out.println(name + "汪汪汪~~~");
    }
}

5. 父类成员访问

子类中访问父类的成员变量

访问规则

  • 子类可以访问父类中继承的成员变量,但访问规则取决于以下情况:
    1. 子类和父类不存在同名成员变量:子类可以直接访问父类的成员变量。
    2. 子类和父类成员变量同名:子类优先访问自己的成员变量。如果需要访问父类的同名变量,可以使用 super 关键字。
示例代码
// 父类
class Animal {
    public String name; // 公有成员变量
    protected int age;  // 保护成员变量
}

// 子类
class Cat extends Animal {
    public void mew() {
        System.out.println(name + " 喵喵喵~~~"); // 直接访问父类的成员变量
    }
}

// 另一个子类,演示同名变量
class Dog extends Animal {
    public String name; // 与父类同名的成员变量
    public void bark() {
        System.out.println(name + " 汪汪汪~~~"); // 访问的是自己的成员变量
        System.out.println(super.name + "父类"); // 使用super访问父类的成员变量
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "小狗"; // 子类的成员变量
        dog.age = 5;      // 访问父类的成员变量
        dog.bark();       // 输出: 小狗 汪汪汪~~~ 小狗 父类

        Cat cat = new Cat();
        cat.name = "小猫"; // 访问父类的成员变量
        cat.mew();         // 输出: 小猫 喵喵喵~~~
    }
}
子类中访问父类的成员方法

使用 super 关键字

在子类方法中,如果需要访问父类的成员方法,可以使用 super 关键字,其明确访问父类的方法。只能在非静态方法中使用。

方法访问规则

  • 同名方法:如果子类和父类中有同名方法,优先访问子类的方法。
  • 不同名方法:如果子类中没有同名方法,则访问父类的方法。
示例代码
class Base {
    public void show() {
        System.out.println("Base类的show方法");
    }
}

// 子类
class Derived1 extends Base {
    public void display() {
        this.show(); // 访问子类的方法
    }
}

// 另一个子类,演示同名变量
class Derived2 extends Base {
    public void show() {
        System.out.println("Derived2类的show方法");
    }
    public void display() {
        this.show(); // 访问子类的方法
    }
}

public class Test {
    public static void main(String[] args) {
        Derived1 obj1 = new Derived1();
        obj1.display();
        System.out.println("===========");
        Derived2 obj2 = new Derived2();
        obj2.display();
    }
}

输出结果:

Base类的show方法
===================
Derived2类的show方法

6. 子类构造方法

构造顺序

在创建子类对象时,构造过程遵循“先父后子”的原则:

  1. 首先调用父类构造方法(基类)。
  2. 然后执行子类构造方法。
隐式和显式调用
  • 隐式调用:如果父类有无参构造方法,子类构造方法第一行默认隐含 super() 调用。
  • 显式调用:如果父类构造方法有参数,子类必须显式调用父类构造方法,语法为 super(参数),且必须是构造方法的第一条语句。
注意事项
  • super(...)this(...) 不能同时出现在同一构造方法中。
  • super(...) 只能出现一次,且必须是构造方法的第一条语句。
示例代码
public class Base {
    public Base() {
        System.out.println("Base类构造方法");
    }
}

public class Derived extends Base {
    public Derived() {
        super(); // 显式调用父类构造方法
        System.out.println("Derived类构造方法");
    }
}

public class Test {
    public static void main(String[] args) {
        Derived obj = new Derived();
    }
}

输出结果:

Base类构造方法
Derived类构造方法

7. thissuper

定义
  • this:指向当前对象的引用,用于访问本类的成员变量和方法。
  • super:指向父类的引用,用于访问父类的成员变量和方法。
使用场景
  • 在构造方法中
    • this(...) 用于调用本类的其他构造方法。
    • super(...) 用于调用父类的构造方法。
    • 两者不能同时使用,且必须是构造方法中的第一条语句。
  • 在非静态成员方法中
    • this用来访问本类的方法和属性,
    • super用来访问父类继承下来的方法和属性
访问权限
  • this 可以访问当前对象的所有成员(包括私有成员)。
  • super 只能访问父类中可见的成员,私有成员不可直接访问,但依然被继承。

8. 初始化的顺序

父类的静态代码块——子类的静态代码块

父类的实例代码块——父类的构造方法

子类的实例代码块——子类的构造方法

  • 静态代码块只执行一次,实例代码块在每次实例化时执行,构造方法在实例代码块后执行。

  • 当创建子类对象时,父类的静态和实例代码块、构造方法依次执行,确保对象的完整初始化。

  • 静态代码块在类加载时执行,实例代码块在每次创建对象时执行,构造方法在实例代码块之后执行,普通代码块在方法调用时执行

class Person {
    public String name;
    public int age;
    // 构造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person:构造方法执行");
    }
    // 实例代码块
    {
        System.out.println("Person:实例代码块执行");
    }
    // 静态代码块
    static {
        System.out.println("Person:静态代码块执行");
    }
}

class Student extends Person {
    public Student(String name, int age) {
        super(name, age); // 调用父类构造方法
        System.out.println("Student:构造方法执行");
    }
    // 实例代码块
    {
        System.out.println("Student:实例代码块执行");
    }
    // 静态代码块
    static {
        System.out.println("Student:静态代码块执行");
    }
}

//主方法
public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("张三", 19);
        System.out.println("============================");
        Student student2 = new Student("李四", 20);
    }
}

执行结果:

Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
============================
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行

9. protected 关键字

在类和对象章节中,为了实现封装特性,Java中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其他包中被访问。

package testdemo;

public class Demo1 {
    public int a;
    protected int b;
    int c;
    private int d;
}

// 同包的子类
package testdemo;

public class Demo2 extends Demo1 {
    public void method1() {
        super.a = 10;
        super.b = 20;
        super.c = 30;
        // super.d = 4; // 编译报错,private范围在同包同类
    }
}

// 不同包的子类
package testdemo2;
import testdemo.*;

public class Demo3 extends Demo1 {
    public void method2() {
        super.a = 1;
        super.b = 2;
        // super.c = 3; // 编译报错,default范围在同包的不同类
        // super.d = 4; // 编译报错,private范围在同包同类
    }
}

10. final 关键字

定义
  • final 是Java中的一个关键字,用于限制类、方法或变量的修改。
  • final 关键字可以增强代码的安全性和可维护性,防止类的继承、方法的重写和变量的修改。
使用场景
  • 修饰变量
    • 当一个变量被声明为 final 时,该变量的值在初始化后不能被修改,成为常量。
    • 示例:

java

final int a = 10;
// a = 20; // 编译错误,不能修改final变量
  • 修饰类
    • 当一个类被声明为 final 时,表示该类不能被继承。
    • 示例:

java

final class Animal {
    // 类的内容
}

// class Dog extends Animal { } // 编译错误,无法继承final类
  • 修饰方法
    • 当一个方法被声明为 final 时,表示该方法不能被重写(override)。
    • 示例:

java

class Parent {
    final void show() {
        System.out.println("Parent show method");
    }
}

class Child extends Parent {
    // void show() { } // 编译错误,无法重写final方法
}

11. 继承和组合

定义
  • 继承

    • 是一种类之间的关系,表示“is-a”关系(例如:狗是动物)。
    • 通过继承,子类可以重用父类的属性和方法,增加代码复用性。
    • 语法使用 extends 关键字。
  • 组合

    • 是一种类之间的关系,表示“has-a”关系(例如:汽车有引擎)。
    • 通过组合,一个类可以包含另一个类的实例作为其成员,增强灵活性和可重用性。
    • 不使用特殊的关键字,仅通过创建对象来实现。
使用场景
  • 选择继承的情况

    • 当两个类之间存在明确的层次关系时(如动物与其子类)。
    • 需要共享代码和行为时,可以使用继承。
  • 选择组合的情况

    • 当类之间的关系不适合用继承表示时。
    • 希望通过组合来增强灵活性,便于后期维护和扩展。
    • 当需要更换组合的对象时,使用组合更为方便。
优缺点
  • 继承的优点

    • 代码复用性高,易于扩展。
    • 通过多态可以实现灵活的对象行为。
  • 继承的缺点

    • 增加了类之间的耦合性,可能导致不必要的复杂性。
    • 继承层次过深可能导致代码难以维护。
  • 组合的优点

    • 降低了类之间的耦合性,增强灵活性。
    • 组合关系可以在运行时动态改变,适应性强。
  • 组合的缺点

    • 可能导致代码重复,需要手动管理组合的对象。
示例代码
// 继承示例
class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}

class Dog extends Animal {
    public void bark() {
        System.out.println("狗叫:汪汪");
    }
}

// 组合示例
class Mouse { // 鼠标
    //..
}

class KeyBoard { // 键盘
    //..
}

class Screen { // 屏幕
    //..
}

class Computer { // 电脑
    private Mouse mouse;
    private KeyBoard keyBoard;
    private Screen screen;

    public Computer() {
        this.mouse = new Mouse();
        this.keyBoard = new KeyBoard();
        this.screen = new Screen();
    }
}

二、多态

1. 动态绑定

定义
  • 动态绑定(也称为后期绑定或晚绑定)是指在程序运行时决定调用哪个方法的过程。与之相对的是静态绑定(前期绑定),后者在编译时确定调用的方法。
工作原理
  • 在Java中,当通过父类引用调用方法时,实际调用的是子类中重写的方法,而不是父类的方法。这种特性使得Java能够实现多态。
  • 动态绑定的实现依赖于对象的实际类型,而不是引用的类型。
示例代码
class Animal {
    public void sound() {
        System.out.println("动物发出声音");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("狗:汪汪");
    }
}

class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("猫:喵喵");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal myAnimal;
        myAnimal = new Dog(); // 动态绑定,实际类型为 Dog
        myAnimal.sound();     // 输出: 狗:汪汪
        myAnimal = new Cat(); // 动态绑定,实际类型为 Cat
        myAnimal.sound();     // 输出: 猫:喵喵
    }
}

2. 重写与重载

重写(override)
  • 也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。
  • 重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。
返回类型是父子关系
class Animal {
    public Animal makeSound() {
        System.out.println("Some generic animal sound");
        return null;
    }
}

class Dog extends Animal {
    @Override
    public Dog makeSound() {
        System.out.println("Bark");
        return null;
    }
}

3. 认识多态

概念
  • 多态是指同一个方法在不同对象上有不同的表现形式。简单来说,就是“多种形态”。
  • 通过多态,不同的对象可以通过同一个接口调用各自的实现,增强了代码的灵活性和可扩展性。
多态的实现条件

要在Java中实现多态,必须满足以下条件:

  1. 继承体系:必须有继承关系,即存在父类和子类。
  2. 方法重写:子类必须重写父类中的方法。
  3. 父类引用:通过父类的引用调用重写的方法。
多态的好处
  • 降低代码复杂度:使用多态可以避免大量的if-else 或 switch 语句,从而降低代码的复杂度,使代码更易于理解和维护。
  • 增强扩展性:新增功能时,只需添加新的子类,其他代码无需修改,降低了改动成本。
示例代码
// 父类
class Animal {
    String name;

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

    public void eat() {
        System.out.println(name + " 吃饭");
    }
}

// 子类 Cat
class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void eat() {
        System.out.println(name + " 吃鱼~~~");
    }
}

// 子类 Dog
class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void eat() {
        System.out.println(name + " 吃肉~~~");
    }
}

// 测试多态
public class Test {
    public static void main(String[] args) {
        Animal myCat = new Cat("小猫");
        Animal myDog = new Dog("小狗");
        myCat.eat(); // 输出: 小猫 吃鱼~~~
        myDog.eat(); // 输出: 小狗 吃肉~~~
    }
}
多态的缺陷
  • 运行效率降低:多态的实现依赖于动态绑定,可能导致一定的性能损耗。
  • 属性没有多态性:通过父类引用只能访问父类的属性,无法访问子类的同名属性。
  • 构造方法没有多态性:构造方法在对象创建时调用,无法体现多态。

4. 向上转型和向下转型

概念
  1. 向上转型

    • 定义:将子类对象赋值给父类引用。
    • 语法:父类类型 对象名 = new 子类类型();
    • 优点:
      • 简化代码,增强灵活性。
    • 缺点:
      • 无法调用子类特有的方法。
  2. 向下转型

    • 定义:将父类引用转换回子类类型,以调用子类特有的方法。
    • 注意:
      • 向下转型不安全,可能导致 ClassCastException
      • 使用 instanceof 进行安全检查。
示例代码
Animal animal = new Cat(); // 向上转型
animal.eat(); // 调用父类方法
if (animal instanceof Cat) {
    Cat cat = (Cat) animal; // 向下转型
    cat.mew(); // 调用子类特有方法
}
使用场景
1.向上转型
  • 直接赋值

    • 定义:将子类对象赋值给父类引用。
    • 示例:
    Animal cat = new Cat("小花", 2); // 子类对象赋值给父类引用
    
  • 方法传参

    • 定义:方法的参数可以是父类类型的引用,接收任意子类对象。
    • 示例:
    public static void eatFood(Animal ani) {
        ani.eat(); // 调用父类方法
    }
    

方法返回

  • 定义:方法可以返回父类类型的引用,返回任意子类对象。
  • 示例:
public static Animal buyAnimal(String var) {
    if ("狗".equals(var)) {
        return new Dog("狗狗", 1);
    } else if ("猫".equals(var)) {
        return new Cat("猫猫", 1);
    } else {
        return null;
    }
}

2.向下转型

  • 使用 instanceof 进行安全检查:

    • 在进行向下转型之前,使用 instanceof 关键字确保对象的类型,以避免运行时异常。
    • 示例:
    if (animal instanceof Cat) {
        Cat cat = (Cat) animal; // 安全向下转型
        cat.purr();
    }
    
  • 调用子类特有方法:

    • 当需要调用子类中定义的特有方法时,必须将父类引用向下转型为子类类型。
    • 示例:
    Animal animal = new Dog();
    if (animal instanceof Dog) {
        Dog dog = (Dog) animal; // 向下转型
        dog.bark(); // 调用 Dog 类特有的方法
    }
    

实现接口的特定功能:

  • 当一个类实现了某个接口,并且需要在调用时区分不同实现时,可以通过向下转型来调用实现类的特定功能。
  • 示例:
List<Animal> animals = Arrays.asList(new Cat(), new Dog());
for (Animal animal : animals) {
    if (animal instanceof Cat) {
        ((Cat) animal).climbTree(); // 向下转型调用特有方法
    }
}

处理多态性:

  • 在多态场景中,父类引用可能在运行时指向不同的子类对象。向下转型可以让程序根据实际类型执行特定的操作。
  • 示例:
Animal animal = getAnimal(); // 可能返回 Cat 或 Dog
if (animal instanceof Cat) {
    Cat cat = (Cat) animal; // 向下转型
    cat.mew();
} else if (animal instanceof Dog) {
    Dog dog = (Dog) animal; // 向下转型
    dog.bark();
}

最后,欢迎在评论区留下你的想法、问题或建议。如果你觉得这篇文章对你有帮助,也请别忘了点赞和收藏。让我们一起在Java的世界中不断学习、成长!

        祝你编程愉快,代码无bug!

  • 30
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值