这篇“怎么使用Lambda表达式简化Comparator的使用问题”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么使用Lambda表达式简化Comparator的使用问题”文章吧。
使用集合时,如果需要实现集合元素排序的话,通常有两种选择,元素本身实现 Comparable
接口或者集合使用 Comparator
对象实现排序。这里来介绍一个 Comparator
这个类。
Comparator 是一个函数式接口,这个可以从它的定义上看出来。它具有这个注解:@FunctionalInterface
。
这个注解标注此接口属于函数式接口,意味着只能有一个抽象方法,但是带你进去看,你会发现两个抽象方法!
int compare(T o1, T o2); boolean equals(Object obj);
这并不是定义错误,而是上面那个注解(@FunctionalInterface
)的文档里有说明:如果接口声明了一个覆盖了 java.lang.Object 的全局方法之一的抽象方法,那么它不会计入接口的抽象方法数量中,因为接口的任何实现都将具有 java.lang.Object 或者其它地方的实现。 因此,它确实是只有一个抽象方法:
int compare(T o1, T o2);
package com.dragon; public class Dog { private String name; private int age; private double weight; public Dog(String name, int age, double weight) { super(); this.name = name; this.age = age; this.weight = weight; } //省略 getter 和 setter 方法,使用 IDE 自动生成比较方便。 //下面两个方法,也都可以自动生成。 @Override public String toString() { return "Dog [name=" + name + ", age=" + age + ", weight=" + weight + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); long temp; temp = Double.doubleToLongBits(weight); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Dog other = (Dog) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (Double.doubleToLongBits(weight) != Double.doubleToLongBits(other.weight)) return false; return true; } }
这个接口虽然是一个函数式接口,但是它的方法可不少!所以,它可以实现非常丰富的排序功能!
**排序规则是按照年龄升序。我这里使用的表达式为:
o1.getAge()-o2.getAge();
如果想要实现反序,调换 o1和o2的位置即可,但是我们不使用这种方式。下面会使用更加方便的方式。
**
1.使用原始的匿名内部类方式,实现 Comparator 对象。
package com.dragon; import java.util.ArrayList; import java.util.Comparator; import java.util.List; public class ComparatorTest { public static void main(String[] args) { //测试使用的集合,下面不再提供,只提供方法的实现。 List<Dog> dogList = new ArrayList<>(); dogList.add(new Dog("小黑", 3, 37.0)); dogList.add(new Dog("二哈", 2, 40.0)); dogList.add(new Dog("泰迪", 1, 8.0)); dogList.add(new Dog("大黄", 4, 55.0)); rawComparator(dogList); } /** * 原始的实现比较器的方法,使用匿名类 * */ static void rawComparator(List<? extends Dog> dogList) { dogList.sort(new Comparator<Dog>() { @Override public int compare(Dog o1, Dog o2) { return o1.getAge()-o2.getAge(); } }); dogList.forEach(System.out::println); } }
说明:这样显得较为繁琐,不够体现代码的简介,下面使用Java8的 lambda 表达式来改写。
运行结果:
2.使用Java8 的lambda 表达式来简化代码
/** * 使用lambda的写法 * */ static void lambda(List<? extends Dog> dogList) { Comparator<Dog> c = (dog1, dog2)->dog1.getAge() - dog2.getAge(); dogList.sort(c); dogList.forEach(System.out::println); }
3.舍去中间变量 c,进一步简化代码
/** * 舍去中间变量 c 的写法 * */ static void lambda2(List<? extends Dog> dogList) { dogList.sort((dog1, dog2)->dog1.getAge() - dog2.getAge()); dogList.forEach(System.out::println); }
总结:基本上,我们第一次接触 lambda 的话,都会去学习写这个表达式,感觉使用起来特别的方便,达到了简化代码的目的。
接口中有一个静态方法 comparing
,使用起来也特别的方便,基本上可以代替上面的那种方式了,它的参数为:Function<? super T, ? extends U> keyExtractor
,这需要传入一个 lambda 表达式。虽然这些方法的定义很复杂,但是使用起来却感觉很简单,复杂的事情都被别人做了。
comparing 方法源码
public static <T, U extends Comparable<? super U>> Comparator<T> comparing( Function<? super T, ? extends U> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)); }
1.使用 comparing方法创建 Comparator 对象
/** * 使用 Java 8 提供的静态方法 comparing 方法, * 再配合方法引用,写法更加简洁了。但是看这个 * 方法,我们可能会疑问排序顺序到底是正序还是逆序呢? * */ static void lambda3(List<? extends Dog> dogList) { dogList.sort(Comparator.comparing(Dog::getAge)); dogList.forEach(System.out::println); }
说明: 通过上面的源码可以看到,c1 和 c2 的位置是固定的(排序是固定的升序方式),它是通过 Function 接口,调用apply方法,生成一个对象,然后调用 compareTo 方法进行比较的。(例如,我们传进去的是age,类型为int,但是通过apply会返回 Integer类型。因为包装类型和 String 类都实现了 Comparable 接口。)
注意: 如果不使用方法引用的话,那么 Dog::getAge
应该被替换为:
(dog1, dog2)->dog1.getAge() - dog2.getAge()
不过,这样做显然就是去了简介性。
2.使用重载的 comparing 方法创建 Comparator 对象
comparing 方法源码
public static <T, U> Comparator<T> comparing( Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator) { Objects.requireNonNull(keyExtractor); Objects.requireNonNull(keyComparator); return (Comparator<T> & Serializable) (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2)); }
说明: 它比上面的 comparing 方法多了一个参数,意味着它可以实现更丰富的比较操作。而且,这个参数也是一个 Comparator 对象。
好了,下面使用这个方法,来实现按照年龄逆序排序。
/** * 解决 lambda3 中的疑问,关于排序顺序的问题。 * 上面那个方法是一个简便方法,它的排序是默认的正序, * 而我们有时会希望逆序排序。所以我们需要使用它的一个重载方法了。 * */ static void lambda4(List<? extends Dog> dogList) { //它的第二个参数,可能会引起困惑,第二个参数的类型就是第一个参数指定的类型(如果是基本类型,则为对应的包装类) dogList.sort(Comparator.comparing(Dog::getAge, (age1, age2)->age2-age1)); dogList.forEach(System.out::println); }
注意: 这里的第二个参数中的 age1 和 age2 的实际类型为 Integer而不是 int,可以直接相减的原因是因为自动拆箱机制,所以这里推荐更换为:
说明: 这样看起来,似乎不够简洁,下面将使用更加简洁的方式来实现逆序排序。
(age1, age2)->age2.compareTo(age1)
运行结果:
3.使用comparing方法的更加简洁形式
Comparator 具有一个静态的方法,它的功能很简单就是逆序。
/** * 相信看完 lambda4 都会感觉还没有 lambda2 的方式简洁呢, * 但是因为正序和逆序只是一个变换顺序的问题,所以它也提 * 供了简洁的实现。当然了,这也与我这里的使用的Dog对象,比较简单有关, * 只看这里的话, 和上面 lambda2 进行比较,优势不太明显。 * */ static void lambda5(List<? extends Dog> dogList) { dogList.sort(Comparator.comparing(Dog::getAge, Comparator.reverseOrder())); dogList.forEach(System.out::println); }
这样,代码就显得简洁多了,当然了,还可以使用一个默认方法当到同样的目的。
/** * 这样也可以 * */ static void lambda55(List<? extends Dog> dogList) { dogList.sort(Comparator.comparing(Dog::getAge).reversed()); dogList.forEach(System.out::println); }
thenComparing 方法源码:
default <U extends Comparable<? super U>> Comparator<T> thenComparing( Function<? super T, ? extends U> keyExtractor) { return thenComparing(comparing(keyExtractor)); }
有时候,会碰到这样的需求,需要使用多种排序方法,而不是单纯的一种。例如使用:姓名、年龄、体重进行排序。这时就需要使用 thenComparing 方法了。
/** * 实现按照多个标准排序:姓名、年龄、体重 * 全部按照自然排序(升序)的顺序 * */ static void lambda7(List<? extends Dog> dogList) { dogList.sort(Comparator .comparing(Dog::getName) .thenComparing(Dog::getAge) .thenComparing(Dog::getWeight)); dogList.forEach(System.out::println); }
说明1: 这里按照三个条件排序是指如果姓名相同了,再按照下一个排序,以此类推,所以你可能看不出来差别(这个结果和按照姓名排序一样,主要是排序的数据不太适合,但我不想换了。)。
说明2: 你仍然可以继续添加更多的排序规则,因为 thenComparing 方法也有重载的方法。
运行结果:
thenComparing 方法的重载方法源码:
default <U> Comparator<T> thenComparing( Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator) { return thenComparing(comparing(keyExtractor, keyComparator)); }
它的第二个方法,也和上面的 comparing 方法作用相同,是自己实现一个key的比较器,这里就不再说明了。
适用于 Int、long 和 double 类型的 thenComapring 方法
default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) { return thenComparing(comparingInt(keyExtractor)); } default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) { return thenComparing(comparingLong(keyExtractor)); } default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) { return thenComparing(comparingDouble(keyExtractor)); }
说明:这几个方法和上面的 thenComparing 方法作用基本相同,但是更加适合处理 int、long和double类型。如果需要排序的类型为这几个,使用这些方法很好,但是我还是喜欢通用的 thenComparing
方法,这里只演示一个 thenComparingDouble
方法:
static void thenComparingDouble() { List<Dog> dogList = new ArrayList<>(); dogList.add(new Dog("小黑", 3, 37.0)); dogList.add(new Dog("二哈", 2, 55.0)); dogList.add(new Dog("泰迪", 1, 8.0)); dogList.add(new Dog("大黄", 2, 40.0)); dogList.sort(Comparator .comparing(Dog::getAge) .thenComparingDouble(Dog::getWeight)); dogList.forEach(System.out::println); }
运行结果:
如果去掉 thenComparingDouble 方法,运行结果为:
注意和上面的结果对比。
适用于 Int、long 和 double 类型的 comapring 方法
这三个方法,也是专门用于处理 int、long 和double类型的,和使用 comparing方法差不多。
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2)); } public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2)); } public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2)); }
这里演示 comparingInt
和 comparingDouble
两个方法的用法:
我感觉没什么区别,可能是我这个测试用例太简单了吧。
/** * comparingToInt * */ static void comparingToInt(List<? extends Dog> dogList) { dogList.sort(Comparator.comparingInt(Dog::getAge)); dogList.forEach(System.out::println); } /** * comparingToDouble * */ static void comparingToDouble(List<? extends Dog> dogList) { dogList.sort(Comparator.comparingDouble(Dog::getWeight)); dogList.forEach(System.out::println); }
static void nullSort() { List<String> strList = new ArrayList<>(); strList.add("dog"); strList.add("cat"); strList.add(null); strList.add("Bird"); strList.add(null); strList.sort(Comparator.comparing(String::length)); strList.forEach(System.out::println); }
运行上面的代码,结果为:
说明:null 值是一个很头疼的问题,所以 Comparator接口也专门提供了处理null值得方法,它们都是对 null 值友好的方法(null-friendly)。
//null 值在前面。 //Returns a null-friendly comparator that considers null to be less than non-null. public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) { return new Comparators.NullComparator<>(true, comparator); } //null 值在后面。 //Returns a null-friendly comparator that considers null to be greater than non-null. public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) { return new Comparators.NullComparator<>(false, comparator); }
因此,对于含有null值的元素进行排序,可以这样做:
/** * 含有 null 值得元素排序 * */ public static void nullValueSort() { List<String> strList = new ArrayList<>(); strList.add("dog"); strList.add("cat"); strList.add(null); strList.add("Bird"); strList.add(null); //我一开始以为是一个字符常量呢?但是一想不对劲,原来是一个静态常量比较器。 //这个是 String 类的比较器:CASE_INSENSITIVE_ORDER //null 值在前排序 strList.sort(Comparator.nullsFirst(String.CASE_INSENSITIVE_ORDER)); strList.forEach(System.out::println); System.out.println("===================分隔符===================="); //null 值在后排序 strList.sort(Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)); strList.forEach(System.out::println); }
运行结果:
注:摆脱了,烦人的NullPointerException,哈哈。
in other words, it returns a comparator that imposes the reverse of the natural ordering on a collection of objects that implement the Comparable interface。
换言之,它返回一个比较器,该比较器对实现可比较接口的对象集合施加与自然顺序相反的顺序。
说明: 由于它是默认方法,所以必须由比较器对象本身来调用,正好可以实现逆序操作。可以在创建比较器后继续调用这个方法,就可以实现逆序了。但是要注意它调用的顺序,它和下面这个 reverseOrder 方法还是不一样的,下面这个方法是静态方法,可以通过类直接调用。注意,用法上的区别就是了。
default Comparator<T> reversed() { return Collections.reverseOrder(this); }
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() { return Collections.reverseOrder(); }
如果直接使用这个方法,创建比较器对象的话,那么集合里面的元素必须使用 Comparable 接口。
static void reverseSort() { List<String> strList = new ArrayList<>(); strList.add("dog"); strList.add("cat"); strList.add("Bird"); strList.sort(Comparator.reverseOrder()); strList.forEach(System.out::println); }
注意:这里有一个很有趣的地方,这个方法Comparator.reverseOrder()
无法使用方法引用改写:Comparator::reverseOrder
,具体原因我看了,但是不是太理解,就不说了。
运行结果:
补充: 晚上思考了一下,结合别人的答案,这里其实也是不难理解的。自所以不能使用方法引用,是因为它根本就不是 lambda 表达式。Lambda 表达式需要依赖一个函数式接口,也就是 Comparator 接口。它的作用就是一个简化,所以它的需要的参数就是 int compare(T o1, T o2);
的方法中的参数。
所以,如果这样写的话,会报一个错误。
The type Comparator does not define reverseOrder(String, String) that is applicable here
strList.sort(Comparator::reverseOrder);
因此,上面这个写法就是错误的了。它并不能使用lambda的形式改写。
reverseOrder 和 reversed联合使用
static void reverseSort() { List<String> strList = new ArrayList<>(); strList.add("dog"); strList.add("cat"); strList.add("Bird"); Comparator c = Comparator.reverseOrder().reversed(); strList.sort(c); strList.forEach(System.out::println); }
说明:上面这个例子我不会添加泛型了,我无论怎么添加都是错误的,但是如果不添加泛型的话,那么编译就能通过了。但是这个东西的泛型似乎很奇怪,我也不太明白了,但是这个方法很有趣,反序的反序又是正序了。(这里存粹是娱乐一下,但是好像发现了好玩的东西。)
运行结果:
naturalOrder
定制排序里面居然有一个方法名叫做自然排序,这个方法感觉很有趣。但是使用的话,需要抑制一下 unchecked
警告。
@SuppressWarnings("unchecked") public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() { return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE; }
方法注释里面说明了:
@param <T> the {@link Comparable} type of element to be compared。
参数必须是 Comparable类型的,即实现 Comparable 接口。
自然排序:
/** * Comparator 实现自然排序 * */ @SuppressWarnings("unchecked") static void lambda6(List<? extends Dog> dogList) { dogList.sort((Comparator<Dog>) Comparator.naturalOrder()); dogList.forEach(System.out::println); }
如果直接运行这个方法会产生问题,必须要先实现 Comparable接口才行,并重写 compareTo
方法。
@Override public int compareTo(Dog o) { return age-o.age; }
运行结果:
以上就是关于“怎么使用Lambda表达式简化Comparator的使用问题”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。