JAVA 继承和多态

继承

继承用来对类进行共性抽取,实现代码复用。

继承的语法

在JAVA中如果要表示类之间的继承关系,要借助extends关键字:

修饰符 class 子类 extends 父类{
……
}
对于狗、猫,他们都属于动物,那么就可以把动物这个类当做父类,狗和猫就属于子类。

在这里插入图片描述

//Animal.java
public class Animal {
    String name;
    int age;

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

    public void sleep(){
        System.out.println(name+"正在睡觉");
    }
}
//Cat.java
public class Cat extends Animal{
    void mew(){
        System.out.println(name+"喵喵喵");
    }
}

//Dog.java
public class Dog extends Animal{
    void bark(){
        System.out.println(name+"汪汪汪");
    }
}
//TestExtend
public class TestExtend {
    public static void main(String[] args){
        Dog dog = new Dog();
        //dog类中没有定义任何成员变量,name和age属性肯定是从父类Animal中继承下来的
        System.out.println(dog.name);
        System.out.println(dog.age);

        //eat()和sleep()方法也是从Animal中继承下来的
        dog.eat();
        dog.sleep();
        dog.bark();
    }
}

注意:
1.子类会将父类中的成员变量或者成员方法继承到子类中
2.子类继承父类后,必须要添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了

父类成员访问

子类访问父类成员变量

成员变量访问遵循就近原则,自己有,则优先访问自己的,如果没有,向父类中寻找。

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

//Base.java
public class Base {
    int a;
    int b;
}
//Derived.java
public class Derived extends Base{
    int c;
    public void method(){
        a=10;//访问从父类中继承下来的a
        b=20;//访问从父类中继承下来的b
        c=30;//访问子类自己的c
    }
}

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

//Base.java
public class Base {
    int a;
    int b;
    double c;
}
//Derived.java
public class Derived extends Base{
    int c;
    public void method(){
        a=10;//访问从父类中继承下来的a
        b=20;//访问从父类中继承下来的b
        c=8.999;//访问子类自己的c
    }
}

此时会发生报错,因为子类和父类中都有成员c,那么优先访问子类中的c,子类中的c是int类,那么不能赋值为double类。
在这里插入图片描述

子类访问父类成员方法

//Base.java
public class Base {
    public void methodA(){
        System.out.println("Base中的methodA()");
    }

    public void methodB(){
        System.out.println("Base中的methodB()");
    }
}
//Derived.java
public class Derived extends Base{
    public void methodA(int a){
        System.out.println("Derived中的method(int)方法");
    }

    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }

    public void methodC(){
        methodA();
        methodA(33);
        methodB();
    }
}
//TestDerived.java
public class TestDerived {
    public static void main(String[] args){
        Derived d = new Derived();
        d.methodC();
    }
}

在这里插入图片描述通过子类对象访问父类与子类同名方法时,如果子类和父类同名方法的参数列表不同(重载),根据调用方法传递的参数选择合适的方法访问。

super关键字

如果子类和父类中有相同的成员变量和成员方法(参数列表也一样),如果要访问父类中的成员,则用super关键字即可。

我们仅将上述案例中methodC代码略作修改:
在这里插入图片描述调用TestDerived:
在这里插入图片描述子类和父类中methodB方法同名且参数一致,用super.methodB()则可以访问到父类中的methodB方法。

注意:super只能在非静态方法中使用

子类构造方法

子类对象构造时,需要先调用父类构造方法,然后执行子类的构造方法。

//Base.java
public class Base {
   public Base(){
       System.out.println("Base()");
   }
}
//Derived.java
public class Derived extends Base{
    public Derived(){
        System.out.println("Derived()");
    }
}
//TestDerived.java
public class TestDerived {
    public static void main(String[] args){
        Derived d = new Derived();
    }
}

在这里插入图片描述

注意

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

super和this

两者相同点:

1.都是Java中的关键字
2.只能在类的静态方法中使用,用来访问非静态成员方法和字段
3.在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

不同点:

1.this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
2.在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
3.在构造方法中,this(……)用于调用本类构造方法,super(……)用于调用父类构造方法,两种调用不能同时在构造方法中出现
4.构造方法中一定会存在super(……)的调用,用户没有写,编译器也会增加,但是this(……)用户不写则没有

再谈初始化

在没有继承关系时,实例代码块和静态代码块的执行顺序是怎样的呢?

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("张三",15);
        System.out.println("==========");
        Person person2 = new Person("李四",20);
    }
}

在这里插入图片描述1.静态代码块先执行,并且只执行一次,在类加载阶段进行
2.当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行

当有继承关系的执行顺序

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

在这里插入图片描述我们通过打印顺序得出以下结论:

1.父类静态代码块优先于子类静态代码块执行,且是最早执行
2.父类实例代码块和父类构造方法紧接着执行
3.子类的实例代码块和子类构造方法紧接着再执行
4.第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

protected关键字

Java中的访问限定符主要限定:类或者类中成员能否在类外或者其他包中被访问。
在这里插入图片描述
我们接下来在具体代码中测试一下:

package extend01;

public class B {
    private int a;
    protected int b;
    public int c;
    int d;
}
package extend01;

public class D extends B{
    public void method(){
        super.a=1;//报错,父类private成员在同一包中的子类中不可见
        super.b=20;//父类中的protected成员在相同包的子类中可见
        super.c=30;//父类中的public成员在相同包中的子类中可见
        super.d=40;//父类中的default成员在相同包中的子类中可见
    }
}
package extend02;
import extend01.B;

public class C extends B{
    public void method(){
        super.a=10;//报错,父类中的private在不同包中的子类中不可见
        super.b=20;//父类中的protected成员在不同包中的子类中可见
        super.c=30;//父类中的public成员在不同包中的子类中可见
        super.d=40//报错,父类中的default成员在不同包中的子类中不可见
    }
    
}
package extend02;
import extend01.B;

public class E{
    public void method(){
        B b = new B();
        b.a=10;
        b.b=20;
        b.c=30;//只有public成员才能在不同包中的非子类中被访问
        b.d=40;
    }
}

注意

1.父类private成员变量虽然在子类中不能直接访问,但是也继承到子类中了
2.尽可能做到“封装”,能用private尽量不用public

继承方式

Java中支持的继承方式有:

1.单继承,父类仅有一个子类,子类仅有这一个父类
2.多层继承,A继承B,C继承A
3.不同类继承同一个类,A和B都继承C

Java中不支持一个类继承多个类。

final关键字

1.修饰变量或字段,表示常量,不能被修改
final int a=10;
2.修饰类,则此类不可被继承
final public class Animal{
……}
3.修饰方法,则不可被重写

组合

继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系,比如:汽车有引擎、轮胎、散热器……

例如,已有Tire、Engine类,则Car类可以加入如下成员:
Tire tire;
Engine engine;

多态

多态即,去完成某个行为,当不同对象去完成时会产生不同状态

在Java中实现多态,必须要满足如下几个条件,缺一不可:
1.必须在继承体系下
2.子类必须要对父类中方法进行重写
3.通过父类的引用调用重写的方法

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

例如:
Animal.java

public class Animal {
    String name;
    int age;

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

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

Cat.java

public class Cat extends Animal{
    public Cat(String name, int age) {
        super(name, age);
    }

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

}

Dog.java

public class Dog extends Animal{
    public Dog(String name, int age) {
        super(name, age);
    }

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

Test.java

public class TestAnimal {
    public static void eat(Animal a){
        a.eat();
    }

    public static void main(String[] args) {
        Cat cat=new Cat("咪咪",5);
        Dog dog=new Dog("旺旺",6);

        eat(cat);
        eat(dog);
    }
}

TestAnimal的运行结果为:
在这里插入图片描述
上述代码中,分割线以上的代码是类的实现者编写的,分割线下方的代码是类的调用者编写的

当类的调用者在编写eat这个方法的时候,参数类型为Animal(父类),此时在该方法内部并不知道,也不关注当前的a引用指向的是哪个类型(哪个子类)的实例。此时a这个引用调用eat方法可能会有多种不同的表现,这种行为就称为多态。

重写(override)

也称为覆盖。重写是子类对父类非静态、非private修饰、非final修饰,非构造方法等的实现过程进行重新编写,返回值和形参都不能改变即外壳不变,核心重写

【方法重写的规则:】

1.子类在重写父类的方法时,一般必须与父类方法原型一致:返回值类型 方法名 参数列表要完全一致
2.被重写的方法返回值类型可以不同,但是必须是具有父子关系的
3.访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为protected
4.父类被static、private、final(密封方法)修饰的方法、构造方法都不能被重写
5.重写的方法,可以使用@Override注解来显式指定,有了这个注解能帮我们进行一些合法性校验,比如不小心将方法名字拼错了(eat写成了aet),那么此时编译器就会发现父类中没有aet方法,就会编译报错,提示无法构成重写。

【重写和重载的区别:】
在这里插入图片描述静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用哪个方法。典型代表函数重载

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

向上转型和向下转型

向上转型

向上转型实际就是创建一个子类对象,将其当成父类对象来使用

语法格式:父类类型 对象名 = new 子类类型()
例如:

Animal animal = new Cat(“喵喵”,15);

animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。

【使用场景】

1.直接赋值
2.方法传参
3.方法返回

以下是具体使用案例:

public class TestAnimal {
    //方法传参:形参为父类型引用,可以接收任意子类的对象
    public static void eat(Animal a){
        a.eat();
    }

    //作返回值:返回任意子类对象
    public static Animal buyAnimal(String var){
        if("狗".equals(var)){
            return new Dog("狗狗",1);
        }else if("猫".equals(var)){
            return new Cat("猫猫",3);
        }else{
            return null;
        }
    }
    public static void main(String[] args) {
        Animal cat= new Cat("mimi",4);//直接赋值:子类对象赋值给父类对象
        Dog dog = new Dog("gougou",5);

        eat(cat);
        eat(dog);

        Animal animal=buyAnimal("狗");
        animal.eat();

        animal=buyAnimal("猫");
        animal.eat();
    }
}

在这里插入图片描述
向上转型的优点:让代码实现更简单灵活
向上转型的缺点:不能调用到子类特有的方法

public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("元宝",5);
        Dog dog= new Dog("旺财",6);

        //向上转型
        Animal animal = cat;
        animal.eat();
        animal=dog;
        animal.eat();

        //animal.bark();
        //编译失败,编译时编译器将animal当成Animal对象处理
        //而Animal类中没有bark方法,因此编译失败

        //cat=(Cat)animal;
        //cat.mew();
        //ClassCastExceptionclass: ani.Dog cannot be cast to class ani.Cat

        dog=(Dog)animal;
        dog.bark();
        //animal本来指向的就是狗,因此将animal还原为狗也是安全的
    }
}

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常,Java中为了提高向下转型的安全性,引入了instanceof,如果该表达式为true,则可以安全转换。

public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("元宝",5);
        Dog dog= new Dog("旺财",6);

        //向上转型
        Animal animal = cat;
        animal.eat();
//        animal=dog;
//        animal.eat();

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

        if(animal instanceof Dog){
            dog=(Dog)animal;
            dog.bark();
        }
    }
}

在这里插入图片描述

多态的优缺点

使用多态的好处:

  • 1.降低代码的“圈复杂度”,避免使用大量的“if else”语句

例如,现在要打印多个形状,如果不基于多态,实现代码如下:

class Shape{
    public void draw(){
        System.out.println("画图");
    }
}

class Rect extends Shape{
    @Override
    public void draw(){
        System.out.println("♦");
    }
}

class Cycle extends Shape{
    @Override
    public void draw(){
        System.out.println("●");
    }
}

class Flower extends Shape{
    @Override
    public void draw(){
        System.out.println("❀");
    }
}
public class DrawShape {
    public static void main(String[] args) {
        Rect rect =new Rect();
        Cycle cycle = new Cycle();
        Flower flower=new Flower();
        String[] shapes ={"cycle","rect","cycle","rect","flower"};

        for (String shape:shapes) {
            if(shape.equals("cycle")){
                cycle.draw();
            }else if(shape.equals("rect")){
                rect.draw();
            }else if(shape.equals("flower")){
                flower.draw();
            }
        }
    }
}

如果使用多态,则不必写这么多if-else语句:

public class DrawShape {
    public static void main(String[] args) {
        Shape[] shapes ={new Cycle(),new Rect(),new Cycle(),new Rect(),new Flower()};

        for (Shape shape:shapes) {
            shape.draw();
        }
    }
}
  • 2.可扩展能力更强
    如果要增加一种新的形状,使用多态的方式。改动代码的成本也更低
class Triangle extends Shape{
    @Override
    public void draw(){
        System.out.println("△");
    }
}

对于类的调用者来说(DrawShape方法),只需要创建一个新的类就可以了,而不用多态的话,就要把DrawShape中的if-else进行修改,改动成本高。

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

下面这段代码略有小坑:

class B{
    public B(){
        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的同时,会调用B的方法。
  • B的构造方法中调用了func方法,此时会除法动态绑定,会调用到D中的func。
  • 此时D对象自身还没有构造,此时num处在未初始化的状态,值为0。
  • 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。
class B{
    public B(){
        func();
    }

    private void func(){
        System.out.println("B.func()");
    }
}

class D extends B{
    private int num=1;

    private void func(){
        System.out.println("D.func()"+num);
    }
}
public class Test {
    public static void main(String[] args) {
        D d = new D();
    }
}


在这里插入图片描述
但注意,此时B和D类中的func方法就不是重写的关系了,而是重载关系。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值