实现Comparator接口
Comparator接口的身影在JDK库中随处可见,从查找到排序,再到反转操作,等等。Java 8里它变成了一个函数式接口,这样的好处就是我们可以使用流式语法来实现比较器了。
我们用几种不同的方式来实现一下Comparator,看看新式语法的价值所在。你的手指头会感谢你的,不用实现匿名内部类少敲了多少键盘啊。
使用Comparator进行排序
下面这个例子将使用不同的比较方法,来将一组人进行排序。我们先来创建一个Person的JavaBean。
public class Person { private final String name; private final int age; public Person(final String theName, final int theAge) { name = theName; age = theAge; } public String getName() { return name; } public int getAge() { return age; } public int ageDifference(final Person other) { return age - other.age; } public String toString() { return String.format("%s - %d", name, age); } }
我们可以通过Person类来实现Comparator接口,不过这样我们只能使用一种比较方式。我们希望能比较不同的属性——比如名字,年龄,或者这些的组合。为了可以灵活的进行比较,我们可以使用Comparator,当我们需要进行比较的时候,再去生成相关的代码。
我们先来创建一个Person的列表,每个人都有不同的名字和年龄。
final List<Person> people = Arrays.asList( new Person("John", 20), new Person("Sara", 21), new Person("Jane", 21), new Person("Greg", 35));
我们可以通过人的名字或者年龄来对他们进行升序或者降序的排序。一般的方法就是使用匿名内部类来实现Comparator接口。这样写的话只有比较相关的代码是有意义的,其它的都只是走走形式而已。而使用lambda表达式则可以聚焦到比较的本质上来。
我们先按年龄从小到大对他们进行排序。
既然我们已经有了一个List对象,我们可以用它的sort()方法来进行排序。不过这个方法也有它的问题。这是一个void方法,也就是说当我们调用这个方法的时候,这个列表会发生改动。要保留原始列表的话,我们得先拷贝出一份来,然后再调用sort()方法。这简直太费劲了。这个时候我们得求助下Stream类了。
我们可以从List那获取一个Stream对象,然后调用它的sorted()方法。它会返回一个排好序的集合,而不是在原来的集合上做修改。使用这个方法的话可以很方便的配置Comparator的参数。
List<Person> ascendingAge = people.stream() .sorted((person1, person2) -> person1.ageDifference(person2)) .collect(toList()); printPeople("Sorted in ascending order by age: ", ascendingAge);
Comparator的抽象方法compareTo()接收两个参数,也就是要比较的对象,并返回一个int类型的结果。为了兼容这个,我们的lambda表达式也接收两个参数,两个Person对象,它们的类型是由编译器自动推导的。我们返回一个int类型,表明比较的对象是否相等。
因为要按年龄进行排序,所以我们会比较两个对象的年龄,然后返回比较的结果。如果他们一样大,则返回0。否则如果第一个人更年轻的话就返回一个负数,更年长的话就返回正数。
sorted()方法会遍历目标集合的每个元素并调用指定的Comparator,来确定出元素的排序顺序。sorted()方法的执行方式有点类似前面说到的reduce()方法。reduce()方法把列表逐步归约出一个结果。而sorted()方法则通过比较的结果来进行排序。
一旦我们排好序后我们想要把结果打印出来,因此我们调用了一个printPeople()方法;下面来实现下这个方法。
public static void printPeople( final String message, final List<Person> people) { System.out.println(message); people.forEach(System.out::println); }
这个方法中,我们先打印了一个消息,然后遍历列表,打印出里面的每个元素。
我们来调用下sorted()方法看看,它会将列表中的人按年龄从小到大进行排列。
Sorted in ascending order by age: John - 20 Sara - 21 Jane - 21 Greg - 35
我们再看一下sorted()方法,来做一个改进。
.sorted((person1, person2) -> person1.ageDifference(person2))
这里用到的参数路由和前面看到的有点不同。我们之前看到的,要么参数是作为调用目标,要么是作为调用参数。而现在,我们有两个参数,我们希望能分成两个部分,一个是作为方法调用的目标,第二个则作为参数。别担心,Java编译器会告诉你,“这个我来搞定”。
我们可以把前面的sorted()方法里面的lambda表达式替换成一个短小精悍的ageDifference方法。
people.stream() .sorted(Person::ageDifference)
重用Comparator
我们很容易就将列表中的人按年龄从小到大排好序了,当然从大到小进行排序也很容易。我们来试一下。
printPeople("Sorted in descending order by age: ", people.stream() .sorted((person1, person2) -> person2.ageDifference(person1)) .collect(toList()));
我们调用了sorted()方法并传入一个lambda表达式,它正好能适配成Comparator接口,就像前面的例子那样。唯一不同的就是这个lambda表达式的实现——我们把要比较的人调了下顺序。结果应该是按他们的年龄由从大到小排列的。我们来看一下。
Sorted in descending order by age: Greg - 35 Sara - 21 Jane - 21 John - 20
前面我们已经创建了两个lambda表达式:一个是按年龄从小到大排序,一个是从大到小排序。这么做的话,会出现代码冗余和重复,并破坏了DRY原则。如果我们只是想要调整下排序顺序的话,JDK提供了一个reverse方法,它有一个特殊的方法修饰符,default。我们会在77页中的default方法来讨论它,这里我们先用下这个reversed()方法来去除冗余性。
Comparator<Person> compareAscending = (person1, person2) -> person1.ageDifference(person2); Comparator<Person> compareDescending = compareAscending.reversed();
printPeople("Sorted in ascending order by age: ", people.stream() .sorted(compareAscending) .collect(toList()) ); printPeople("Sorted in descending order by age: ", people.stream() .sorted(compareDescending) .collect(toList()) );
我们已经可以按年龄进行排序了,想按名字来排序的话也很简单。我们来按名字进行字典序排列,同样的,只需要改下lambda表达式里的逻辑就好了。
printPeople("Sorted in ascending order by name: ", people.stream() .sorted((person1, person2) -> person1.getName().compareTo(person2.getName())) .collect(toList()));
Sorted in ascending order by name: Greg - 35 Jane - 21 John - 20 Sara - 21
现在为止,我们要么就按年龄排序,要么就按名字排序。我们可以让lambda表达式的逻辑更智能一些。比如我们可以同时按年龄和名字排序。
我们来选出列表中最年轻的人来。我们可以先按年龄从小到大排序然后选中结果中的第一个。不过其实用不着那样,Stream有一个min()方法可以实现这个。这个方法同样也接受一个Comparator,不过返回的是集合中最小的对象。我们来用下它。
people.stream() .min(Person::ageDifference) .ifPresent(youngest -> System.out.println("Youngest: " + youngest));
Youngest: John - 20
输出年纪最大的同样也很简单。只要把这个方法引用传给一个max()方法就好了。
people.stream() .max(Person::ageDifference) .ifPresent(eldest -> System.out.println("Eldest: " + eldest));
Eldest: Greg - 35
多重比较和流式比较
我们来看下Comparator接口提供了哪些方便的新方法,并用它们来进行多个属性的比较。
我们还是继续使用上节中的那个例子。按名字排序的话,我们上面是这么写的:
people.stream() .sorted((person1, person2) -> person1.getName().compareTo(person2.getName()));
和上个世纪的内部类的写法比起来,这种写法简直太简洁了。不过如果用了Comparator类里面的一些函数能让它变得更简单,使用这些函数能够让我们更流畅的表述自己的目的。比如说,要按名字排序的话,我们可以这么写:
final Function<Person, String> byName = person -> person.getName(); people.stream() .sorted(comparing(byName));
这段代码中我们导入了Comparator类的静态方法comparing()。comparing()方法使用传入的lambda表达式来生成一个Comparator对象。也就是说,它也是一个高阶函数,接受一个函数入参并返回另一个函数。除了能让语法变得更简洁外,这样的代码读起来也能更好的表述我们想要解决的实际问题。
有了它,进行多重比较的时候也能变得更加流畅。比如,下面这段按名字和年龄比较的代码就能说明一切:
final Function<Person, Integer> byAge = person -> person.getAge(); final Function<Person, String> byTheirName = person -> person.getName(); printPeople("Sorted in ascending order by age and name: ", people.stream() .sorted(comparing(byAge).thenComparing(byTheirName)) .collect(toList()));
我们先是创建了两个lambda表达式,一个返回指定人的年龄,一个返回的是他的名字。在调用sorted()方法的时候我们把这两个表达式组合 到了一起,这样就能进行多个属性的比较了。comparing()方法创建并返回了一个按年龄比较的Comparator ,我们再调用这个返回的Comparator上面的thenComparing()方法来创建一个组合的比较器,它会对年龄和名字两项进行比较。下面的输出是先按年龄再按名字进行排序后的结果。
Sorted in ascending order by age and name: John - 20 Jane - 21 Sara - 21 Greg - 35
问题内容: 如何在Java中模拟函数式编程,特别是如何将函数映射到项目集合? 什么是最冗长和尴尬的方法? 问题答案: 在Java之前,所有的函数式编程尝试在Java中都会有些 冗长 和/或 笨拙 ,直到Java 8。 最 直接的 方法是提供一个接口(例如Guava的这种形式),并提供采用和调用该接口的各种方法(例如我认为您的方法应该执行的操作)。 不好的事情是,您需要使用匿名内部类来实现并经常这样
本文向大家介绍详解JAVA 函数式编程,包括了详解JAVA 函数式编程的使用技巧和注意事项,需要的朋友参考一下 1.函数式接口 1.1概念: java中有且只有一个抽象方法的接口。 1.2格式: 1.3@FunctionalInterface注解: 与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注 解可用于
本文向大家介绍Java函数式编程(六):Optional,包括了Java函数式编程(六):Optional的使用技巧和注意事项,需要的朋友参考一下 选取单个元素 直觉来说选取单个元素肯定会比选取多个要简单得多,不过这里也存在一些问题。我们先看下一般的做法的问题是什么,然后再看下如何用lambda表达式来解决它。 我们先新建一个方法来查找一个以特定字母开头的元素,然后打印出来。 这个方法简直跟刚过去
本文向大家介绍Java函数式编程(七):MapReduce,包括了Java函数式编程(七):MapReduce的使用技巧和注意事项,需要的朋友参考一下 译注:map(映射)和reduce(归约,化简)是数学上两个很基础的概念,它们很早就出现在各类的函数编程语言里了,直到2003年Google将其发扬光大,运用到分布式系统中进行并行计算后,这个组合的名字才开始在计算机界大放异彩(那些函数式粉可能并不
函数式编程 -> 函数响应式编程 现在大家已经了解我们是如何运用函数式编程来操作序列的。其实我们可以把这种操作序列的方式再升华一下。例如,你可以把一个按钮的点击事件看作是一个序列: // 假设用户在进入页面到离开页面期间,总共点击按钮 3 次 // 按钮点击序列 let taps: Array<Void> = [(), (), ()] // 每次点击后弹出提示框 taps.forEach {
函数式编程(functional programming)是一种编程范式(Programming paradigm),或者说编程模式,比如我们常见的过程式编程是一种编程范式,面向对象编程又是另一种编程范式。 函数式编程的一大特性就是:可以把函数当成变量来使用,比如将函数赋值给其他变量、把函数作为参数传递给其他函数、函数的返回值也可以是一个函数等等。 Python 不是纯函数式编程语言,但它对函数式