2.5 实现多个接口
在Java语言中,类和类之间是单继承关系,一个类只可以有一个父类,即Java中不支持多继承关系,但是一个类可以实现多个接口,下面通过Animal类来具体说明
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
另外我们再提供一组接口,分别表示这个动物会跑,会游泳,会飞
interface IFlying {
void fly();
}
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
接下来是具体的动物类,比如青蛙是两栖动物,既会跑,有会游泳
class Frog extends Animal implements IRunning, ISwimming {//可以实现多个接口
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在往前跳");
}
@Override
public void swim() {
System.out.println(this.name + "正在蹬腿游泳");
}
}
注意:在一个类实现多接口的时候,每一个接口的方法都必须被重写,我们在idea编译器中,可以使用Ctrl+i快速实现接口
再比如鸭子,既会跑,又会游泳,又会飞
class Duck extends Animal implements IRunning, ISwimming, IFlying {
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "正在用翅膀飞");
}
@Override
public void run() {
System.out.println(this.name + "正在用两条腿跑");
}
@Override
public void swim() {
System.out.println(this.name + "正在漂在水上");
}
}
上面展示了Java中面相对象中最常见的一种用法:一个类继承一个父类,同时实现多个接口
继承表达的含义是is a,即一个类属于什么大类,接口则表示的是can xxx,即一个类具有什么样的特性或能力
这样设计的好处在哪里呢?时刻牢记多态的好处,它可以让程序员忘记类型,有了接口之后,就不必关心具体的类型,而是关注某个类是否具有某种能力
面试题:什么时候会发生多态
在不同类继承一个父类,并且重写了父类中的方法,并且通过父类引用创建的子类对象调用了重写的方法,此时会发生动态绑定并发生向上转型,此时就会发生多态
只要这个类的东西具有某种能力,就可以实现一个接口,比如实现上述接口的类不一定必须是动物类,还可以是其他类,比如机器人也会跑
class Robot implements IRunning {
private String name;
public Robot(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name + "正在用轮子跑");
}
}
2.6 接口之间的继承
在Java中,类和类之间是单继承关系,一个类可以实现多个接口,接口与接口之间可以多继承,即接口可以达到多继承的目的
接口可以继承一个接口,达到复用的效果,使用extends关键字
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
// 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {
}
通过一个创建一个新接口IAmphibious,并继承了IRunning, ISwimming,来达到合并两个接口的目的,在Frog类中,还需要继续重写run方法和swim方法
2.7 接口的经典使用案例
- 给对象数组排序
如果我们有一个数组,这个数组中的数据类型是学生类
public class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
public class Main {
public static void main(String[] args) {
Student[] student = new Student[]{
new Student("zhangsan",95),
new Student("lisi",89),
new Student("wangwu",88),
new Student("zhaoliu",98),
};
}
}
比如我们在这里要对这个数组中的学生按照成绩进行排序,数组中我们有sort方法,能否直接使用这个方法呢?
在这里我们可以看出,编译器在这个地方抛出了异常,为什么呢,是因为一个数组中的对象有两个成员,一个是姓名,一个是成绩,编译器并不知道你要通过哪个成员来比较对象,所以我们这里引入了Comparable接口,通过重写该接口中的compareTo方法来实现对象的比较
public class Student implements Comparable {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
@Override
public int compareTo(Object o) {//重写compareTo方法
Student s = (Student) o;//object类强转为Student类
if (this.score > s.score){
return -1;
}else if (this.score<s.score){
return 1;
}else {
return 0;
}
}
}
在sort方法中,会自动调用重写的compareTo方法
然后比较当前对象和参数对象的大小关系(按分数来算).
如果当前对象应排在参数对象之前, 返回小于 0 的数字;
如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回 0;
之后的运行就符合预期了
注意事项: 对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则.
在这里我们给出几种更加灵活的方法,利用比较器,即实现Compartor接口
public class nameCompare implements Comparator<Student> {//比较什么,尖括号里就写什么
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
public class sorceCompare implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
if (o1.score > o2.score){
return -1;
}else if (o1.score<o2.score){
return 1;
}else {
return 0;
}
}
}
public class Main {
public static void main(String[] args) {
Student[] student = new Student[]{
new Student("zhangsan",95),
new Student("lisi",89),
new Student("wangwu",88),
new Student("zhaoliu",98),
};
Arrays.sort(student);
System.out.println(Arrays.toString(student));
nameCompare comparator1 = new nameCompare();
sorceCompare comparator2 = new sorceCompare();
System.out.println(comparator1.compare(student[0],student[1]));
System.out.println(comparator2.compare(student[0],student[1]));
}
}
在这里我们可以看出,与Comparable接口不同的是,它不是在想要比较的类上直接添加,而是单独创建了一个类并创建对象,可以根据成绩比较,可以根据名字比较,返回的是一个整数,若我们想利用比较器对数组进行排序,可不可以实现呢,当然可以,我们在上述代码的基础上稍作改动
public class Main {
public static void main(String[] args) {
Student[] student = new Student[]{
new Student("zhangsan",95),
new Student("lisi",89),
new Student("wangwu",88),
new Student("zhaoliu",98),
};
Arrays.sort(student);
System.out.println(Arrays.toString(student));
nameCompare comparator1 = new nameCompare();
sorceCompare comparator2 = new sorceCompare();
System.out.println(comparator1.compare(student[0],student[1]));
System.out.println(comparator2.compare(student[0],student[1]));
Arrays.sort(student,comparator1)//传入比较器
System.out.println(Arrays.toString(student));
}
}
在数组类的sort方法中,我们传入了一个关于名字的比较器,是sort的一个重载方法,那么运行结果就会根据名字字母的先后顺序进行排序,运行结果如下
在这里我们给出重载sort方法的源码,方便大家理解
public static <T> void sort(T[] a, Comparator<? super T> c) {//在这里我们可以看到该方法有比较器的形式参数
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
2.8抽象类与接口的区别
抽象类和接口都是 Java 中多态的常见使用方式. 都需要重点掌握. 同时又要认清两者的区别(重要!!! 常见面试题).
核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法.
如之前写的 Animal 例子. 此处的 Animal 中包含一个 name 这样的属性, 这个属性在任何子类中都是存在的. 因此此处的 Animal 只能作为一个抽象类, 而不应该成为一个接口.
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}