Java中的继承、多态和接口

1、用类比引入继承概念

众所周知,Java是一门面向对象的语言。如果我们要设计多种多样的交通工具,比如汽车、火车、飞机,虽然这些工具功能不同、形态不同,但是他们很多的基本功能相同,比如——载人,行驶,是否能飞...,于是我们把class Vehicle(交通工具类)当作设计交通工具的标准,再通过这个标准创建不同的交通工具,包括汽车、火车、飞机等等(class Car,class Train,class Air...),这些工具的共有属性都是继承设计标准(class Vehicle)中的属性。所谓继承,就是将Vehicle类中的部分属性和功能改变,但是大体框架相同,构成新的汽车类(class Car)、火车类(class Train)、飞机类(class Air)。

2、用问题深入什么是继承?

现在有一个交通工具制造厂; 制造场可以制造汽车、火车、飞机三类交通工具; 制造场可以展示三类交通工具的信息; 汽车:name,capacity,length,tyreNum(型号,载客量,长度,轮胎的个数) 火车:name,capacity,length,haveTrack(型号,载客量,长度,是否有轨道) 飞机:name,capacity,length,haveWing(型号,载客量,长度。是否有机翼)

3、用代码演示继承的作用

  1. 首先,观察问题,汽车、火车、飞机都是交通工具,它们的信息都有name,capacity,length,所以我们构建一个class Vehicle(交通工具类),Vehicle类中包含它们三者共有的信息。并且最后都是要输出信息的,所以我们在Vehicle类中也构建一个showInfo的方法
public class Vehicle {
    protected String name;
    protected int capacity;
    protected int length;

    public Vehicle(String name , int capacity , int length){
        this.name = name;
        this.capacity = capacity;
        this.length = length;
    }

    public void showInfo(){
        System.out.print(name+"的最大载客量是:"+capacity+",长度是:"+length+"米");
    }
}
  1. Vehicle类构建完了,接下来构建一个class Car(汽车类)。在这个Car类中,重点来了,我们用extends关键字把Car作为Vehicle类的子类。然后,我们再在Car类中给一个tyreNum整形变量,代表汽车轮胎个数。然后给Car类一个构造器,包括name,capacity,length,tyreNum。重点来了,用super关键字,将形参name,capacity,length,给定到父类Vehicle中,而Car类专属的tyreNum就在本类用。再给定一个showInfo方法,用来展示汽车的信息。重点来了,在showInfo方法中,用super关键字调用Vehicle中的showInfo方法。
public class Car extends Vehicle //extends关键字,表示Car类继承Vehicle类,即Car是Vehicle的子类
	{
    protected int tyreNum;
    public  Car(String name , int capacity , int length , int tyreNum){
        super(name, capacity, length);//super关键字,将形参传给父类
        this.tyreNum=tyreNum;
    }

    @Override
    public void showInfo(){
        super.showInfo();//super关键字,调用父类Vehicle中的showInfo方法
        System.out.println(",轮胎数量是:"+tyreNum);
    }
}
  1. 在Car类构建完毕之后,同理,构建出Train类和Air类,分别由自己的专属实例变量,haveTrack和haveWing。代码如下:
public class Train extends Vehicle{
   protected boolean haveTrack;
   public Train(String name , int capacity , int length , boolean haveTrack){
       super(name, capacity, length);
       this.haveTrack=haveTrack;
   }

   @Override
   public void showInfo(){
       super.showInfo();
       System.out.println(",是否有轨道:"+haveTrack);
   }
}
public class Air extends Vehicle{
    protected boolean haveWing;
    public Air(String name , int capacity , int length , boolean haveWing){
        super(name, capacity, length);
        this.haveWing=haveWing;
    }

    @Override
    public void showInfo(){
        super.showInfo();
        System.out.println(",是否有机翼:"+haveWing);
    }
}
  1. 在完成以上构建类的步骤之后,我们创建一个交通工具制造厂(class Database),用来进行制造交通工具,并输出交通工具的信息。我们用ArrayLise的方式new一个容器vehicles。构建一个增加交通工具的add方法,目的是将交通工具放入vehicles容器中。再建立一个输出交通工具信息的list方法,用来展现交通工具的信息。
import java.util.ArrayList;

public class Database {
    ArrayList<Vehicle>vehicles=new ArrayList<Vehicle>();

    public void add(Vehicle vehicle){
        vehicles.add(vehicle);
    }

    public void list(){
        for(Vehicle v:vehicles){
            v.showInfo();
        }
    }
}
  1. 在完成上述四步之后,我们可以进入测试环节,创造一个测试类用于测试,代码如下:
public class Test {
   public static void main(String[] args) {
       Database db=new Database();
       db.add(new Car("宝马",5,3,4));
       db.add(new Train("和谐号",1800,500,true));
       db.add(new Air("客机",500,28,true));
       db.list();
   }
}
  1. 运行代码,结果如下: 我们可以看到,成功运行,并且把信息成功输出!

4、继承的更多和多态的概念

在上述例子中,我们可以看到继承的基本操作,那么与继承关系紧密的另一个概念是什么呢?那就是多态了。面向对象的三个基本特征就是:封装,继承,多态。

  1. 封装最好理解了。 封装是面向对象的特征之一,是对象和类概念的主要特性。封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

  2. 继承的基本概念在上面我已经演示过了,现在详细说说。 继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”,被继承的类称为“父类”。在Java语言中,默认继承,Object父类。继承的过程,就是从一般到特殊的过程。

  3. 接下来到了新概念——多态。 多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。 实现多态,有二种方式,覆盖,重载。 覆盖,是指子类重新定义父类的虚函数的做法。 重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

继承的机制,引用学长上培训课给我们的总结就是:

  • 子类调用方法时优先调用自己的,如果自己没有在调用父类的;
  • 子类构造对象时,需要用到super();方法向父类传递对应的参数,并且符合一下顺序:父类构造->子类的定义初始化->构造器初始化
  • 如果不调用super,构造器会自动在父类中寻找不带任何参数的构造器
  • 要在子类的方法中调用父类的方法,需要用到super关键字

多态的机制,引用学长上培训课给我们的总结就是:

  • 子类的对象可以被当作父类的对象来使用
    • 赋值给父类的变量
    • 传递给需要父类对象的函数
    • 放进存放父类对象的容器里
  • Java的所有对象变量都是多态的,它能保存不止一种类型的对象
  • 它们可以保存的是声明类型的对象(静态类型),也可以是声明类型的子类的对象(动态类型)
  • 当我们把一个子类的对象交给父类的变量管理时就发生了向上造型;

那何为造型Cast?

  • 把一个类的实例对象赋值给另外一个类的变量的过程叫做造型
    • 父类的变量可以管理子类的对象(向上造型)
    • 子类的变量不能管理父类的对象
      Vehicle v=new Vehicle();
      Car c=new Car();
      v=c;	//可以
      c=v;	//编译错误!会爆红
      
      除非用向下造型,c=(Car)v; 但是前提是v管理的的确是一个Car的对象
  • 向上造型总是安全的,是默认的,不需要加括号
  • 向下造型是强制的,是不安全的,可能会发生异常
  • 造型不是类型转换

5、由继承引入抽象类和抽象方法(abstract)

思考下面一个问题:

  • 现在需要做三个类
  • 矩形,三角形,圆形
  • 每个类里面都需要有计算周长的方法getPerimeter(); 和计算面积的方法getArea();

是不是想到了些什么?这三个类如果向上抽取出共同特点,那就是,都有面积,都有周长。而且每个类都要有计算面积、计算周长的方法。那么我们如何让这三个类建立联系呢?其实可以通过建立抽象类和抽象方法实现

  1. 我们首先创建矩形类(class Rectangle),在矩形类中创建构造器和计算周长的方法getPerimeter();和计算面积的方法getArea()。
public class Rectangle extends Shape{
    protected double length;
    protected double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public double getPerimeter(){
         return (length+width)*2;
    }

    @Override
    public double getArea(){
         return length*width;
    }
}
  1. 同理,我们随之建立三角形类(class Triangle)和圆类( class Circular),在类中创建构造器和计算周长的方法getPerimeter();和计算面积的方法getArea()。
public class Triangle extends Shape{
    protected double sideOne;
    protected double sideTwo;
    protected double sideThree;

    public Triangle(double sideOne, double sideTwo, double sideThree) {
        this.sideOne = sideOne;
        this.sideTwo = sideTwo;
        this.sideThree = sideThree;
    }

    @Override
    public double getPerimeter(){
        return  sideOne+sideTwo+sideThree;
    }

    @Override
    public double getArea(){
        double p=(sideOne+sideTwo+sideThree)/2.0;
        return Math.sqrt(p*(p-sideOne)*(p-sideTwo)*(p-sideThree));
    }
}
public class Circular extends Shape{
    double R;

    public Circular(double r) {
        R = r;
    }

    @Override
    public double getPerimeter(){
        return 2*R*Math.PI;
    }

    @Override
    public double getArea(){
        return Math.pow((R*Math.PI),2);
    }
}
  1. 你会发现上面三段代码的共同点 extends Shape,那么这个class Shape是何方神圣呢?
public abstract class Shape {
    public abstract double getPerimeter();
    public abstract double getArea();
}
  1. 非常简短的代码,只不过多了一个关键字abstract。这个关键字是抽象的意思,在abstract的类中,不能有具体的方法。我们上面的三种形状都是Shape的子类。那么我们创造一个test类来测试一下!
public class test {
    public static void main(String[] args) {
        Shape R=new Rectangle(10,15);
        Shape T=new Triangle(3,4,5);
        Shape C=new Circular(5);

        System.out.println(R.getArea());
        System.out.println(R.getPerimeter());

        System.out.println(T.getArea());
        System.out.println(T.getPerimeter());

        System.out.println(C.getArea());
        System.out.println(C.getPerimeter());

    }
}

这里要注意的问题是,抽象类不能实例化!

Shape shape=new Shape();
//这种情况是不允许存在的,因为抽象的类不能实例化!!!

以上的例子就是由继承引出抽象类和抽象方法,各位可以细品。

6、抽象的接口

在上面了解了抽象之后,我们引入接口的概念:

  • 接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
  • 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
  • 除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
  • 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

接口在IDE中有自己的创建(我这里用idea举例)

为了更了解接口,下面由一个问题引入:

  • 现在有一个手机类Phone,和人类Person
  • 两者都有发出声音的方法Sound();
  • 我们想把这个公共的方法提取出来但是又不违和;

如何构建呢?

  1. 我们建立人类和手机类
public class Person implements Soundable {
    @Override
    public void sound(){
        System.out.println("你好");
    }
}
public class Phone implements Soundable{
    @Override
    public void sound(){
        System.out.println("叮铃铃");
    }
}
  1. 建立Soundable接口
public interface Soundable {
    public void sound();
}
  1. 创建测试类
public class test {
    public static void main(String[] args) {
        Soundable person=new Person();
        Soundable phone=new Phone();
        person.sound();
        phone.sound();
    }
}

结果如下:

此外,又因为接口本身就是抽象的,所以下面两个没区别(有无abstract)

public abstract interface Soundable {
    public abstract void sound();
}
public interface Soundable {
    public void sound();
}

还有,当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。

7、接口的继承

一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。

接着举个例子吧,下面的Sports接口被Hockey和Football接口继承:

public interface Sports
{
   public void setHomeTeam(String name);
   public void setVisitingTeam(String name);
}
public interface Football extends Sports
{
  public void homeTeamScored(int points);
  public void visitingTeamScored(int points);
  public void endOfQuarter(int quarter);
}
public interface Hockey extends Sports
{
  public void homeGoalScored();
  public void visitingGoalScored();
  public void endOfPeriod(int period);
  public void overtimePeriod(int ot);
}

Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。相似的,实现Football接口的类需要实现五个方法,其中两个来自于Sports接口。

此外,接口可以多继承,在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:

public interface Hockey extends Sports, Event

以上的程序片段是合法定义的子接口,与类不同的是,接口允许多继承,而 Sports及 Event 可能定义或是继承相同的方法。

8、接口的总结

这些篇幅就直接引用学长上课时候的总结吧:

接口的一些机制

  • 接口里的所有方法都是抽象方法,即使前面没有加abstract声明,系统会自动默认
  • 接口里的所有变量都是静态变量
  • 接口可以继承接口,但不能继承类
  • 接口不能有实体
  • 一个类可以实现多个接口,实现 用关键字 implements

接口存在的意义

  • 将某个功能抽象出来,提高可扩展性
  • 是Java成功的关键之一,因为极适合多人写一个大程序
  • 也是Java被批评的要点之一,因为容易造成代码膨胀

继承类(不管是实体类还是抽象类)侧重于规则和概念 实现接口侧重于功能(很多able结尾的接口,如Cloneable,Readable,Runable)

9、总结与感想

  • 其实这篇博文,很多概念我都是在写的时候更进一步的弄清楚的,继承、多态、接口,许许多多的知识在查阅的时候才会更多的灌输到脑子里。当然,敲代码举例子也是很不错的过程。如果对我这篇文章很多概念还是没有弄清楚的,可以参考 Java继承Java多态 还有 Java接口 ,或者是看看其他的有关博文。
  • 当然,这篇博文对我而言不仅仅是一项任务,更像是一次自我提升,在查资料、敲代码、写博文,这三步之后,我感觉我的基础框架构建出来了。古人云,授人以鱼不如授人以渔。而我发现,在授人以渔的过程中,自己的进步是真的很大,比起刷网课视频,自己实践动手给人更多的提升感觉,尽管过程很苦很累,但是一切都是值得的!写下这篇博文,更像是给自己做一份笔记,让自己了解更细致,这钟感觉针不戳~
  • 还望未来的自己再接再厉,继续前行!