对于刚学完Java面向对象的三大特性的人来说,还是会比较习惯用封装和继承两个特征来面向具体编程,而在我们后期的开发中,其实还是主要面向抽象编程,而多态,便是我们面向抽象编程的最要体现之一了!今天,我们就在多态的基础上,继续学习面向抽象编程的两个重要知识——抽象类和接口。
抽象类在讲抽象类之前,我们先回忆下我们之前的面向对象是怎么面向的:
我们把自然界的猫、狗、鸟等这些具体的对象提取共同特征,形成一个动物类。把自然界的树、草、花等这些具体的对象提取共同特征,形成一个植物类。
那对于以上的两个动物和植物,我们能不能继续提取共同特征呢?可以的,这里我们就可以引出抽象类了:
这时候,我们可以把动物类和植物类抽象成一个类或者抽象类。类的话我们之前说过,就不再讲了,如果抽象成抽象类的话我们应该要怎么抽象呢?答案是用修饰符abstract来修饰这个类。
0 1抽象类的概念什么是抽象类呢?抽象类就是把类与类之间的共同特征抽象出来形成的一个类。而就是因为抽象出来的东西不是具体的,所以抽象类是不能实例化的,也就是不能创建对象。你如果用抽象类创建对象,那么编译器是会报错的。
0 2抽象类的定义我们用以下格式定义一个抽象类(和之前的类的定义差一个abstract而已,所以也是引用数据类型):
访问权限修饰符 abstract class 类名 {}
而抽象类的类体中格式和普通的类体差不多,类体有的三大方法和三大变量它都能有,与类体的区别是有抽象方法,而抽象方法的定义为:
访问权限修饰符 abstract 返回值类型 方法名;
对于抽象方法需要知道的是抽象方法没有方法体,以分号结尾。而且抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类。
0 3抽象类的作用到这里有的读者可能就会有点疑惑了,我们定义一个类不就是为了用来创建对象嘛?抽象类不能创建对象我们为什么要有抽象类呢?抽象类的抽象方法有什么用呢?抽象类的存在就是为了面向抽象的,抽象类的主要作用就是用来被继承的。抽象类的子类可以是抽象类或者非抽象类。而对于继承了抽象类的非抽象子类,必须实现(可以理解为方法覆盖/重写)抽象类的所有抽象方法!对于继承了抽象类的抽象子类,没有要求要实现抽象类的所有抽象方法。
理论讲完来看一个简单的代码实例:
public abstract class Biology { public abstract void move();}class Cat extends Biology {// 实现抽象类的方法 public void move() { System.out.println("Cat move!"); } }
上面的Biology是一个抽象类,我们在里面定义了一个抽象方法move。因为Cat类不是抽象类所以继承了抽象类后必须实现抽象类中的抽象方法。对于抽象类,我们也是可以用多态的,这里不再演示。
那么,我们什么时候用抽象类呢?一般是存在继承关系,或有共同方法且实现功能一样时,使用抽象类
接口讲完了抽象类后读者是不是感觉对于面向抽象编程来说,还是有点含糊的,其实正常,因为要用抽象类来面向抽象编程的话其实是很少的,一般都是用接口来实现,因为接口比抽象类更加抽象了!因此我们重点讲解接口:
0 1接口的概念什么是接口呢?接口其实可以说是抽象类的子集,因为接口是在抽象类的基础上进行改进使之可以完全面向抽象编程,因此,抽象编程也叫做接口编程。对于接口,拿我们经常使用的电脑为例,一台电脑少不了USB接口,耳机接口等等,这些就可以类比成我们Java的接口了。
0 2接口的定义及实现定义一个接口的格式为(和之前定义的类差别就是class改成interface而已,接口也是引用数据类型):
访问权限修饰符 interface 接口名 {}
而接口的类体就和抽象类差很多了!在JDK8之前,接口的类体只能存放常量和抽象方法这两种而已,JDK8之后支持在接口里面放static或default修饰的方法。不过这种新特性作者感觉使接口完全抽象的性质就被破坏了,我们来看一个JDK8之前接口的定义例子:
而实现一个接口我们不像抽象类那样用extends来被继承,而是用implements来实现!
如果不这样写,那么编译器就会报错。而且一个类可以实现多个接口,这在一定程度上弥补了Java单继承的缺陷。如果一个非抽象类实现多个接口,那么需要把多个接口中的抽象方法加以实现,否则编译器会报错。
接下来还有需要我们知道的点就是接口和抽象类类似,抽象类是天生用来被继承的,接口天生用来被实现的,而两者的抽象方法都要被实现(覆盖/重写),因此方法的修饰符都写public。而常量的static final和抽象方法的abstract都是固定的,所以在接口中,我们可以把以上说到的关键词都省略,因此以上的A接口可以简写成:
一般我们都写简写形式,而在JDK8之后,我们可以在接口中加这样的两种方法:
default修饰的就相当于实例方法,static修饰的相当于静态方法。所以...(对于这个新特性作者也是很懵)。这里只解释在之前,default修饰符都是省略的,但这里default不能省略,因为省略了就和简写的抽象方法一样,所以不能省略。
0 3接口的作用上面说过接口和抽象类相似,所以接口的作用也是主要用来被实现,当然,接口也可以被继承,但继承的类必须是接口,不仅如此,接口支持多继承!目前Java中只有接口支持多继承!
而且你可以重写父接口的方法。
这时候读者可能有疑惑了,那一个非抽象类能不能同时继承父类也同时实现接口呢?答案是可以的,在非抽象类中,我们可以在继承父类的同时实现一个或多个接口。
以上是个能上天遁水的鸟类。
对于接口,最有趣的多态出现了,我们先来看一个例子:
我们定义了三个接口A,B,C,然后让D这个类实现A,B两个接口,然后我们用多态,A类型的引用t指向D对象,然后分别让t强转成B类型和C类型,我们会发现我们在强转成不是D类型的父类的C类型的时候编译器居然没有报错。但运行的时候报错了:
报错原因是15行的类型转换出现了异常,也就是说,一个子类实现了多个父类接口时,用这些父类接口类型的引用指向子类型对象的时候,这个类型可以强转成其他父接口的类型,但不能强转成其他不是父接口的类型,如果这么做了,编译不会报错,但运行会报错!这就是接口的多态和之前的多态的简要区别了。
那么,我们什么时候用接口呢?当我们只关心功能存在性时,使用接口。很多都是形容词。比如,我们定义一个能飞的接口,以后创建能飞的类时就去实现这个接口;我们定义一个能移动的接口,以后创建能移动的类就去实现这个接口。在我们JavaSE阶段,我们对接口的选择也是只能从这方面解释的。
A总结01抽象类和接口都是和类同一级别的,而一个Java文件只能有一个public修饰的类,所以我们在创建抽象类和接口的时候还会新建一个Java文件,然后用public来修饰抽象类和接口。(修饰符的时候会提到)
没有方法体的一定是抽象方法嘛?02不一定,就拿Object的源代码来看:
这个registerNatives就没有方法体,但它没有用abstract修饰,所以不是抽象方法。这个native表示调用了JVM的本地程序。
03非抽象类、抽象类、接口的比较非抽象类 | 抽象类 | 接口 |
三大变量 +常量 | 三大变量 +常量 | 常量 |
三大方法 | 三大方法 +抽象方法 | 抽象方法两 种修饰符方法 |
能封装 | 不能封装 | 不能封装 |
单继承 | 单继承 | 多继承 |
被单继承 | 被单继承 | 被单或与其 他接口多实现 |
多态(没有继 承关系编译 器会报错) | 多态(没有继 承关系编译 器会报错) | 多态(没有继 承关系编译 器不会报错) |
能实例化 | 不能实例化 | 不能实例化 |