`
deepinmind
  • 浏览: 452185 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
1dc14e59-7bdf-33ab-841a-02d087aed982
Java函数式编程
浏览量:41675
社区版块
存档分类
最新评论

Java函数式编程(十一)Comparator

阅读更多
本系列文章译自Venkat Subramaniam的Functional Programming in Java

未完待续,后续文章请继续关注Java译站

实现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);


我们先通过stream()方法将列表转化成一个Stream对象。然后调用它的sorted()方法。这个方法接受一个Comparator参数。由于Comparator是一个函数式接口,我们可以传入一个lambda表达式。最后我们调用collect方法,让它把结果存储到一个列表里。collect方法是一个归约器,它能把迭代过程中的对象输出成某种特定的格式或者类型。toList()方法是Collectors类的一个静态方法。

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))


在传入的这个lambda表达式里,我们只是简单的路由了下这两个参数——第一个参数作为ageDifference()方法的调用目标,而第二个作为它的参数。但是我们可以不这么写,而是用一个office-space模式——也就是使用方法引用,让Java编译器去做路由。

这里用到的参数路由和前面看到的有点不同。我们之前看到的,要么参数是作为调用目标,要么是作为调用参数。而现在,我们有两个参数,我们希望能分成两个部分,一个是作为方法调用的目标,第二个则作为参数。别担心,Java编译器会告诉你,“这个我来搞定”。

我们可以把前面的sorted()方法里面的lambda表达式替换成一个短小精悍的ageDifference方法。

people.stream()
.sorted(Person::ageDifference)


这段代码非常简洁,这多亏了Java编译器提供的方法引用。编译器接收到两个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();


我们先创建了一个Comparator,compareAscending,来将人按年龄从小到大进行排序。为了反转比较顺序,而不是再写一次这个代码,我们只需要调用一下这个第一个Comparator的reversed()方法就可以获取第二个Comparator对象。在reversed()方法底层,它创建了一个比较器,来交换了比较的参数的顺序。这说明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())
);


从代码中明显可以看到,Java8的这些新特性极大的减少了代码的冗余及复杂度,不过好处远不止这些,JDK里还有无限可能等着你去探索。

我们已经可以按年龄进行排序了,想按名字来排序的话也很简单。我们来按名字进行字典序排列,同样的,只需要改下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));


调用min()方法的时候我们用了ageDifference这个方法引用。min()方法返回的是一个Optinal对象,因为列表可能为空并且里面可能不止一个年纪最小的人。接着我们通过Optinal的ifPrsend()方法获取到年纪最小的那个人,并打印出他的详细信息。来看下输出结果。

Youngest: John - 20


输出年纪最大的同样也很简单。只要把这个方法引用传给一个max()方法就好了。

people.stream()
.max(Person::ageDifference)
.ifPresent(eldest -> System.out.println("Eldest: " + eldest));



我们来看下最年长那位的名字和年龄。

Eldest: Greg - 35


有了lambda表达式和方法引用之后,比较器的实现变得更简洁也更方便了。JDK也给Compararor类引入了不少便利的方法,使得我们可以更流畅的进行比较,下面我们将会看到。

多重比较和流式比较


我们来看下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


可以看到,使用lambda表达式和JDK提供的新的工具类,可以很容易的将Comparator的实现进行组合。下面我们来介绍下Collectors。



未完待续,后续文章请继续关注Java译站

原创文章转载请注明出处:http://it.deepinmind.com

2
2
分享到:
评论
1 楼 hollowmanc 2014-04-08  
别用lambda表达式好吗,年龄大了,看不懂

相关推荐

    Java-Java函数式编程教程

    Java函数式编程是一种编程范式,它强调使用函数作为程序的基本构建块,将计算视为函数的组合,并且尽可能避免改变状态和可变数据。在Java 8及更高版本中,函数式编程得到了官方的大力支持,引入了Lambda表达式、...

    《Java函数式编程》_高清华.zip

    Java函数式编程是一种将函数作为一等公民的编程范式,它强调使用函数来构造程序,减少副作用,提高代码的可读性和可维护性。在Java 8及更高版本中,函数式编程得到了显著增强,引入了Lambda表达式、函数接口、Stream...

    黑马程序员Java函数式编程全套视频教程,Lambda表达式、Stream流、函数式编程一套全通关1

    Java函数式编程是一种高效、简洁的编程范式,它在Java 8中得到了全面支持,大大改变了Java开发人员编写代码的方式。本套黑马程序员的Java函数式编程视频教程涵盖了Lambda表达式、Stream流以及函数式编程的核心概念,...

    java lambda函数式编程完成实例代码

    Java Lambda 函数式编程是Java 8引入的一项重要特性,它极大地简化了处理函数对象的方式,使得代码更加简洁、易读。Lambda表达式是函数式编程的核心,它允许我们将函数作为一个参数传递,或者将函数作为返回值。在这...

    面向Java开发者的函数式编程

    面向Java开发者的函数式编程是一种将函数式编程思想应用于Java开发中的实践方法。函数式编程是一种编程范式,强调程序数据的不可变性、避免副作用,并利用纯函数来构造软件。这种编程风格在处理复杂性、提高代码...

    Java 8函数式编程 范例

    Java 8是Java语言的一个重要版本,引入了大量新特性,其中最具革命性的就是函数式编程的支持。函数式编程在Java 8中的实现主要体现在Lambda表达式、Stream API以及方法引用等方面,这些新特性极大地提高了代码的简洁...

    Java函数式编程.rar

    Java函数式编程是一种编程范式,它将计算视为函数的评估,强调数据的无副作用处理和函数的纯粹性。在Java 8及更高版本中,函数式编程得到了大力支持,引入了Lambda表达式、函数接口和Stream API等核心特性,极大地...

    Java 函数式编程的技巧详解.pdf

    Java函数式编程是一种高效、简洁的编程风格,它在Java 8中得到了广泛支持。函数式编程的核心思想是将计算视为函数的组合,避免副作用,提高代码的可读性和可测试性。在Java中,我们可以利用Lambda表达式、高阶函数、...

    Java8函数式编程1

    【Java 8 函数式编程】是针对Java开发者的一本技术书籍,旨在介绍Java 8引入的函数式编程概念和特性。作者Richard Warburton希望通过这本书挑战传统观念,即函数式编程只适合少数专业人士,而让更多的程序员能够理解...

    java8函数式编程.rar

    Java 8是Java语言的一个重要版本,引入了大量新特性,尤其是函数式编程的概念,显著地改变了Java的编程风格。函数式编程的核心理念是将计算视为函数的组合,而不是指令的序列,它鼓励使用无副作用的纯函数,并支持高...

    Java函数式编程(九):Comparator

    Java函数式编程中的Comparator接口是实现排序和比较的关键工具,特别是在Java 8及以后的版本中,它被强化为一个函数式接口,使得编写比较器变得更加简洁和高效。本篇文章将深入探讨Comparator接口的使用,以及如何...

    java8函数式编程学习源码

    Java 8 是一个重要的 Java 发布版本,引入了许多新特性,极大地改变了 Java 开发的方式,尤其是函数式编程的引入。本资源"java8函数式编程学习源码"显然是为了帮助开发者深入理解并实践这些概念。下面我们将详细探讨...

    Java 8新特性之Lambda与函数式编程.zip

    在Java 8中,函数接口(如Runnable、Comparator、Function等)被用来支持函数式编程风格。函数接口有一个抽象方法,可以被Lambda表达式实例化。例如,`Function, R&gt;` 接口代表一个接收一个类型T的参数并返回类型R的...

    java代码-java函数式编程

    Java函数式编程是Java 8引入的一项重要特性,它极大地扩展了Java语言的功能,使得开发者可以采用更加简洁、高效的方式处理问题。函数式编程的核心思想是将计算视为函数的组合,强调无副作用、纯函数以及数据不可变性...

    Java函数式编程(八):字符串及方法引用

    Java函数式编程是一种编程范式,它强调使用函数作为程序的基本构建块,使得代码更加简洁、可读性强且易于测试。在Java 8及其后续版本中,函数式编程得到了广泛支持,引入了lambda表达式、函数式接口以及一系列用于...

    java-functional:关于Java函数式编程研究的回购

    Java函数式编程是一种编程范式,它强调使用函数作为一等公民,允许将函数作为参数传递,也可以作为返回值。在Java中,自Java 8引入Lambda表达式和Stream API后,函数式编程得到了广泛的关注和应用。这篇文档将深入...

    Functions:Java函数式编程

    总之,Java函数式编程提供了新的工具和技术,使开发者能够在Java环境中实现更高效、更易于维护的代码。通过学习和掌握这些概念,你可以提升代码质量和开发效率,更好地应对现代软件工程的需求。

    深入了解java 8的函数式编程

    Java 8的函数式编程是该语言的重大更新之一,它引入了对函数式编程风格的广泛支持。在Java 8之前,Java主要以其面向对象的特性而闻名,但随着技术的发展,开发人员开始寻求更简洁、更易于测试和并行处理的编程模式,...

    函数式编程:Java SE平台上的函数式编程的完整介绍

    三、Java函数式编程实践 1. **集合操作**:使用Lambda表达式和Stream API,可以对集合进行高效且易于理解的操作,如查找、过滤、排序、映射和减少。 2. **并行处理**:Stream API支持并行流,利用多核处理器提高...

    fnz:Groovy的函数式编程思路

    Groovy是一种强大的、动态的、基于Java平台的脚本语言,它吸收了多种编程范式的优点,其中包括函数式编程。函数式编程是一种编程范式,它强调数据处理和计算作为数学函数,避免改变状态和可变数据。在Groovy中,我们...

Global site tag (gtag.js) - Google Analytics