JDK8新特性:使用stream、Comparator和Method Reference实现集合的优雅排序

原创 2017年02月05日 13:23:55

大家对java接口Comparator和Comparable都不陌生,JDK8里面Comparable还和以前一样,没有什么改动;但是Comparator在之前基础上增加了很多static和default方法。本文主要结合JDK的stream编程,学习下Comparator。阅读本文需要一些前置知识,可以参考如下文章。

JDK8新特性:接口的静态方法和默认方法
http://blog.csdn.net/aitangyong/article/details/54134385


JDK8新特性:函数式接口@FunctionalInterface的使用说明

http://blog.csdn.net/aitangyong/article/details/54137067


JDK8新特性:lambda入门
http://blog.csdn.net/aitangyong/article/details/54317539

JDK8新特性:使用Method References实现方法复用,简化lambda表达式
http://blog.csdn.net/aitangyong/article/details/54586197


可以使用Stream.sort对集合进行排序,sort有2个重载方法,区别如下。




// Student实现Comparable接口,默认按照id升序排列
public class Student implements Comparable<Student>{

	private int id;

	private int age;

	private String name;

	private Address address;

	public Student(int id, int age, String name, Address address) {
		this.id = id;
		this.age = age;
		this.name = name;
		this.address = address;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}

	@Override
	public String toString() {
		return "Student [id=" + id + ", age=" + age + ", name=" + name + ", address=" + address + "]";
	}

	@Override
	public int compareTo(Student o) {
		return this.id - o.id;
	}

}


stream().sorted()/Comparator.naturalOrder()/Comparator.reverseOrder(),要求元素必须实现Comparable接口
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class TestComparator {

	public static void main(String[] args) {
		List<Student> students = buildStudents();
		
		// 按照默认顺序排序
		List<Student> ascList1 = students.stream().sorted().collect(Collectors.toList());
		System.out.println(ascList1);
		
		// 按照自然序排序(其实就是默认顺序)
		List<Student> ascList2 = students.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList());
		System.out.println(ascList2);
		
		// 按照默认顺序的相反顺序排序
		List<Student> descList = students.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
		System.out.println(descList);

	}

	private static List<Student> buildStudents() {
		List<Student> students = new ArrayList<>();
		students.add(new Student(10, 20, "aty", new Address("d")));
		students.add(new Student(1, 22, "qun", new Address("c")));
		students.add(new Student(1, 26, "Zen", new Address("b")));
		students.add(new Student(5, 23, "aty", new Address("a")));
		return students;
	}

}



如果Student没有实现Comparable接口,效果如下:



接下来测试,都不要求Student实现Comparable接口,这里直接给出Student和Address实体类。

public class Student {

	private int id;

	private int age;

	private String name;

	private Address address;

	public Student(int id, int age, String name, Address address) {
		this.id = id;
		this.age = age;
		this.name = name;
		this.address = address;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}

	@Override
	public String toString() {
		return "Student [id=" + id + ", age=" + age + ", name=" + name + ", address=" + address + "]";
	}

}
public class Address {
	private String address;

	public Address(String address) {
		super();
		this.address = address;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	@Override
	public String toString() {
		return "Address [address=" + address + "]";
	}
	

}


Comparator.comparing(Function keyExtractor)生成1个Comparator对象,要求keyExtractor.apply()返回值一定要实现Comparable接口。比如下面代码extractIdWay1和extractIdWay2都是等价的,从Student对象中提取id属性,而id是int类型(Integer实现了Comparable)
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class TestComparator {

	public static void main(String[] args) {
		List<Student> students = buildStudents();

		// 使用lambda表达式创建Function对象
		Function<Student, Integer> extractIdWay1 = (student) -> student.getId();
		
		// 使用方法引用简化lambda
		Function<Student, Integer> extractIdWay2 = Student::getId;
		
		// Comparator.comparing(Function keyExtractor)
		Comparator<Student> byId = Comparator.comparing(extractIdWay2);
		
		// 升序
		List<Student> ascList = students.stream().sorted(byId).collect(Collectors.toList());
		System.out.println(ascList);
		
		// 降序
		List<Student> descList = students.stream().sorted(byId.reversed()).collect(Collectors.toList());
		System.out.println(descList);

	}

	private static List<Student> buildStudents() {
		List<Student> students = new ArrayList<>();
		students.add(new Student(10, 20, "aty", new Address("d")));
		students.add(new Student(1, 22, "qun", new Address("c")));
		students.add(new Student(1, 26, "Zen", new Address("b")));
		students.add(new Student(5, 23, "aty", new Address("a")));
		return students;
	}

}


由于Student.getAddress()返回的对象没有实现Comparable接口,所以不能通过Comparator.comparing()创建一个Comparator对象。



如果我们想安装Address(没有实现Comparable接口)排序怎么办呢?使用另一种形式的comparing方法:


import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class TestComparator {

	public static void main(String[] args) {
		List<Student> students = buildStudents();

		Comparator<Address> cmpAddr = Comparator.comparing(Address::getAddress);
		Comparator<Student> byAddress = Comparator.comparing(Student::getAddress, cmpAddr);
		List<Student> sortedAddressList = students.stream().sorted(byAddress).collect(Collectors.toList());
		System.out.println(sortedAddressList);
	}

	private static List<Student> buildStudents() {
		List<Student> students = new ArrayList<>();
		students.add(new Student(10, 20, "aty", new Address("d")));
		students.add(new Student(1, 22, "qun", new Address("c")));
		students.add(new Student(1, 26, "Zen", new Address("b")));
		students.add(new Student(5, 23, "aty", new Address("a")));
		return students;
	}

}


这种形式的comparing()接收2个参数,第一个参数提取要排序的key,第二个参数指定排序的Comparator。自己指定比较器,可以灵活定制比较逻辑。比如,我们想实现字符串不区分大小写比较。
//getName()返回String本身已经实现了Comparable,但是我们可以自己传递一个不区分大小写的比较器
Comparator<Student> byName = Comparator.comparing(Student::getName, String.CASE_INSENSITIVE_ORDER);
List<Student> sortedNameList = students.stream().sorted(byName).collect(Collectors.toList());
System.out.println(sortedNameList);

comparingDouble()、comparingLong()、comparingInt()不过是comparing()更具体的版本,使用方式相同。
public static void main(String[] args) {
	List<Student> students = buildStudents();

	Comparator<Student> byAge1 = Comparator.comparingInt(Student::getAge);
	Comparator<Student> byAge2 = Comparator.comparing(Student::getAge);
	List<Student> sortedAgeList1 = students.stream().sorted(byAge1).collect(Collectors.toList());
	List<Student> sortedAgeList2 = students.stream().sorted(byAge2).collect(Collectors.toList());
	System.out.println(sortedAgeList1);
	System.out.println(sortedAgeList2);
}

private static List<Student> buildStudents() {
	List<Student> students = new ArrayList<>();
	students.add(new Student(10, 20, "aty", new Address("d")));
	students.add(new Student(1, 22, "qun", new Address("c")));
	students.add(new Student(1, 26, "Zen", new Address("b")));
	students.add(new Student(5, 23, "aty", new Address("a")));
	return students;
}

Comparator.nullsFirst()和Comparator.nullsLast(),前面我们创建的Student列表中没有null,如果有null的话,上面的代码都会抛异常。而这2个方法就是用来处理null的,一个认为null比所有非null都小,一个认为比所有都大。
public class TestComparator {

	public static void main(String[] args) {
		List<Student> students = buildStudents();
		Comparator<Student> nullNotAllowed = Comparator.comparing(Student::getId);
		Comparator<Student> allowNullComparator = Comparator.nullsFirst(nullNotAllowed);
		
		// 正常排序
		List<Student> result1 = students.stream().sorted(allowNullComparator).collect(Collectors.toList());
		System.out.println(result1);
		
		// 抛异常
		List<Student> result2 = students.stream().sorted(nullNotAllowed).collect(Collectors.toList());
		System.out.println(result2);

	}

	private static List<Student> buildStudents() {
		List<Student> students = new ArrayList<>();
		students.add(new Student(10, 20, "aty", new Address("d")));
		students.add(new Student(1, 22, "qun", new Address("c")));
		students.add(new Student(1, 26, "Zen", new Address("b")));
		students.add(new Student(5, 23, "aty", new Address("a")));
		students.add(null);
		return students;
	}

}



至此Comparator的static方法已经介绍完毕,接下来我们看下它的default方法。

reversed()前面已经介绍了,返回一个新的比较器(排序顺序相反)

thenComparing()系列方法与comparing()使用方法类似





如果我们先按照id排序,id相等的话再按照name排序,那么可以这样写。

public static void main(String[] args) {
	List<Student> students = buildStudents();
	
	// id升序
	Comparator<Student> byIdASC = Comparator.comparing(Student::getId);
	
	// named不分区大小写降序
	Comparator<Student> byNameDESC = Comparator.comparing(Student::getName, String.CASE_INSENSITIVE_ORDER)
			.reversed();

	// 联合排序
	Comparator<Student> finalComparator = byIdASC.thenComparing(byNameDESC);
	
	List<Student> result = students.stream().sorted(finalComparator).collect(Collectors.toList());
	System.out.println(result);
}

private static List<Student> buildStudents() {
	List<Student> students = new ArrayList<>();
	students.add(new Student(10, 20, "aty", new Address("d")));
	students.add(new Student(1, 22, "qun", new Address("c")));
	students.add(new Student(1, 26, "Zen", new Address("b")));
	students.add(new Student(5, 23, "aty", new Address("a")));
	return students;
}



版权声明:本文为博主原创文章,未经博主允许不得转载。

Java8 Stream排序

/* * 排序 * sorted():自然排序(Comparable) * sorted(Comparator com):定制排序(Comparator) */ List list3 = Ar...
  • u013063153
  • u013063153
  • 2017年08月06日 00:25
  • 957

原来 Java8 Stream 中的排序是插入排序

写了小程序,验证 Java8 Steam sort是如何实现 package com.pnp.tryJ8col; import java.util.Arrays; import java.uti...
  • span76
  • span76
  • 2016年08月25日 16:09
  • 7751

JDK 8 之 Stream sorted() 示例

原文链接:http://www.concretepage.com/java/jdk-8/java-8-stream-sorted-example 国外对Java8一系列总结的不错, 翻译过来给大家共...
  • lsgqjh
  • lsgqjh
  • 2017年03月19日 17:34
  • 9787

Java流(Stream)操作实例-筛选、映射、查找匹配

Java流(Stream)操作实例-筛选、映射、查找匹配
  • zyhlwzy
  • zyhlwzy
  • 2017年11月24日 15:26
  • 487

Java 8系列之Stream的基本语法详解

概述继Java 8系列之Lambda表达式之后,我们来了解Stream。Stream 是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易...
  • IO_Field
  • IO_Field
  • 2017年02月10日 16:27
  • 8221

java 8 lambda—第三章—Stream

第三章:streams java8的变化是为了帮助我们编写更好的代码,新的核心代码库作为这种变化中的一部分,我们这章开始讨论新的库,变化最主要集中在集合api以及新添加的特性streams.stre...
  • yangnianbing110
  • yangnianbing110
  • 2014年05月25日 23:37
  • 8945

java8 stream map根据value排序

java8 stream map根据value排序
  • ht99582
  • ht99582
  • 2017年09月21日 16:34
  • 983

java8的Stream的排序

java8 Stream的各种排序
  • ziwenCSDN
  • ziwenCSDN
  • 2017年11月24日 15:50
  • 93

初探Lambda表达式-Java多核编程【2】并行与组合行为

今天又翻了一下书的目录,第一章在这之后就结束了。也就是说,这本书所涉及到的新的知识已经全部点到了。 书的其余部分就是对这几个概念做一些基础知识的补充以及更深层次的实践。 最后两个小节的内容较少,所...
  • gm48mg0m
  • gm48mg0m
  • 2017年02月20日 17:24
  • 226

[Java 8] (3) 字符串遍历和Comparator接口

遍历一个字符串 在Java 8中,CharSequence接口新添加了一个方法叫做chars(),方法的签名是这个样子的: public default IntStream chars() ...
  • dm_vincent
  • dm_vincent
  • 2014年10月23日 09:16
  • 10188
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:JDK8新特性:使用stream、Comparator和Method Reference实现集合的优雅排序
举报原因:
原因补充:

(最多只允许输入30个字)