在本系列之前的所有文章中,没有看到的同学可以先去看看之前的文章,我们已经了解了 Java8 引入的函数式接口。
其实在 Java 8 之前就存在函数式接口。在本文中,我们将了解它们如何参与函数式编程。
最关键的两个函数接口是Comparable和Comparator。这两个接口主要用于对无序集合进行排序。这篇文章写成面试谈话。便于更好地理解。
介绍
这些是面试中会问的重要接口。事实上,到目前为止面试过很多人关于这两个接口的问题,大多数人都没有回答出来。因此,在本文中,我们将说明所有这些棘手的问题,阅读本文后,您将能够轻松地回答这些问题。
我沿着同样的思路面试很多人,以了解应聘者对这两个接口了解多少?下面指定的模拟面试将消除您对“Comparable”和“Comparator”的所有疑惑,并将帮助您回答未来面试中的任何问题。让我们从基础开始我们的面试,慢慢深入研究这两个接口。让我们开始我们的模拟面试吧。
让我们开始面试吧!
面试官:你了解Comparable和Comparator接口吗?你能说明它们的区别吗?
应聘者:是的。 Comparable 接口有一个抽象方法compareTo(),它接受实现类类型的单个参数。有两种情况我们需要实现Comparable接口。
首先,在TreeMap 和 TreeSet 或任何排序的集合中会用到。因为这些集合需要一种比较对象的方法。
其次,当我们需要使用 Collections.sort() 或 Arrays.sort() 方法对对象集合进行排序时。
面试官:说的不错。但是你还没有提到 Comparator 接口?
应聘者:哦!没有。Comparator接口也有同样的用途。它还有一个抽象方法,它接受两个参数,这两个参数是我们需要比较的对象。
面试官:你能写一个Comparable接口的例子吗?
应聘者:当然可以,我将以 Student 类为例,让它实现 Comparable 接口,如下所示。
class Student implements Comparable<Student> {
private final int id;
private final String name;
private final int marks;
// Three arg Constructor
// Getters
...
@Override
public int compareTo(Student other) {
if (id < other.id) {
return -1;
} else if (id > other.id) {
return 1;
} else {
return 0;
}
}
}
面试官:你能解释一下你写的这段代码的意思吗?
应聘者:这里我定义了一种使用学生对象的 ID 来比较学生对象的方法。无论哪个 Student 对象具有更大的 ID,都将被视为比另一个更大的 Student 对象。
面试官:但是你不觉得compareTo()方法看起来很笨拙吗?你能尝试优化它吗?
(这又是面试者经常犯的另一个错误。我想说这是因为编程能力差。我们知道返回正整数意味着第一个对象大于另一个,负数意味着小于,0意味着等于。我们需要返回的只是正数或负数或零。不需要+1或-1或0。这是面试官发现的)。
应聘者:是的。我也觉得。所有 if-else-else 块都可以用单个语句替换,如下所示。
@Override
public int compareTo(Student other) {
return id - other.id;
}
面试官:太棒了!您能可以使用 Comparator 接口编写相同的比较代码吗?
应聘者:当然
class StudentComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return s1.id - s2.id;
}
}
到目前为止,面试官测试了你关于比较 Java 中对象的基本知识。面试官现在带你进入真正的游戏)。
面试官:很好,让我们继续吧。 Comparable 和 Comparator 接口具有相同的用途。但为什么我们需要两个接口来实现相同的目的呢?
(这里面试官是测试你是否知道这两个接口的区别)
应聘者:当我们需要比较我们拥有的类的对象时,使用 Comparable 接口。这意味着我们自己编写这些类。由于我们是类的所有者,因此我们可以修改这些类,以便它们可以从 Comparable 接口实现。
但也可能存在一种情况,我们需要比较我们不是其所有者的类的对象。例如,我们将来自第三方类添加到类路径中。我们无法修改这些类。在这种情况下,我们使用 Comparator 来定义比较它们的方法。
(这是一个完美且切中要害的答案。如果你说出这个答案,你的面试官会肯定会对你印象深刻。例如你如果说的是Comparable 具有带有一个参数的compareTo()方法,而 Comparator 具有带两个参数的compare()方法,你会直接被pass。 好吧,虽然应聘者给出了一个非常好的答案,但他还漏掉了面试官发现的一点。)
面试官:说的不错。这是我们在 Comparator 和 Comparable 之间进行选择时的唯一场景吗?或者您认为还有其他场景,要用到这俩个接口吗?
应聘者:思考、思考、再思考……
面试官:好的,我给你一个提示。假设我们是该类的所有者,并且Student 类实现了 Comparable 接口 。如果我们不想在compareTo()方法中指定比较方法怎么办?
应聘者:哦!我明白了。在这种情况下,我们将使用 Comparator 接口。这是第二种情况,我们需要使用 Comparator 接口。
面试官:不错你很聪明
应聘者:笑一笑哈哈哈
(由于候选人给出了答案,候选人和面试官之间建立了融洽的关系)
面试官:好的。您知道 Comparator 接口与 Lambda 有何关系吗?
应聘者:因为它有一个抽象方法,所以可以被视为一个函数式接口。我们可以为它实现 lambda 表达式。
面试官:您可以为 StudentComparator 编写两个单独的 lambda 表达式来根据 Id 和名称进行比较吗?
应聘者:如下所示
Comparator<Student> idComparator = (s1, s2) -> s1.id - s2.id;
Comparator<Student> nameComparator =
(s1, s2) -> s1.getName().compareTo(s2.getName();
面试官:好的。您可以使用第二个nameComparator对 Student 对象列表进行排序吗?假设您有一个 Student 对象列表,如下所示。
List<Student> students = … ;
应聘者:当然。我可以在 List 对象上使用 sort() 方法并将Comparator作为参数发送。
students.sort((s1, s2) -> s1.getName().compareTo(s2.getName()));
面试官:现在假设我有一个不同的要求?我需要对学生进行排序,首先按姓名排序,如果姓名相等,则需要按 ID 升序排序。听明白需求了吗?
应聘者:是的,
students.sort((s1, s2) -> {
int diff = s1.getName().compareTo(s2.getName());
if (diff == 0) {
diff = s1.getId() - s2.getId();
}
return diff;
});
面试官:。。。如果我要求您添加另一个条件,即 ID 相等并且需要按照分数升序排序,该怎么办?
应聘者:所以我需要先按名称排序,然后按 ID 排序,最后按分数排序?我会在 if 块内添加另一个条件,如下所示。
students.sort((s1, s2) -> {
int diff = s1.getName().compareTo(s2.getName());
if (diff == 0) {
diff = s1.getId() - s2.getId();
if (diff == 0) {
diff = s1.getMarks() - s2.getMarks();
}
}
return diff;
});
(这不完全是正确的答案。如果我们再添加一个字段怎么办?面试官是对此不满意,并提出了问题,因为有更好的解决办法。)
面试官:如果我们需要再添加一个字段(比如部门编号)怎么办?假设如果分数相等,则必须按部门编号排序。还可以添加另一个条件吗?或者有什么更好的办法吗?
应聘者:思考、思考、思考……我不知道
面试官:好的。你知道Comparator接口中的的compare()和thenComparing()方法吗?
应聘者:。。。不知道
面试官:好的,面试结束后可以了解一下。
应聘者:好的
面试官:面试差不多结束了。你还有什么问题要问吗?
应聘者:没有,这是一次很棒的面试,学到了很多。
面试官:我会把情况反馈给HR,他们会联系你。祝你有个美好的一天。再见。
应聘者:谢谢!再见。
这就是我们的面试过程。应聘者对Comparator和Comparable接口有很好的理解。最后一个问题对于那些不了解comparing()和thenComparing()方法的人来说有点困难。我们将在下一篇文章中介绍它们。
希望这篇文章对你有帮助!
如果喜欢这篇文章,点赞支持一下,微信搜索:京城小人物,关注我第一时间查看更多内容,最近需要面试的同学,可以点击 「链接」 获取java面试笔记!感谢支持!