JavaSE基础语法_继承和多态


1.继承

1.1为什么需要继承

在面向对象的时候,对象之间会存在一些关联,我们在设计程序的时候需要考虑。继承就是专门用来进行共性抽取,实现代码的简化和复用
继承允许程序员在原有类(父类/基类)特性的基础上进行扩展,增加新功能从而产生新的类(子类/派生类)
在这里插入图片描述

1.2继承的语法

修饰符 class 子类 extends 父类 {
}

子类会将父类中的成员变量和成员方法继承到子类中

1.3 父类成员的访问

子类成员将父类的成员变量和成员方法继承下来后,子类中能否直接访问父类继承下来的成员呢

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

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

class Base {
    int a;
}
class Derived extends Base {
    int c;
    public void meth0d() {//JAVA所有的除定义或声明语句之外的
    //任何语句都必须在方法内部
        // (而方法又必须在某个类内部,不存在游离于类外部的方法或成员)
        a = 10;//访问从父类中继承下来的成员变量a
        c = 30;//访问子类自己的c
    }
}

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

此时如果访问的成员变量子类有同名的,则优先访问子类的成员变量。
成员变量的访问遵循就近原则,自己有则优先自己的,如果没有再向父类中查找

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

同上,通过子类对象访问父类和子类不同方法名时,优先在子类中查找,找到就访问子类自己的。没在子类中找到再去父类中寻找
通过派生类创建的对象访问父类与子类同名的方法时,如果子类和父类的同名方法参数列表不同(意思是两个同名方法构成了重载),则会根据调用方式时传递的参数选择合适的方法。
当子类和父类中存在相同的成员时,如何在子类中访问父类呢

1.4 super关键字

子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,直接访问会变成访问子类的成员。Java提供了super关键字,通过该关键字,就能在子类中访问父类的成员
在这里插入图片描述

注意:super只能在非静态方法中使用。只能在子类的方法中,去访问父类的成员变量和方法。super的其他用法在后文介绍

1.5 子类的构造方法

子类对象构造时,需要先调用父类构造方法,然后再执行子类自己的构造方法。
子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分。在构造对象的时候,会先调用父类的构造方法,将从父类继承下来的那部分成员变量初始化,然后再调用子类自己的构造方法,将子类自己新增加的成员变量初始化。

注意

  1. 若父类显示定义无参或者默认的构造方法,在子类构造方法第一行默认隐藏super()调用,即调用基类的构造方法
  2. 如果父类构造方法是带有参数的,此时需要用户自己在子类显式定义构造方法,并且在子类构造方法中选择合适的父类构造方法调用,否则会报错
  3. 在子类构造方法中,super(…)只能在子类构造方法的第一条语句(注释不算做语句,第一条不一定非要是第一行)
  4. super(…)只能在子类构造方法中出现一次,并且不能和this同时出现

1.6 super和this

上文提到了suoer和this,这两个都可以在成员方法中访问成员变量 和 调用其他成员方法,都可以作为构造方法的第一条语句,那么他们有什么异同呢

相同点

  1. 只能在类中的非静态方法中使用,用来访问非静态成员方法和成员变量
  2. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不可以同时出现

不同点
this是对当前对象的引用,当前对象就是调用实例方法的对象(静态方法不可以被调用的)。super是子类对象中从父类继承下来那一部分成员的引用
在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现
构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加(只不过有时候不可见),但是this(…)用户不写则没有

1.7 执行顺序

在继承关系上,静态代码块,实例代码块和构造方法的执行顺序又是哪样的呢
结论

  1. 父类静态代码块优先于子类的静态代码块执行,且是最高执行
  2. 父类实例代码块和父类构造方法紧接着执行
  3. 子类的实例代码块和子类构造方法紧接着再执行
  4. 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
class Father {
    String name;
    public Father(String name) {//父类的带一个参数的构造方法
        this.name = name;
        System.out.println("父类的构造方法");
    }
    {
        System.out.println("父类实例代码块");
    }
    static {
        System.out.println("父类静态代码块");
    }
}
class Son extends Father {
    public Son(String name) {//父类构造方法带参数
        //需要在子类显式定义构造方法,并且在子类构造方法中
        // 选择那个带一个参数的父类构造方法调用
        super(name);
    }
    {
        System.out.println("子类的实例代码块");
    }
    static {
        System.out.println("子类的静态代码块");
    }
}
public class Test {
    public static void main(String[] args) {
        Son son1 = new Son("张三");
        Son son2 = new Son("李四");
    }

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/8822015b8af7425c8d74a54e8047707e.p

1.8 继承方式

Java是不支持多继承的,在真实项目中类会有很多,类之间的关系也会更加复杂,即便如此一般也不希望出现三层以上的继承关系。Java中也有解决多继承问题的方法,在下一篇接口中会介绍

1.9 final关键字

final关键字可以用来修饰变量、方法以及类

  1. 修饰变量或字段,即变为常量
  2. 修饰类:表示此类无法被继承
  3. 修饰方法:表示该方法无法被重写

1.10 继承与组合

组合也是表示类与类之间关系的方式,并没有特殊语法,仅仅是将一个类的实例化对象作为另一个类的成员变量
继承表示 is-a 的关系:狗是动物
组合表示 has-a 的关系:汽车有轮胎

class Tyre {

}
class Car {
    private Tyre tyre;//组合
}

2 多态

2.1 多态的概念

同一个行为,当不同的对象去完成时会产生不同的状态

2.2 多态的实现条件

  1. 必须在继承体系下,向上转型
  2. 子类必须对父类中的方法进行重写
  3. 通过父类对象的引用调用重写的方法

完成以上三步,就会发生动态绑定,动态绑定是多态的基础

多态体现:在代码运行时,当传递不同类创建的对象时,会调用对应类中的方法

class Animal {
    String name;

    public Animal(String name) {
        this.name = name;
    }
    public void eat() {
        System.out.println(this.name+"吃饭");
    }
}
class Dog extends Animal{
    public Dog(String name) {
        super(name);//子类对象构造时,需要先调用父类构造方法
    }
    @Override
    public void eat() {//子类必须对父类中的方法进行重写
        System.out.println(this.name+"吃狗粮");
    }
}
public class Test {
    public static void eat(Animal a) {
        a.eat();//通过父类Animal对象的引用a
        //调用子类Dog中重写的方法
    }

原则上父类只能调用自己的方法,之所以表现出来是子类的方法,是因为发生了动态绑定

2.3 重写

  1. 重写是子类对父类中非static,非private,非final,非构造方法等实现过程的重新编定
  2. 子类重写父类方法时,返回值类型 方法名(参数列表)必需与父类完全一致
  3. 访问权限必须大于等于父类中被重写的方法的访问权限。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected重写的方法
  4. 可以使用 @Override 注解来显式指定. 有了这个注解编译器能帮我们进行一些合法性校验. 例如方法名写错了编译器会报错

重写与重载的区别

方法重载是一个类的多态性,方法重写则是子类与父类的一种多态性
在这里插入图片描述

两种绑定

静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用哪个方法。典型代表函数重载。

动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用哪个类的方法

2.4向上转型和向下转型

2.4.1向上转型

实际就是创建一个子类对象,将其当成父类对象来使用。让父类对象的引用指向子类对象

实现方式:

  1. 直接赋值
  2. 方法传参
  3. 方法返回
public class Test {
    //3.作返回值
    public static Animal buyAnimal(String var) {//这里返回类型是Animal
        if("狗".equals(var)) {
            return new Dog("狗狗");//返回一个子类对象,用父类接收
        }else {
            return null;
        }
    }
    
    public static void eatFood(Animal a) {//2.方法传参:用父类对象的引用去指向子类的对象
        a.eat();
    }
    
    Animal dog = new Dog("小七");//1.直接赋值
}

优缺点:
可以让代码更加接单灵活,但是向上转型后就不能调用子类特有的方法了

2.4.2向下转型

将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法

此时:将父类引用再还原为子类对象即可,即向下转换。使用强制类型转换实现

public static void main(String[] args) {
       Dog dog = new Dog("小七");
       Animal animal = dog;//向上转型
       dog = (Dog)animal;//向上转型后向下转型
       dog.bark();//向下转型后就可以通过dog去调用
                  //Dog类中特有的方法了
   }

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。
在这里插入图片描述

这里将animal强转为Cat的时候不会报错,但是运行的时候会报错,无法将原来向上转型指向Dog的animal强转成Cat类,所以说向下转型非常不安全

Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换。

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

加入instanceof之后,运行时就不会报错,强转不会执行,当然miaomiao方法也不会被调用

2.5 多态的优缺点

优点

  1. 可以降低代码的圈复杂度,避免使用大量的if-else
  2. 可扩展能力更强

拓展:什么是圈复杂度
圈复杂度(Cyclomatic Complexity)是一种代码复杂度的衡量标准,由 Thomas McCabe 于 1976年定义。它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,也可理解为覆盖所有的可能情况最少使用的测试用例数。圈复杂度大说明程序代码的判断逻辑复杂,可能质量低且难于测试和维护。程序的可能错误和高的圈复杂度有着很大关系。圈复杂度越低越好

缺陷

  1. 成员变量没有多态性,当父类和子类都由同名属性的时候,通过父类引用,只能引用父类自己的成员属性
  2. 构造方法没有多态性

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

class A {
    public A() {
        fun();
    }
    public void fun() {
        System.out.println("A.fun");
    }
}
class B extends A {
    private int num = 1;
    public void fun() {
        System.out.println(this.num+"Bfun");
    }
}
public class Test {
    public static void main(String[] args) {
        B b = new B();//运行结果为0Bfun
    }
}

这里的运行结果为什么是0Bfun:

  1. 我们在new一个B类对象的时候,因为B是A的子类,所以需要先调用A类的构造方法才行
  2. 但是在A类的构造方法中我们调用了fun函数,这个fun方法并不会用A类自己写的,那是因为此时发生了动态绑定,调用了B类中的fun函数
  3. 在B类的fun函数包含this.num,这里num为零的原因是B类还为进行初始化(因为这些是发生在父类的构造方法,子类压根还没开始构造),num的默认值为0,所以输出0Bfun

综上:避免在构造方法中调用的方法
除了:Java构造方法之间通过 this 关键字进行调用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值