JavaSE--继承和多态

目录

1.继承

1.1父类成员访问

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

1.1.2子类中访问父类的成员方法

1.2子类构造方法

1.3继承关系的执行顺序

1.4继承方式

1.5final关键字 

2.多态

2.1重写

2.2向上转型和向下转型

2.3多态的优缺点

2.4避免在构造方法中调用重写的方法


1.继承

继承是面向对象编程中的一个概念,它允许一个类从另一个类继承属性和方法。被继承的类称为父类或基类,继承的类称为子类或派生类。子类可以继承父类的公共成员(如属性和方法),并且可以根据需要添加新成员或重写父类的成员。

继承可以帮助实现代码的重用和模块化,减少重复编写相似的代码的工作量。子类可以获得父类的属性和方法,并可以根据需要进行修改或扩展。通过继承,我们可以建立类的层次结构,从而更好地组织和管理代码。

Java 中如果要表示类之间的继承关系,需要借助 extends 关键字。
//Dog.java(子类)
package demo1;

public class Dog extends Animals{
    public void bark(){
        System.out.println(this.name+" wang wang wang~~~");
    }
}

//Cat.java(子类)
package demo1;

public class Cat extends Animals{
    public void mew(){
        System.out.println(this.name+" mi mi mi~~~");
    }
}

//Animals.java(父类)
package demo1;

public class Animals {
    public String name;
    public int age;
    public void eat(){
        System.out.println(this.name+" is eating...");
    }
}


//Test.java
package demo1;

public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "大黄";
        dog.age = 2;
        dog.eat();
        dog.bark();
        System.out.println("--------------------------");
        Cat cat = new Cat();
        cat.name = "小虎";
        cat.age = 2;
        cat.eat();
        cat.mew();
    }
}

 

1.1父类成员访问

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

        1.子类和父类不存在同名成员变量

                直接访问或者使用this方法。

        2.子类和父类存在同名成员变量 

                用this访问子类变量,用super访问父类变量(super只能在非静态方法中使用)。

//Base.java
package demo;

public class Base {
    public int a = 10;
    public int b = 20;
}

//Derive.java
package demo;

public class Derive extends Base{
    public int c = 30;
    public int a = 100;
    public void show(){
        System.out.println(super.a);//父类中的a=10
        System.out.println(this.a);//子类中的a=100
        System.out.println(b);
        System.out.println(c);
    }
}


//Test.java
package demo;

public class Test {
    public static void main(String[] args) {
        Derive derive = new Derive();
        derive.show();
    }
}

//输出:10
//     100
//     20
//     30
在子类方法中或者通过子类对象访问成员时
  • 如果访问的成员变量子类中有,优先访问自己的成员变量。
  • 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
  • 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
  • 成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找
1.1.2子类中访问父类的成员方法

和访问成员一样。

1.2子类构造方法

子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextInt()) {
            int x = scanner.nextInt();
            int y = scanner.nextInt();
            int z = scanner.nextInt();
            Sub sub = new Sub(x, y, z);
            System.out.println(sub.calculate());
        }
    }

}

class Base {

    private int x;
    private int y;

    public Base(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

}

class Sub extends Base {

    private int z;

    public Sub(int x, int y, int z) {
        super(x,y);
        this.z = z;
    }

    public int getZ() {
        return z;
    }

    public int calculate() {
        return super.getX() * super.getY() * this.getZ();
    }

}

//输入:1  2  3 
//输出:6
  • 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法。
  • 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的 父类构造方法调用,否则编译失败。
  • 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
  • super(...)只能在子类构造方法中出现一次,并且不能和this同时出现。
  • 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有

1.3继承关系的执行顺序

先来看没有继承关系时的执行顺序:

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
    this.name = name;
    this.age = age;
    System.out.println("构造方法执行");
    }
{
    System.out.println("实例代码块执行");
}
static {
    System.out.println("静态代码块执行");
    }
}
    public class TestDemo {
    public static void main(String[] args) {
    Person person1 = new Person("bit",10);
    System.out.println("============================");
    Person person2 = new Person("gaobo",20);
    }
}

//执行结果:
//静态代码块执行
//实例代码块执行
//构造方法执行
//============================
//实例代码块执行
//构造方法执行
  • 静态代码块先执行,并且只执行一次,在类加载阶段执行
  • 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行

有继承关系时的执行顺序:

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 TestDemo4 {
    public static void main(String[] args) {
    Student student1 = new Student("张三",19);
    System.out.println("===========================");
    Student student2 = new Student("gaobo",20);
}


//执行结果:
//Person:静态代码块执行
//Student:静态代码块执行
//Person:实例代码块执行
//Person:构造方法执行
//Student:实例代码块执行
//Student:构造方法执行
//===========================
//Person:实例代码块执行
//Person:构造方法执行
//Student:实例代码块执行
//Student:构造方法执行
  • 父类静态代码块优先于子类静态代码块执行,且是最早执行
  • 父类实例代码块和父类构造方法紧接着执行
  • 子类的实例代码块和子类构造方法紧接着再执行
  • 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

1.4继承方式

继承关系一般不超过三层!

1.5final关键字 

1. 修饰变量或字段,表示常量 ( 即不能修改)
2. 修饰类:表示此类不能被继承
3. 修饰方法:表示该方法不能被重写

2.多态

多态是面向对象编程中的一个重要概念,它指的是同一个方法在不同的对象上具有不同的行为。多态性可以使得一个方法可以被不同类型的对象调用,从而实现不同的操作或者返回不同的结果。

实现条件:

  • 必须在继承体系下
  • 子类必须要对父类中方法进行重写
  • 通过父类的引用调用重写的方法

下面是一个关于多态的简单例子:

假设有一个“动物”类(Animal)和它的两个子类,分别是“狗”类(Dog)和“猫”类(Cat)。每个类都有一个“叫声”(makeSound)的方法。可以通过多态来实现不同动物的叫声。

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

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("狗发出汪汪叫声");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("猫发出喵喵叫声");
    }
}

public class PolymorphismExample {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();
        
        animal1.makeSound(); // 输出:狗发出汪汪叫声
        animal2.makeSound(); // 输出:猫发出喵喵叫声
    }
}

在上面的例子中,通过将父类引用(Animal)指向子类的对象(Dog、Cat),实现了多态性。通过调用父类引用的同名方法(makeSound),根据实际对象类型确定调用的是子类的方法。

在这个例子中,无论是animal1还是animal2的类型都是Animal,但是实际指向的是Dog和Cat对象,所以调用makeSound方法时会根据实际对象类型来决定调用的方法。这样就实现了多态性,不同的动物对象调用makeSound方法时会有不同的叫声输出。

2.1重写

重写(Override)是面向对象编程中的一个概念,指的是在子类中重新定义父类中已经存在的方法,以便使得子类对象可以根据自己的特性来执行该方法。

简单来说,当子类中定义了与父类中同名、同参数列表、同返回类型的方法时,就称为方法的重写。重写的方法在子类中可以有不同的实现,即可以改变方法的行为,但不能改变方法的签名(方法名、参数列表和返回类型)。

重写的规则如下:

  • 重写的方法必须和父类的方法具有相同的方法名、参数列表和返回类型。
  • 重写的方法不能有更严格的访问权限,可以有更宽松的访问权限。例如,父类的方法是public,子类的重写方法可以是public或protected,但不能是private。
  • 重写的方法不能抛出比父类方法更多的异常,可以抛出相同的异常或子类异常,或者不抛出异常。
  • 当调用重写的方法时,实际执行的是子类中的方法,而不是父类中定义的方法。这个过程称为动态绑定。

重写的目的是为了实现多态,通过子类的不同实现来满足不同的需求。在实际使用中,可以根据具体的业务场景来决定是否需要重写某个方法。

2.2向上转型和向下转型

向上转型(upcasting)指的是将一个子类的对象赋值给一个父类的引用变量。这样做可以实现多态性,即通过父类引用变量调用子类对象的方法。

例如,假设有一个父类Animal和一个子类Dog,可以将一个Dog对象向上转型为Animal对象:

Animal animal = new Dog();

在这里,将Dog对象赋值给Animal引用变量,即实现了向上转型。

向上转型的优点:让代码实现更简单灵活。
                  缺陷:不能调用到子类特有的方法。

向下转型(downcasting)指的是将一个父类的引用变量赋值给一个子类的引用变量。在向上转型后,如果需要使用子类特有的方法或属性,可以通过向下转型实现。

例如,在上述例子中,可以使用向下转型将Animal引用变量转换为Dog对象:

Dog dog = (Dog) animal;

在这里,使用了强制类型转换符将Animal引用变量转换为Dog对象。

需要注意的是,向下转型可能会出现运行时异常,因为在向上转型后,父类引用变量只能访问父类的方法和属性,无法访问子类特有的方法和属性。如果在向下转型时将父类引用变量转换为错误的子类类型,会导致ClassCastException异常的抛出。因此,在向下转型前,最好使用instanceof运算符进行类型检查,以确保转型的安全性。

                                           

2.3多态的优缺点

多态的优点:

  1. 代码灵活性:多态允许使用父类引用变量来引用子类对象,这样可以实现更灵活的代码编写。通过多态,可以在不改变代码结构的情况下,通过改变对象的具体类型来修改程序的行为。

  2. 可扩展性:通过多态,可以轻松地扩展已有的代码。例如,可以通过添加新的子类来扩展已有的父类,而不需要修改原有的代码。

  3. 代码复用:多态可以在不重写代码的情况下,直接复用已有的父类代码。这样可以提高代码的可维护性和复用性。

多态的缺点:

  1. 性能损失:多态在运行时需要进行类型检查和动态绑定,这会引入一定的性能损失。相比于直接调用对象的方法,通过多态来调用方法会稍微慢一些。

  2. 可读性降低:多态会增加代码的复杂性,使得代码更难阅读和理解。当多个不同的子类实现了相同的方法时,需要通过查看父类的定义来确定具体会执行哪个子类的方法。

需要注意的是,多态并不适用于所有的情况。当需要调用特定子类的特定方法时,就不能使用多态。在这种情况下,可以通过向下转型来获得子类的引用,然后调用子类的方法。

2.4避免在构造方法中调用重写的方法

class B {
    public B() {
        // do nothing
        func();
    }
    public void func() {
        System.out.println("B.func()");
    }
}
class D extends B {
    private int num = 1;
    @Override
    public void func() {
    System.out.println("D.func() " + num);
    }
}
public class Test {
    public static void main(String[] args) {
    D d = new D();
    }
}
// 执行结果:D.func() 0
  • 构造 D 对象的同时, 会调用 B 的构造方法。
  • B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func。
  • 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1。
  • 所以在构造函数内,尽量避免使用实例方法,除了finalprivate方法。

在构造方法中调用重写的方法可能会导致不可预知的行为。这是因为在对象构造期间,子类的构造方法会在父类的构造方法执行之前执行。因此,如果在父类的构造方法中调用了一个被子类重写的方法,那么实际调用的将是子类重写的方法。

为了避免这种情况,应该尽量避免在构造方法中调用重写的方法。如果确实需要在构造方法中执行某些逻辑,可以考虑将这些逻辑提取为一个独立的方法,在构造方法执行完毕后再调用该方法。这样可以保证对象的正确构造和初始化。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哞哞不熬夜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值