本篇博客介绍抽象类和接口的相关概念。
抽象类
前面我们在学习多态的时候,例如哪个买票的例子,我们发现,父类中的买票方法,没有用到,用到的都是子类中对父类买票方法进行重写后的方法。
- 像这种没有实际工作的方法,我们可以将其设计成一个抽象方法(abstract method),包含抽象方法的类我们成为抽象类(abstract class)。
- 在buyTicket方法前加上abstract关键字,表示这是一个抽象方法。同时抽象方法没有方法体(没有{},不能执行具体代码)。
- 对于包含抽象方法的类,必须加上abstract关键字表示这是一个抽象类。
抽象类的注意事项
- 抽象类不能直接实例化。
- 抽象方法不能是private的。
- 抽象类中可以包含其他的非抽象方法,也可以包含字段。这个非抽象方法和普通方法的规则都是一样的,可以被重写,也可以被子类直接调用。
抽象类的作用
- 抽象类存在的最大意义就是为了被继承。
- 抽象类本身不能被实例化,要想使用,只能创建该抽象类的子类。然后让子类重写抽象类中的抽象方法。
- 虽然说普通类也可以被继承,普通的方法也可以被重写。但是使用抽象类和抽象方法相当于多了一重编译器检查。
- 使用抽象类的场景就如上面的代码,实际工作不应该由父类完成,而应由子类完成。那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的。但是父类是抽象类就会在实例化的时候提示错误,让我们尽早发现问题。
- 很多语法存在的意义都是为了“预防出错”,例如我们曾经用过的final也是类似。创建的变量用户不去修改,不就相当于常量嘛?但是加上final能够在不小心误修改的时候,让编译器及时提醒我们。
- 充分利用编译器的校验,在实际开发中是非常有意义的。
接口
接口是抽象类的更进一步。抽象类中还可以包含非抽象方法和字段。而接口中包含的方法都是抽象方法,字段只能包含静态常量。
- 我们还是使用买票的例子来看一下接口的基本使用。
接口使用的注意事项
- 使用interface定义一个接口;
- 接口中的方法一定是抽象方法,因此可以省略abstract;
- 接口中的方法一定是public,因此可以省略public;
- Student使用implements继承接口。此时表达的含义不再是扩展,而是实现;
扩展(extends)指的是当前已经有一定功能了,进一步扩充功能;
实现(implements)指的是当前什么都没有,需要从头构造出来; - 在调用的时候同样可以创建一个接口的引用,指向一个子类的实例。
- 接口不能单独被实例化。
- 接口中只能包含抽象方法,对于字段来说,接口中只能包含静态常量(final static)。
其中的public、static和final都可以省略。
注意:
- 我们创建接口的时候,接口的命名一般一大写字母I开头。
- 接口的命名一般使用“形容词”词性的单词。
- 阿里编码规范中约定,接口中的方法和属性不要加任何修饰符号,保持代码的简洁性。
下面看一个简单而又经典的错误:
接口IPeople中public可以省略,所以接口IPeople中省略了public还是public权限。而类Student中省略访问权限就是default权限。default权限比public权限更加严格了,所以无法重写。
实现多个接口
有的时候,我们需要让一个类同时继承自多个父类。这件事情在有些编程语言中可以通过多继承来实现。
然而Java中只支持单继承,一个类只能extends一个父类。但是可以同时实现多个接口,也能达到多继承类似的效果。
下面我们来具体演示一下:
- 首先,还是创建一个动物类作为基类:
- 然后,我们再提供一组接口,分别表示“会飞的”、“会跑的”和“会游的”。
接下来我们创建几个具体的动物。
- 兔子是会跑的动物。
- 鱼是会游的动物。
- 青蛙是既能跑又能游的动物。
- 鸭子是既能跑又能游还能飞的动物。
上面的代码展示了Java面向对象编程中最常见的用法:一个类继承一个父类,同时实现多个接口。
这样设计的好处:时刻牢记多态的好处,让程序猿忘记类型。有了接口以后,类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力。
下面实现一个散步的代码,来看一下:
- 我带着小伙伴散步代码如下:
- 甚至参数可以不是动物,只要会跑就行。
接口使用示例
这里,我们实现一下对学生按成绩从高到低排名,使用Comparable接口。
- 首先,我们先来写一下学生类,并实现Comparable接口。
- 然后看效果如下:
在Arrays.sort方法中会自动调用compareTo方法。compareTo的参数是Object,其实传入的就是Student类型的对象。然后比较当前对象和参数对象的大小关系(按分数来算)。
注意:对于Arrays.sort方法来说,需要传入的数组的每个对象都是可比较的,需要具备compareTo这样的能力。通过重写compareTo方法的方式,就可以定义比较规则。
为了进一步加深对接口的理解,我们可以尝试自己实现一个sort方法来完成刚才的排序过程。
- 我们使用冒泡排序来实现:
- 执行代码,结果如下:
接口间的继承
接口可以继承接口,而且可以继承多个接口。使用extends关键字。
- 创建一个两栖类的接口,既能跑也能游。
- 我们使用两栖类的接口,创建一只青蛙。
通过接口继承创建一个新的接口IAmphibious表示“两栖类的”。此时实现接口创建的Frog类,就需要实现run方法和swim方法。
- 接口间的继承相当于把多个接口合并在一起。
总结
抽象类和接口都是Java中多态的常见的使用方式。二者的区别如下:
- 抽象类可以包含普通方法和普通字段,这样的普通方法和字段可以被子类直接使用(不必重写);
- 接口中不能包含普通方法,子类必须重写所有的抽象方法。只能包含静态常量字段。
No | 区别 | 抽象类(abstract) | 接口(interface) |
---|---|---|---|
1 | 结构组成 | 普通类+抽象方法 | 抽象方法+静态常量 |
2 | 权限 | 各种权限 | public |
3 | 子类使用 | 使用extends关键字继承抽象类 | 使用implements关键字实现接口 |
4 | 关系 | 一个抽象类可以实现若干个接口 | 接口不能继承抽象类,但是接口可以使用extends关键字继承多个父接口 |
5 | 子类限制 | 一个子类只能继承一个抽象类 | 一个子类可以实现多个接口 |
注意:
- 抽象类的存在时为了让编译器更好的校验,像Animal这样的类我们并不会直接使用,而是使用它的子类。万一不小心创建了Animal的实例,编译器会及时提醒我们。
- 实际开发中,接口使用的更频繁相对于抽象类。