java中的抽象类与接口(面试常考,重要)!!(二)

简介: java中的抽象类与接口(面试常考,重要)!!(二)

接口使用实例(Comparable 接口与Comparator接口)

Comparable接口

刚才的关于例子比较抽象, 我们再来一个更能实际的例子.

给对象数组排序

给定一个学生类

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 "[" + this.name + ":" + this.score + "]";
    }
}

再给定一个学生对象数组

Student[]students=new Student[]{
        new Student("张三",95),
        new Student("李四",96),
        new Student("王五",97),
        new Student("赵六",92),
        };

现对这个对象数组中的元素进行排序(按分数降序).

按照我们之前的理解, 数组我们有一个现成的 sort 方法, 能否直接使用这个方法呢?

Arrays.sort(students);
System.out.println(Arrays.toString(students));
// 运行出错, 抛出异常.
        Exception in thread"main"java.lang.ClassCastException:Student cannot be cast to java.lang.Comparable

我们呢会发现此时会发生类型转换异常,仔细思考, 不难发现, 和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系怎么确定? 需要我们额外指定.

假设我们不去指定的话,我们也不知道到底是按照学生姓名比较,还是学生的年龄来进行比较.

此时就需要让我们的 Student 类实现 Comparable 接口, 并重写 其中的 抽象的compareTo 方法,下面来看代码:

1.//自定义类型比较大小需要实现Comparable接口,<>为泛型  
2.//对于Comparable接口来说,一般都是在类的内部定义的
3.class Student implements Comparable<Student> {  
4.    public String name;  
5.    public int age;  
6.    public int score;  
7.  
8.    public Student(String name, int age, int score) {  
9.        this.name = name;  
10.        this.age = age;  
11.        this.score = score;  
12.    }  
13.  
14.    //重写toString方法  
15.    @Override  
16.    public String toString() {  
17.        return "Student{" +  
18.                "name='" + name + '\'' +  
19.                ", age=" + age +  
20.                ", score=" + score +  
21.                '}';  
22.    }  
23.  
24.    //因为此时实现了Compareable接口,则需要重写其内部的抽象方法compareTo  
25.    @Override  
26.    public int compareTo(Student o) {  
27.        //通过分数来进行排序,如果想通过年龄等直接修改score替换成age即可  
28.        //如果大于的时候return 1,小于return -1,说明是按照从小到大的顺序排列的
29.        //如果大于的时候return -1,小于return 1,说明是按照从大到小的顺序排列的
30.        if(this.score > o.score) {  
31.            return 1;  
32.        }else if(this.score == o.score) {  
33.            return 0;  
34.        }else {  
35.            return -1;  
36.        }  
37.    }  
38.}  
39.public class TestDemo2 {  
40.  
41.    public static void main(String[] args) {  
42.        //如果想要对Student类的引用进行大小比较,就需要Student类去实现Comparable接口  
43.        Student student1 = new Student("bit",18,79);  
44.        Student student2 = new Student("gao",29,70);  
45.        Student student3 = new Student("shasha",17,99);  
46.          
47.        Student[] students = new Student[3];  
48.        students[0] = student1;  
49.        students[1] = student2;  
50.        students[2] = student3;  
51.  
52.        //sort方法默认从小到打排序  
53.        Arrays.sort(students);  
54.        System.out.println(Arrays.toString(students));  
55.    }  
56.}

此时我们通过分数进行排序,在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象. 然后比较当前对象和参数对象的大小关系(按分数来算).


如果当前对象应排在参数对象之前, 返回小于 0 的数字;

如果当前对象应排在参数对象之后, 返回大于 0 的数字;

如果当前对象和参数对象不分先后, 返回 0;


再次执行程序, 结果就符合预期了.


// 执行结果

[Student{name=‘gao’, age=29, score=70}, Student{name=‘bit’, age=18, score=79}, Student{name=‘shasha’, age=17, score=99}]

注意:

但是上述的比较方式是有局限性的,因为上述的比较方式中是直接把比较写死的,可以说只能比较年龄,不能比较姓名等其他东西,但是我现在就想比较除掉年龄的其他东西该怎么办?此时就用到了比较器.

对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则.

Comparator接口(比较器)

Comparable接口 与Comparator接口的区别

首先我们知道的是Compareable接口是定义在类的内部 来实现对象的比较的,而我们比较器Comparator是专门定义在类的外部的

代码示例

还是之前的学生类,不同的是Student类不再实现Compareable接口:


此时我们想根据分数来对学生进行排序,那就在类的外部重新定义一个public类去实现Comparator接口,重写Comparator接口中的compare方法,以此来实现我们对于分数的比较和排序:来看代码:

首先来看Comparator接口中的compare方法:

image.png

来看对于分数比较的代码

//ScoreComparator.java类
public class ScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.score - o2.score;
    }
}

如果还想要对于学生的名字进行比较:来看代码实现:

public class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}

这块的代码就不能再使用o1.name-o2.name这样的写法了,原因是我们的name是String类型,是不能进行相加减的,所以此处应该使用compareTo方法,有些同学可能就会纳闷了,compareTo方法出现于Compareable接口中,为什么要在这里使用这个呢?

答案如下:


我们先来看String类的源码:

image.png

可以看到String类实现了我们的Compareable接口,并重写了我们Compareable接口中的compareTo方法

image.png

所以关于名字的比较器我们可以知道它虽然实现了Comparator接口但是其内部使用的compareTo方法来源于Compareable接口.

下面来看主函数中是如何实现对于名字和分数的比较的:

class Student {
    public String name;
    public 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 TestDemo2 {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("bit", 89);
        students[1] = new Student("abc", 19);
        students[2] = new Student("htf", 59);
        System.out.println("===============没有排序前==============");
        System.out.println(Arrays.toString(students));
        System.out.println("===============根据分数排序后==============");
        ScoreComparator scoreComparator = new ScoreComparator();
        //传两个参数进来
        Arrays.sort(students, scoreComparator);
        //输出结果为:[Student{name='abc', score=19}, Student{name='htf', score=59}, Student{name='bit', score=89}]
        System.out.println(Arrays.toString(students));
        System.out.println("===============根据姓名进行排序后==============");
        NameComparator nameComparator = new NameComparator();
        Arrays.sort(students, nameComparator);
        //输出结果为:[Student{name='abc', score=19}, Student{name='bit', score=89}, Student{name='htf', score=59}]
        //以上姓名的排序是按照首字母进行排序的
        System.out.println(Arrays.toString(students));
    }
}

接口间的继承(扩展)

接口可以继承一个接口,也可以继承(扩展)多个接口,已达到复用的效果. 使用 extends 关键字.当然这里的继承关系我们把它理解成扩展的意思更为准确

interface IRunning { void run();
}
interface ISwimming { void swim();
}
// 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
  //此时IAmphibious这个接口相当于有了两个方法,一个是run,一个是swim
}
class Frog implements IAmphibious {
 //此时Frog这个类必须重写IAmphibious这个接口的两个抽象方法,一个run方法,一个swim方法
}

通过接口继承创建一个新的接口 IAmphibious 表示 “两栖的”. 此时实现接口创建的 Frog 类, 就继续要实现 run 方法, 也需要实现 swim 方法.

总结

抽象类和接口都是 Java 中多态的常见使用方式. 都需要重点掌握. 同时又要认清两者的区别(重要!!! 常见面试题).


核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法.


如之前写的 Animal 例子. 此处的 Animal 中包含一个 name 这样的属性, 这个属性在任何子类中都是存在的. 因此此处的 Animal 只能作为一个抽象类, 而不应该成为一个接口.

        class Animal {
            protected String name;
            public Animal(String name) {
                this.name = name;
            }
        }

再次提醒:

抽象类存在的意义是为了让编译器更好的校验, 像 Animal 这样的类我们并不会直接使用, 而是使用它的子类. 万一不小心创建了Animal 的实例, 编译器会及时提醒我们.

image.png

相关文章
|
2天前
|
存储 Java 测试技术
滚雪球学Java(61):从源码角度解读Java Set接口底层实现原理
【6月更文挑战第15天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
11 1
滚雪球学Java(61):从源码角度解读Java Set接口底层实现原理
|
3天前
|
Java
深入理解 Java 8 函数式接口:定义、用法与示例详解
深入理解 Java 8 函数式接口:定义、用法与示例详解
6 1
|
3天前
|
安全 Java
Java 并发编程详解:Lock 接口及其实现 ReentrantLock
Java 并发编程详解:Lock 接口及其实现 ReentrantLock
12 1
|
4天前
|
Java
JAVA高级部分(接口)
JAVA高级部分(接口)
|
4天前
|
Java
Java动态获取某个接口下所有的实现类对象集合
Java动态获取某个接口下所有的实现类对象集合
11 1
|
5天前
|
存储 安全 算法
Java基础19-一文搞懂Java集合类框架,以及常见面试题(二)
Java基础19-一文搞懂Java集合类框架,以及常见面试题(二)
34 8
|
5天前
|
安全 Java 开发工具
Java基础19-一文搞懂Java集合类框架,以及常见面试题(一)
Java基础19-一文搞懂Java集合类框架,以及常见面试题(一)
31 6
|
5天前
|
JSON Java 数据格式
java读取接口返回的json数据 (二)
java读取接口返回的json数据 (二)
17 5
|
5天前
|
JSON Java 数据格式
java读取接口返回的json数据
java读取接口返回的json数据
17 5
|
5天前
|
安全 Java 程序员
Java基础18-一文搞懂Java多线程使用方式、实现原理以及常见面试题(二)
Java基础18-一文搞懂Java多线程使用方式、实现原理以及常见面试题(二)
26 4