继承和多态详解【java】

目录

前言

二、继承详解

2.1 继承的概念

2.2 继承的使用 

2.3 子类构造方法

2.4 父类子类中的代码块

2.5 protected关键字详解

2.6 继承的方式

2.7 final关键字详解

2.8 继承组合

三、多态

3.1 什么是多态

3.2 满足多态的条件

3.3 再次了解多态

3.4 向下转型

总结

this与super的区别


前言

学习继承和多态,可以让我们的代码更加的整洁明了。


一、为什么会出现继承?

我们所生活的世界是错综复杂的,每一个事物之间可能有着千丝万缕的联系,也有可能毫无关联,而Java的存在就是对世界中的实体进行描述的,类实例化对象,对象就可以来描述生活中的实体,都说C生物,拿Java也是一样的。例如自行车与摩托车,他们都是车。下面来定义这两个类:

class Bicycle{
    public String name;
    public int price;
    public String model;
    public void RideCar(){
        System.out.println("骑车");
    }
}
class Auto{
    public String name;
    public int price;
    public String model;
    public void RideBike(){
        System.out.println("开车");
    }
}

从上面的代码可知,自行车和汽车都有共同的成员变量,并且每一个对象都会为他们开辟空间,为了提高代码的复用性,所以出现了继承。 

二、继承详解

2.1 继承的概念

继承 (inheritance) 机制 :是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性 的基础上进行扩展,增加新功能 ,这样产生新的类,称 派生类 。继承呈现了面向对象程序设计的层次结构, 体现了 由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用(就好似自行车和汽车一样,他们都有着一些共同的特性,我们可以对共性的内容进行共性抽取,然后通过继承的思想来实现。)

2.2 继承的使用 

class Car{
    public String name;
    public int price;
    public String model;
    public double weight;
    public int a = 6;
    public void func(){
        System.out.println("父类的方法");
    }
}
class Bicycle extends Car{
    public double weight;
    public int a = 3;
    public void RideCar(){
        System.out.println("骑车");
        System.out.println("访问相同名的成员变量:"+super.a);
        super.func();//父类的方法
        func();//子类的方法
    }
    public void func(){
        System.out.println("子类的方法");
    }

    @Override//前面类和对象已解释
    public String toString() {
        return "Bicycle{" +
                "name='" + name + '\'' +
                ", price=" + price +
                ", model='" + model + '\'' +
                ", weight=" + weight +
                '}';
    }
}
class Auto extends Car{
    public String colour;
    public void RideBike(){
        System.out.println("开车");
    }
}
public class Test {
    public static void main(String[] args) {
        Bicycle bicycle = new Bicycle();
        bicycle.name = "自行车";
        bicycle.weight = 36.3;
        System.out.println(bicycle);//打印Bicycle{name='自行车', price=0, model='null', weight=36.3}
        System.out.println(bicycle.name);
    }
}

 下面我们根据上面的代码可以提出相应的问题

(1)如何继承?

Java中通过extends来实现继承。

(2)继承后,子类有什么变化

子类会将父类中的成员变量或者成员方法继承到子类中了,子类相当于扩容,多了成员变量和方法

class Car{
    public String name;
    public int price;
    public Car(String name, int price) {
        this.name = name;
        this.price = price;
    }
}
class Bicycle extends Car{
    public double weight;
    /*public Bicycle(){//默认的,父类没有自定义构造函数时
        super();
    }*/
    public Bicycle(String name, int price, double weight) {
        super(name, price);//调用父类的构造函数,来初始化子类从父类继承过来的属性,只能在第一行
        this.weight = weight;
    }
}

(3)如何访问子类和父类的成员变量和方法?

子类继承父类后,通过子类实例化对象,根据对象的引用来访问。访问时根据就近原则,先访问子类,再去访问父类的,如果子类和父类都没有方法或变量时,代码会出错。

(4)父类与子类出现相同的方法名或变量时,又是怎么访问父类的变量和方法?

子类与父类同时出现相同类名的变量或方法时,子类的访问就是正常的对象的引用,访问父类则需要super关键字,super.变量、super.方法。

(5)什么是super?

super可以理解为从父类继承过来的父类那一块成员的地址,可以说super里面存了父类成员的地址;Java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员

2.3 子类构造方法

什么是子类构造方法,与类一样,编译器都会默认的提供构造方法,但是呢,当我们自定义构造方法的时候,编译器就不会提供了。子类继承父类的时候,是将它的成员继承过来,对应着构造方法也会继承过来。子类对象构造完成之前(就是代码块{}走完),是先要帮助父类的成员进行初始化。如下代码:

class Car{
    public String name;
    public int price;
    public Car(String name, int price) {
        this.name = name;
        this.price = price;
    }
}
class Bicycle extends Car{
    public double weight;
    /*public Bicycle(){//默认的,父类没有自定义构造函数时
        super();
    }*/
    public Bicycle(String name, int price, double weight) {
        super(name, price);//调用父类的构造函数,来初始化子类从父类继承过来的属性,只能在第一行
        this.weight = weight;
    }
}
在子类构造方法中,并没有写任何关于父类构造的代码,但是在构造子类对象时,先执行父类的构造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,父类继承下来的以及子类新增加的部分 。父子父子, 肯定是先有父再有子,所以在构造子类对象时候 ,先要调用父类的构造方法,将从父类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。

2.4 父类子类中的代码块

class Car{
    public Car() {
        System.out.println("父类的构造代码块");
    }
    {
        System.out.println("父类的实例代码块");
    }
    static{
        System.out.println("父类的静态代码快");
    }
}
class Bicycle extends Car{
    public Bicycle() {
        System.out.println("子类的构造方法");
        System.out.println("==============");
    }
    {
        System.out.println("子类的实例代码块");
    }
    static{
        System.out.println("子类的静态代码块");
    }
}
public class Test {
    public static void main(String[] args) {
        Bicycle bicycle = new Bicycle();
        Bicycle bicycle1 = new Bicycle();
    }
}

//打印结果
父类的静态代码快
子类的静态代码块
父类的实例代码块
父类的构造代码块
子类的实例代码块
子类的构造方法
==============
父类的实例代码块
父类的构造代码块
子类的实例代码块
子类的构造方法

Process finished with exit code 0

由上面的代码可以知道继承的类也是遵循代码块的执行顺序。(类和对象有详解),总的一句话,有父及子,静态先行

2.5 protected关键字详解

//同一个包下不同的类访问
class Student{
   protected int age;
   public void func(){
      System.out.println(age);
   }
}
public class Test {
   public static void main(String[] args) {
      Student student = new Student();
      student.func();//打印默认值0
   }
}

//同一个类访问
public class Test {
   protected int age;
   public static void main(String[] args) {
      Test test = new Test();
      System.out.println(test.age);//打印0
   }
}

//不同包的子类
//inherit包下的子类
public class Test {
   protected int age;
   public static void main(String[] args) {
      Test test = new Test();
      System.out.println(test.age);//打印0
   }
}
//demo包下的子类
public class Test extends inherit.Test {
    public void func(){
        System.out.println(super.age);//不能直接的访问,需要super才行
    }
    public static void main(String[] args) {
        //inherit.Test test = new inherit.Test();
        //test.age = 10;err
        Test test1 = new Test();
        test1.func();
    }
}
访问限定符的使用要看运用的场景。我们希望类要尽量做到 "封装", 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者。

2.6 继承的方式

分为三种,看代码:

//单继承
class Student{}
class Student1 extends Student{} 

//不同的类继承同一个类
class Student{}
class Student1 extends Student{}
class Student2 extends Student{}
//多个类继承
class Student{}
class Student1 extends Student{}
class Student2 extends Student1{}

//不能一个子类继承多个父类
class Student{}
class Student1{}
class Student2 extends Student1,Student{}
我们并不希望类之间的继承层次太复杂. 一般不超过三层的继承关系.。如果继承层次太多, 就需要考虑对代码进行重构了。如果想从语法上进行限制继承, 就可以使用 final 关键字对类进行密封,密封后这个类就不能继承了。
 

2.7 final关键字详解

final class Student{}//final修饰类后,类就会被密封,不能被继承
//class Student1 extends Student{} err 

public class Test {
   public final int a = 10;//final修饰后,变量就不可以更改了
   //a = 20; err
   
}
//final修饰方法后,这个方法不能重写了

2.8 继承组合

简单的说明:继承就是谁是谁的关系,汽水是水,开水也是水;而组合呢:就是多个细节组合起来,比如电脑由内存、显示器、鼠标平键盘...等组成 

三、多态

3.1 什么是多态

什么是多态呢?实例化出来的对象去完成某一件事(行为)的时候,不同的对象会产生不同的结果。下面的动物就很好的诠释了多态,兔子和老虎都是动物,但是呢吃的东西是完全不一样的。

3.2 满足多态的条件

简单的说有三个条件:

🚀发生向上转型

🚀重写父类的方法

🚀通过父类的引用调用子类中重写父类的方法(动态绑定,方法的传参,返回值)

class Animal{
   public String name;
   public int age;

   public Animal(String name, int age) {
      this.name = name;
      this.age = age;
   }
   public void eat(){//public Animal eat() 父子类关系
      System.out.println(name+"吃东西");
   }
}
class Tiger extends Animal{
   public Tiger(String name, int age) {
      super(name, age);
   }
   public void running(){
      System.out.println(name+"正在飞快的跑");
   }
   @Override
   public void eat() {//public Tiger eat() 父子类关系
      System.out.println(name+"吃肉");
   }
}
public class Test {
   public static void func(Animal animal){}
   public static Animal func1(){
      return new Tiger("老虎",6);
   }
   public static void main(String[] args) {
      //Animal animal = new Tiger("老虎",6);
      Tiger tiger = new Tiger("老虎",6);
      Animal animal = tiger;//父类的引用 引用了 子类引用的对象
      animal.eat();//发生了动态绑定,没有重写eat()方法的时候会打印老虎吃东西
      //animal.running();//不能调用了,只能调用父类自己独有的方法
      animal.eat();//重写了父类的eat();这时调用就会以子类的方法为主,打印老虎吃肉
      func(tiger);//方法的传参-动态绑定
      func1();//返回值-动态绑定
   }
}

根据上面的代码,我们可以提出以下问题:

1.什么是向上转型,转型后有什么影响?何为动态绑定

向上转型:Animal animal = new Tiger("老虎",6);就是将子类的对象赋值父类的引用;发生向上转型后,父类的引用(animal)只能调用自己独有的方法了,不能调用子类的,是因为发生了动态绑定;动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法,编译的时候还是调用父类,但是执行的时候就会发生动态绑定。(静态绑定,在编译的时候就已经确定了,重载)

2.如果子类重写了父类的方法后会发生什么呢?什么又是重写?

重写:就是在子类中重写父类的方法,重写的规则有:返回值相同(父子类的关系也可以,协变类型) ;参数的类型、个数,顺序相同 ;方法名相同(上面就重写了eat()方法);static和private修饰的方法不能被重写(子类的访问限定符要大于等于父类的访问限定符);重写后父类的引用就可以调用子类的重写父类后的方法了。如果你不想这个方法重写的话,加上final就行(密封)。

3.3 再次了解多态

class Animal{
   public String name;
   public int age;
   public Animal(String name, int age) {
      this.name = name;
      this.age = age;
      //eat();也可以发生动态绑定,引用子类的对象,super调用父类的构造函数,打印子类的eat();
   }
   public void eat(){
      System.out.println(name+"吃东西");
   }
}
class Tiger extends Animal{
   public Tiger(String name, int age) {
      super(name, age);
   }
   public void running(){
      System.out.println(name+"正在飞快的跑");
   }

   @Override
   public void eat() {
      System.out.println(name+"吃肉");
   }
}
class Rabbit extends Animal{
   public Rabbit(String name, int age) {
      super(name, age);
   }
   @Override
   public void eat() {
      System.out.println(name+"吃草");
   }
}
public class Test {
   public static void func(Animal animal){
      animal.eat();//打印老虎吃肉
   }
   public static void func1(Animal animal){
      animal.eat();//打印兔子吃草
   }
   public static void main(String[] args) {
      func(new Tiger("老虎",6));//方法的传参-动态绑定
      func1(new Rabbit("兔子",3));
   }
}

父类的引用引用了子类的对象,通过父类的引用调用子类重写后的方法,而上面的代码就只写了一个父类的引用,父类的引用通过调用同一个重写后方法(动态绑定),出现了不同的变现形式,这种就叫做多态。一个多种对象同事去做一个行为的时候,表现会有所不同。

3.4 向下转型

🚀将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。
🚀向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。 Java 中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为 true ,则可以安全转换。
//tiger 和 rabbit这个两个类接上面的代码,这里就不重复写了
public class Test {
   public static void func(Animal animal){
      //animal是否引用了Rabbit的对象
      if(animal instanceof Rabbit){//通过instanceof来判断animal是不是Rabbit这个类的成员
         Rabbit rabbit = (Rabbit) animal;
         rabbit.eat();//会出错
      }

   }
   public static void main(String[] args) {
     /*Animal animal = new Tiger("老虎",6);
     Rabbit rabbit = (Rabbit) animal;
     rabbit.eat();//编译能通过,执行会错;因为兔子不会吃肉,只吃草*/
      func(new Rabbit("兔子",3));
   }
}

总结

this与super的区别

相同点:

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

不同点:

🚀this是当前对象的引用,super相当于是子类对象中从父类继承下来部分成员的引用

🚀在非静态成员方法中, this 用来访问本类的方法和属性, super 用来访问父类继承下来的方法和属性
🚀this 是非静态成员方法的一个隐藏参数, super 不是隐藏的参数
🚀在构造方法中: this(...) 用于调用本类带参数的构造方法, super(...) 用于调用父类构造方法,两种调用不能同时在构造方法中出现(而且两者都只能出现在第一行)
🚀构造方法中一定会存在 super(...) 的调用,用户没有写编译器也会增加,但是 this(...) 用户不写则没有

Java中的swap()函数是需要在堆上实现;不同类里面的方法(继承后)也可以形成重载

多态的优点:可以降低代码的复杂度;扩展能力强 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

霄百

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

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

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

打赏作者

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

抵扣说明:

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

余额充值