基于Java8详细介绍了Stream流的含义和大部分API方法的使用,以及Optional容器、并行流等Java8的新特性!
public interface Stream< T >
extends BaseStream>
Java8开始提供了Stream API,这些API方法是用函数式编程方式在对一批数据集合进行复杂操作的工具,简称“流”。流的特点如下:
先看看一个常见的普通集合操作和流操作的代码对比,体验流式编程的连续性以及简单性:
public class StreamFrist {
public static void main(String[] args) {
//学生集合,学生有age-年龄 ,name-名字,score-分数,三个属性
List<Student> students = new ArrayList<>();
students.add(new Student(10, 55, "小花"));
students.add(new Student(13, 100, "小华"));
students.add(new Student(9, 85, "晓华"));
students.add(new Student(8, 70, "肖华"));
//我们需要筛选出成绩大于等于60的学生名字
//使用普通集合操作
ArrayList<String> nameList1 = new ArrayList<>();
for (Student student : students) {
if (student.getScore() >= 60) {
nameList1.add(student.getName());
}
}
//使用流,不同的操作都是链式的,非常适合人类的思维,而不需要考虑迭代、判断操作
List<String> nameList2 = students.stream()
//筛选出成绩大于等于60的学生
.filter(student -> student.getScore() >= 60)
//收集学生名字
.map(Student::getName)
//返回结果
.collect(toList());
}
}
Stream API定义了许多流的操作,它们大概可以分为两类:
案例演示:
@Test
public void test() {
//学生集合,学生有age-年龄 ,name-名字,score-分数,三个属性
List<Student> students = new ArrayList<>();
students.add(new Student(10, 55, "小花"));
students.add(new Student(13, 100, "小华"));
students.add(new Student(9, 85, "晓华"));
students.add(new Student(8, 70, "肖华"));
//没有终端操作的流,不会执行中间操作
students.stream()
//筛选出成绩大于等于60的学生
.filter(student -> {
System.out.println("中间操作" + student.getScore());
return student.getScore() >= 60;
})
//收集学生名字
.map(Student::getName);
//有终端操作的流,才会执行
//没有终端操作的流,不会执行中间操作
students.stream()
//筛选出成绩大于等于60的学生
.filter(student -> {
System.out.println("终端操作" + student.getScore());
return student.getScore() >= 60;
})
//收集学生名字
.map(Student::getName)
//collect是一个终端操作
.collect(toList());
}
要想使用流,需要做三件事:
我们可以发现流的使用和Java构建者模式类似,构建者模式使用一系列操作设置属性和配置,最后调用一个build方法时,对象才被真正创建。而流操作同样使用一系列中间操作,最后调用一个终端操作方法,该方法触发中间操作的执行并获取最终的结果!
Stream提供了非常多的API方法,这些方法可以根据不用的作用进行分类讲解!
有以下常见方式获取流:
获取流的案例:
/**
* @author lx
*/
public class CreateTest {
/**
* @author lx
*/
class Filter {
private int x;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public Filter(int x) {
this.x = x;
}
public Filter() {
}
@Override
public String toString() {
return "Filter{" +
"x=" + x +
'}';
}
}
/**
* 从集合获取流
*/
@Test
public void test() {
List<Filter> filters = new ArrayList<>();
filters.add(new Filter(0));
filters.add(new Filter(3));
filters.add(new Filter(9));
filters.add(new Filter(8));
//从集合
Stream<Filter> stream = filters.stream();
stream.forEach(System.out::println);
}
/**
* 从数组获取流
*/
@Test
public void test1() {
Filter[] filArr = new Filter[]{
new Filter(1),
new Filter(3),
new Filter(9),
new Filter(8)};
//从数组
Stream<Filter> stream = Arrays.stream(filArr);
stream.forEach(System.out::println);
}
/**
* 从文件获取流
*/
@Test
public void test2() {
//读取文件的所有行的流
try (Stream<String> lines = Files.lines(Paths.get("target/classes/lines.txt"))) {
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* iterate获取10个偶数
*/
@Test
public void iterate() {
Stream.iterate(0, n -> n + 2)
//取前十个数据(后面会讲,这叫做“筛选”)
.limit(10)
.forEach(System.out::println);
}
/**
* iterate获取下标
* 采用Stream通过下标遍历集合
*/
@Test
public void iterate2() {
ArrayList<Object> objects = new ArrayList<>();
objects.add(1);
objects.add(3);
objects.add(2);
objects.add(4);
Stream.iterate(0, i -> i + 1)
//截取前集合长度的数据
.limit(objects.size())
.forEach(i -> System.out.println(i + "-->" + objects.get(i)));
}
/**
* generate获取10个随机数
*/
@Test
public void generate() {
Stream.generate(Math::random)
.limit(10)
.forEach(System.out::println);
}
//复杂数据生成
/**
* iterate获取10个斐波那契数
*/
@Test
public void iterateFibonacci() {
//斐波那契数列的规律:F(0)=0,F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
//我们初始化数组new int[]{0, 1}
//后续的数组的第1个元素是前一个数组的第2个元素,后续的数组的第2个元素是前一个数组的第1、2个元素的和
//这样实际上生成的数组如下:
//new int[]{0, 1}
//new int[]{1, 1}
//new int[]{1, 2}
//new int[]{2, 3}
//new int[]{3, 5}
//new int[]{5, 8}
//new int[]{8, 13}
//new int[]{13, 21}
//new int[]{21, 34}
//取每个数组的第一个元素就是斐波那契数
Stream.iterate(new int[]{
0, 1},
t -> new int[]{
t[1], t[0] + t[1]})
//生成10个数组
.limit(10)
//获取每个数组的第一个元素(后面会讲,这叫做“映射”)
.map(t -> t[0])
.forEach(System.out::println);
}
/**
* Stream.of
*/
@Test
public void of() {
Stream.of(1, 2, 3, "11").forEach(System.out::println);
}
}
Stream API提供了对流元素的筛选操作,既有普通的条件筛选filter,也有特殊的筛选,比如distinct去重、limit和skip截取!
筛选操作是一个中间操作!
Stream< T > filter(Predicate< ? super T > predicate)
filter是使用最广泛的筛选操作,它接受一个断言,返回由与此给定断言匹配的此流的元素组成的流。
Stream< T > distinct()
返回一个由该流的不同元素(根据元素的equals方法)组成的流。对于有序流,选择不同的元素是稳定的(对于重复的元素,首先在遇到顺序中出现的元素被保留。)对于无序流,不能保证稳定性。
Stream< T > limit(long maxSize)
返回由该流的前maxSize元素组成的流,limit会最多截取流元素的前maxSize个。
Stream< T > skip(long n)
在丢弃流的前n个元素后,返回由该流的前n元素之后的元素组成的流。 如果此流包含少于n元素,那么将返回一个空流。
使用案例:
/**
* @author lx
*/
public class FilterTest {
List<Student> students = new ArrayList<>();
@Before
public void test() {
students.add(new Student(10, 55, "小花"));
students.add(new Student(13, 100, "小华"));
students.add(new Student(9, 85, "晓华"));
students.add(new Student(8, 70, "肖华"));
students.add(new Student(8, 70, "肖华"));
}
@Test
public void filter() {
System.out.println("filter筛选成绩大于等于70的学生");
//filter筛选成绩大于等于70的学生
students.stream().filter(student -> student.getScore() >= 70).forEach(System.out::println);
System.out.println("filter+distinct筛选成绩大于等于70的学生,且去除重复数据");
//filter+distinct筛选成绩大于等于70的学生,且去除重复数据
students.stream().filter(student -> student.getScore() >= 70).distinct().forEach(System.out::println);
System.out.println("limit最多截取前2个数据");
//limit最多截取前2个数据
students.stream().filter(student -> student.getScore() >= 70).limit(2).forEach(System.out::println);
System.out.println("skip丢弃前2个数据");
//skip丢弃前2个数据
students.stream().filter(student -> student.getScore() >= 70).skip(2).forEach(System.out::println);
System.out.println("skip丢弃前1个数据,limit最多截取前1个数据");
students.stream().filter(student -> student.getScore() >= 70).skip(1).limit(1).forEach(System.out::println);
}
static class Student {
private int age;
private int score;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(int age, int score, String name) {
this.age = age;
this.score = score;
this.name = name;
}
public Student(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", score=" + score +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
if (getAge() != student.getAge()) return false;
if (getScore() != student.getScore()) return false;
return getName() != null ? getName().equals(student.getName()) : student.getName() == null;
}
@Override
public int hashCode() {
int result = getAge();
result = 31 * result + getScore();
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
return result;
}
}
}
集合支持排序,Stream API也提供了对流元素进行排序操作的方法!
排序操作是一个中间操作!
Stream< T > sorted()
返回由此流的元素组成的流,根据自然顺序排序如果流元素不是Comparable类型 ,则执行终端操作时抛出ClassCastException。
Stream< T > sorted(Comparator< ? super T > comparator)
返回由该流的元素组成的流,根据提供的Comparator进行排序。对于有序流,排序稳定。 对于无序的流,不能保证稳定性。
使用案例:
/**
* @author lx
*/
public class SortedTest {
/**
* 自然排序
*/
@Test
public void sorted() {
Stream.of(2, 0, 3, 7, 5).sorted().forEach(System.out::println);
}
/**
* 指定比较规则
*/
@Test
public void sortedCom() {
students.stream()
//首先根据score比较排序,相等时再根据id比较排序
.sorted(Comparator.comparingInt(Student::getScore).thenComparing(Student::getId))
.forEach(System.out::println);
}
List<Student> students = new ArrayList<>();
@Before
public void before() {
students.add(new Student(2, 100, "小花"));
students.add(new Student(1, 100, "小华"));
students.add(new Student(3, 85, "晓华"));
students.add(new Student(4, 70, "肖华"));
}
static class Student {
private int id;
private int score;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(int id, int score, String name) {
this.id = id;
this.score = score;
this.name = name;
}
public Student(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", score=" + score +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
if (getId() != student.getId()) return false;
if (getScore() != student.getScore()) return false;
return getName() != null ? getName().equals(student.getName()) : student.getName() == null;
}
@Override
public int hashCode() {
int result = getId();
result = 31 * result + getScore();
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
return result;
}
}
}
Stream API支持对每一个流元素应用相同的函数,同时将函数计算结果生成一个新的流,类似于映射操作。这种操作在实际开发中非常的有用,比如流元素是一批对象,现在我们需要获取全部对象的id集合,那么我们就可以使用Stream提供的map和flatMap这两个映射方法来完成。
映射操作是一个中间操作!
< R > Stream< R > map(Function< ? super T,? extends R > mapper)
返回由给定函数应用于此流的全部元素的结果组成的流。返回的流的元素数据类型就是函数返回的结果的数据类型。
使用案例:
/**
* @author lx
*/
public class MapTest {
List<Student> students = new ArrayList<>();
@Before
public void before() {
students.add(new Student(1, 55, "小花"));
students.add(new Student(2, 100, "小华"));
students.add(new Student(3, 85, "晓华"));
students.add(new Student(4, 70, "肖华"));
}
@Test
public void test() {
students.stream()
//获取每一个学生对象的学生id集合
.map(Student::getId)
//这是一个终端消费操作,后面会讲
.forEach(System.out::println);
List<Integer> collect = students.stream()
//获取每一个学生对象的学生id集合
.map(Student::getId)
//这是一个终端收集操作,后面会讲
.collect(toList());
System.out.println(collect);
/*将小写字母转换为大写*/
List<String> collected = Stream.of("a", "b", "C")
.map(String::toUpperCase)
.collect(toList());
System.out.println(collected);
}
static class Student {
private int id;
private int score;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(int id, int score, String name) {
this.id = id;
this.score = score;
this.name = name;
}
public Student(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"age=" + id +
", score=" + score +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
if (getId() != student.getId()) return false;
if (getScore() != student.getScore()) return false;
return getName() != null ? getName().equals(student.getName()) : student.getName() == null;
}
@Override
public int hashCode() {
int result = getId();
result = 31 * result + getScore();
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
return result;
}
}
}
< R > Stream< R > flatMap(Function< ? super T,? extends Stream extends R >> mapper)
返回由通过将提供的映射函数应用于每个元素而产生的映射流的内容来替换该流的每个元素的结果的流。每个映射的流在其内容被放入此流之后是closed 。(如果映射的流是null则使用空的流)。
简单的说,flatMap函数的返回值必须是Stream< XXX >流类型,随后返回的结果流会被合并成一个流,那么最终的流元素类型就是XXX。而前面的map函数的返回值不一定是Stream< XXX >流类型,一般来说应该返回一个实体类型XXX,那么最终的流元素类型就是XXX;如果map返回Stream< XXX >流类型,那么最终的流元素类型就是Stream< XXX >。
即map返回的结果将使用一个流来收集,最后返回这个流,而flatMap一定是返回一个流,随后将所有返回的流和并成一个流返回,里面的元素自然也到一块儿去了,这里也就是“扁平化”的由来!
因此flatMap常被用在来将不同批次的数据拆分成单个元素随后合并的操作!
使用案例:
/**
* @author lx
*/
public class FlatMapTest {
List<String> words = new ArrayList<>();
@Before
public void before() {
words.add("hello");
words.add("word");
}
/**
* 将words数组中的元素再按照字符拆分,然后字符去重,最终达到["h", "e", "l", "o", "w", "r", "d"]
*/
@Test
public void test2() {
//如果用map,可以看到流就成了Stream>类型,那么最后收集的元素就是Stream类型
List<Stream<String>> mapList = words.stream()
.map(word -> Arrays.stream(word.split("")))
.distinct()
.collect(toList());
System.out.println(mapList);
//如果使用flatMap,那么就可以达到想要的结果
List<String> flatMapList = words.stream()
.flatMap(word -> Arrays.stream(word.split("")))
.distinct()
.collect(Collectors.toList());
System.out.println(flatMapList);
}
/**
* 原因
*/
@Test
public void test() {
//可以看到Arrays.stream方法返回的就是Stream类型
Stream<String> stream = Arrays.stream("word".split(""));
//如果使用map,那么返回的流就是一个Stream>类型,流元素也是一个流……
//因为map将Arrays.stream方法返回的Stream作为流元素使用一个流进行收集,随后返回这个流
Stream<Stream<String>> streamStream = words.stream().map(word -> Arrays.stream(word.split("")));
//如果使用flatMap,那么返回的流就是一个Stream类型,这才是正常的类型
//因为flatMap将Arrays.stream方法返回的Stream流进行了合并,随后返回合并之后的大流
Stream<String> stringStream = words.stream().flatMap(word -> Arrays.stream(word.split("")));
}
}
Stream< T > peek(Consumer< ? super T > action)
返回由该流的元素组成的流,另外在从生成的流中消耗元素时对每个元素执行提供的消费操作。该方法可以用来查看流水线中间某个点的元素,当前也可以修改一些元素的属性,但是要自己保证线程安全!
查看操作是一个中间操作!
使用案例:
/**
* @author lx
*/
public class PeekTest {
@Test
public void test(){
System.out.println(Stream.of(1, 2, 3, 4, 5)
.peek(System.out::println)
.map(i -> i + 1)
.collect(Collectors.toList()));
System.out.println(Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList()));
}
}
在集合、数组操作中,我们常常需要判断集合是否包含或者不包含具有某些规则的元素。Stream API同样提供了allMatch、anyMatch、noneMatch方法来完成规则匹配操作。
匹配操作是一个终端操作!
boolean allMatch(Predicate< ? super T > predicate)
如果流的所有元素都匹配提供的断言,那么返回true,如果流是空的或者有任何不匹配的元素就返回false。
boolean noneMatch(Predicate< ? super T > predicate)
如果流的所有元素都不匹配提供的断言或者流是空的,那么返回true,如果有任何至少一个匹配的元素就返回false。
boolean anyMatch(Predicate< ? super T > predicate)
如果流存在至少一个元素匹配提供的断言,那么返回true,如果或者流是空的或者任何元素都不匹配就返回false。
注意:流是否为空是最先判断的条件。空集合、空数组、空文件等等来源都将构建一个空流。
使用案例:
/**
* @author lx
*/
public class MatchTest {
List<Student> students = new ArrayList<>();
@Before
public void before() {
students.add(new Student(1, 55, "小花"));
students.add(new Student(2, 100, "小华"));
students.add(new Student(3, 85, "晓华"));
students.add(new Student(4, 70, "肖华"));
}
/**
* 空流测试
*/
@Test
public void testNull() {
System.out.println(Stream.of().anyMatch(i -> true));
System.out.println(Stream.of().anyMatch(i -> false));
System.out.println(Stream.of().noneMatch(i -> true));
System.out.println(Stream.of().noneMatch(i -> false));
System.out.println(Stream.of().anyMatch(i -> true));
System.out.println(Stream.of().anyMatch(i -> false));
}
/**
* match测试
*/
@Test
public void testMatch() {
//集合中是否有姓名为 小华 的学生
System.out.println(students.stream().anyMatch(student -> "小华".equals(student.getName())));
//集合中是否有姓名为 小华1 的学生
System.out.println(students.stream().anyMatch(student -> "小华1".equals(student.getName())));
//集合中是否所有的学生分数都大于55
System.out.println(students.stream().allMatch(student -> student.getScore() > 55));
//集合中是否所有的学生分数都大于等于55
System.out.println(students.stream().allMatch(student -> student.getScore() >= 55));
//集合中是否所有的学生分数都不小于55
System.out.println(students.stream().noneMatch(student -> student.getScore() < 55));
//集合中是否所有的学生分数都不小于等于55
System.out.println(students.stream().noneMatch(student -> student.getScore() <= 55));
}
static class Student {
private int id;
private int score;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(int id, int score, String name) {
this.id = id;
this.score = score;
this.name = name;
}
public Student(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"age=" + id +
", score=" + score +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
if (getId() != student.getId()) return false;
if (getScore() != student.getScore()) return false;
return getName() != null ? getName().equals(student.getName()) : student.getName() == null;
}
@Override
public int hashCode() {
int result = getId();
result = 31 * result + getScore();
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
return result;
}
}
}
Stream提供了findFirst查找第一个流元素和findAny查找任意一个流元素的两个方法,但是没有提供其他的查找操作!
查找操作是一个终端操作!
Optional< T > findFirst()
返回此流的第一个元素的Optional。如果流为空,则返回一个空的Optional,如果源数据没有顺序,则可能会返回任何元素。
Optional< T > findAny()
返回此流的任意一个元素的Optional。如果流为空,则返回一个空的Optional,如果源数据没有顺序,则可能会返回任何元素。这个方法主要是为了并行流的效率考虑的,如果是单线程下也是返回第一个流元素!
使用案例:
@Test
public void find() {
Optional<String> first = Stream.of("xx", "ccc", "eee").findFirst();
Optional<String> any = Stream.of("xx", "ccc", "eee").findAny();
//通常我们这样使用
//如果first存在
if (first.isPresent()) {
String s = first.get();
}
//如果any存在
if (any.isPresent()) {
String s = any.get();
}
//或者这样用
first.ifPresent(System.out::println);
}
find方法没啥好说的,但是我们注意它的返回的结果是一个Optional类型的对象,Optional是Java8新增的类,算作Java8的新特性,这个类实际上是希望帮助程序员避免空指针异常而添加的!
Optional< T >类(java.util.Optional)是一个容器类,代表一个值存在或不存在。在上面的代码中,findAny可能什么元素都没找到,如果此时返回一个原始类型的null,那么粗心的程序员可能就忘记null校验而导致程序异常了,因此返回一个一定不为null的Optional对象,这个对象将真正的返回值包装起来,通过一系列方法获取值或者进行其他操作!
方法 | 说明 |
public boolean isPresent() | 如果存在值,则返回 true ,否则为 false 。 |
public T get() | 如果 Optional中存在值,则返回值,否则抛出 NoSuchElementException 。 |
public void ifPresent(Consumer super T> consumer) | 如果存在值,则使用该值作为参数调用指定的消费者,否则不执行任何操作。如果值存在且consumer为空,那么抛出NullPointerException。 |
public T orElse(T other) | 返回值(如果存在),否则返回一个默认值。 |
public T orElseGet(Supplier extends T> other) | 返回值(如果存在),否则调用 other并返回该调用的结果。 |
public T orElseThrow(Supplier extends X> exceptionSupplier) | 返回包含的值(如果存在),否则抛出由Supplier参数返回的异常。如果没有值,则抛出指定异常;如果没有值且exceptionSupplier为空,则抛出NullPointerException。 |
public static < T > Optional< T > empty() | 返回一个空的Optional实例。 |
public static < T > Optional< T > of(T value) | 返回具有非空值的Optional。如果value为null,那么抛出NullPointerException。 |
public static < T > Optional< T > ofNullable(T value) | 返回具有指定值的Optional。value可以为null。 |
public Optional< T > filter(Predicate super T> predicate) | 如果一个值存在,并且该值给定的断言相匹配时,返回一个 包含此值的Optional,否则返回一个空的 Optional 。如果断言为null,那么抛出NullPointerException。 |
public < U > Optional< U > map(Function super T,? extends U> mapper) | 如果存在值,则应用提供的映射函数,如果结果不为空,则返回一个包含此结果的Optional。否则返回一个空的Optional 。如果函数为null,那么抛出NullPointerException。 |
public < U > Optional< U > flatMap(Function super T,Optional< U >> mapper) | 如果一个值存在,则应用提供的映射函数,返回的结果一定是一个Optional,这个Optional包含了真实结果,如果真实结果为null,则返回一个空的Optional。如果函数为null或者返回null结果,那么抛出NullPointerException。 |
使用案例:
@Test
public void optional() {
System.out.println("filter");
System.out.println(Optional.of(3).filter(integer -> integer > 2));
System.out.println(Optional.of(3).filter(integer -> integer > 3));
System.out.println("map");
System.out.println(Optional.of(3).map(integer -> integer + 2));
System.out.println(Optional.of(2).map(integer -> integer + 2));
System.out.println("flatMap");
System.out.println(Optional.of(3).flatMap(integer -> Optional.of(integer + 2)));
System.out.println(Optional.of(2).flatMap(integer -> Optional.of(integer + 2)));
//map对比,可以看到和Stream的flatMap与map方法差不多
System.out.println(Optional.of(2).map(integer -> Optional.of(integer + 2)));
}
在集合、数组操作中,我们有时候需要对集合的元素进行一个整体的统计,特别是求和、求差、求最值、计数等等计算操作,这些操作需要使用每一个元素,最后返回唯一的一个结果,这样的操作称为“归纳”,Stream API提供了reduce、max、min等方法来完成归纳操作!
归纳操作是一个终端操作!
Optional< T > reduce(BinaryOperator< T > accumulator)
没有初始值,返回一个Optional对象,以表明结果可能不存在。
accumulator:二元操作器,操作两个元素得到一个新元素。第一次计算时两个参数分别取前两个元素,计算的值将会作为下一次计算的第一个参数,后面的元素作为第二个参数,以此类推,最终会返回计算结果。
T reduce(T identity, BinaryOperator< T > accumulator)
identity:一个初始值,最终会返回同类型的值。
accumulator:二元操作器,操作两个元素得到一个新元素。串行模式下:第一次计算时第一个参数取初始值,第二个参数取第一个元素,计算的值将会作为下一次计算的第一个参数,后面的元素作为第二个参数,以此类推,最终会返回计算结果。并行模式下:对初始值和每一个元素应用一次accumulator函数(初始值为第一个参数,流元素为第二个参数),会得到对应个数的结果,最后通过accumulator再一次整合,即对第一轮accumulator获得的结果在应用一轮accumulator!
< U > U reduce(U identity,BiFunction< U,? super T,U > accumulator,BinaryOperator< U > combiner)
identity:一个初始值,最终会返回同类型的值。
accumulator: 一个二元函数,操作两个元素得到一个新元素。串行模式下:第一次计算时第一个参数取初始值,第二个参数取第一个元素,计算的值将会作为下一次计算的第一个参数,后面的元素作为第二个参数,以此类推,最终会返回计算结果。并行模式下:对初始值和每一个元素应用一次accumulator函数(初始值为第一个参数,流元素为第二个参数),会得到对应元素个数的结果,最后通过combiner整合!
combiner:二元操作器,并行模式下第三个参数才会生效。在并行模式下初始值会和每一个流元素进行一次accumulator计算(初始值为第一个参数,流元素为第二个参数),随后会通过combiner对这些计算结果进行整合,最后返回整合后的结果!
使用案例(在“并行流”章节部分,有更加详细的测试):
/**
* @author lx
*/
public class ReduceTest {
/**
* 求差
* 验证二元操作器的参数关系
*/
@Test
public void subtract() {
//Optional< T > reduce(BinaryOperator< T > accumulator);
//求差 前一个计算的值减去后一个元素
Optional<Integer> subtractReduce1 = Stream.of(1, 2, 3, 4, 7)
.reduce((i, j) -> i - j);
subtractReduce1.ifPresent(System.out::println);
//Optional< T > reduce(BinaryOperator< T > accumulator);
//求差 后一个元素减去前一个计算的值
Optional<Integer> subtractReduce2 = Stream.of(1, 2, 3, 4, 7)
.reduce((i, j) -> j - i);
subtractReduce2.ifPresent(System.out::println);
}
@Test
public void test() {
//Optional< T > reduce(BinaryOperator< T > accumulator);
//求和
Optional<Integer> sumReduce = Stream.of(1, 2, 3, 4, 7)
//使用方法引用Integer::sum来表示求和的意图
.reduce(Integer::sum);
sumReduce.ifPresent(System.out::println);
//Optional< T > reduce(BinaryOperator< T > accumulator);
//求最值
Optional<Integer> maxReduce = Stream.of(1, 2, 3, 4, 7)
//使用方法引用Integer::max来表示求最值的意图
.reduce(Integer::max);
maxReduce.ifPresent(System.out::println);
//T reduce(T identity, BinaryOperator< T > accumulator);
//求最值,初始值10
System.out.println(Stream.of(1, 2, 3, 4, 7)
//使用方法引用Integer::max来表示求最值的意图
.reduce(10, Integer::max));
//T reduce(T identity, BinaryOperator< T > accumulator);
//计数
System.out.println(Stream.of(1, 2, 3, 4, 7)
.map(d -> 1)
.reduce(0, Integer::sum));
//更简单的方法,内部使用的数值流,内部实际上是特性化的流操作,后面会讲
System.out.println(Stream.of(1, 2, 3, 4, 7).count());
}
/**
* < U > U reduce(U identity,BiFunction accumulator,BinaryOperator< U > combiner)
* 复杂计算
*/
@Test
public void test2() {
//串行模式下无效
System.out.println(Stream.of(1, 2, 3, 4, 7).reduce(2, Integer::sum, Integer::sum));
System.out.println(Stream.of(1, 2, 3, 4, 7).reduce(2, Integer::sum, Integer::max));
//并行模式下有效
//初始值为2,首先对每一个元素应用一个计算accumulator,得到结果:3,4,5,6,9,最后combiner整合这些值:得到27
System.out.println(Stream.of(1, 2, 3, 4, 7).parallel().reduce(2, Integer::sum, Integer::sum));
//初始值为2,首先对每一个元素应用一个计算accumulator,得到结果:3,4,5,6,9,最后combiner整合这些值:得到9
System.out.println(Stream.of(1, 2, 3, 4, 7).parallel().reduce(2, Integer::sum, Integer::max));
//初始值为2,首先对每一个元素应用一个计算accumulator,得到结果:1,2,3,4,7,最后combiner整合这些值:得到1
System.out.println(Stream.of(1, 2, 3, 4, 7).parallel().reduce(0, Integer::sum, Integer::min));
//初始值为2,首先对每一个元素应用一个计算accumulator,得到结果:-1,0,1,2,5,最后combiner整合这些值:得到7
System.out.println(Stream.of(1, 2, 3, 4, 7).parallel().reduce(2, (i, j) -> j - i, Integer::sum));
//初始值为2,首先对每一个元素应用一个计算accumulator,得到结果:1,0,-1,-2,-5,最后combiner整合这些值:得到-7
System.out.println(Stream.of(1, 2, 3, 4, 7).parallel().reduce(2, (i, j) -> i - j, Integer::sum));
System.out.println(Stream.of(1, 2, 3, 4, 7).parallel().reduce(2, (i, j) -> {
//查看线程,多线程
System.out.println(Thread.currentThread().getName());
return j - i;
}
, Integer::sum));
System.out.println(Stream.of(1, 2, 3, 4, 7).reduce(2, (i, j) -> {
//查看线程,单线程
System.out.println(Thread.currentThread().getName());
return j - i;
}
, Integer::sum));
}
}
集合中只能存储对象,对于基本类型也会涉及到拆装箱操作。而在上面的归纳操作中,比如对于数值的求和、求差等操作,实际上也涉及到基本类型的自动拆箱和装箱的操作,因为Stream内部也是以对象类型存储数据的。另外对于一些简单的操作求和求最值操作Stream并没有提供现成的API,主要是因为Stream面向全部类型,如果是普通对象类型那肯定不能求和或者求最值的。
Java8有什么改进吗?当然有,Java8为我们提供了基于Stream的三个特性化流IntStream、LongStream、DoubleStream,它们只接受指定类型的数据,底层基于基本类型,直接操作基本类型,避免了拆装箱操作的性能开销,并且提供了sum、min等快捷归纳的方法!实际上Stream的count方法内部就是先将对象流转换为LongStream数值流,然后调用sum方法。
Stream流转换特性化流的方法如下,这些方法都是中间操作:
IntStream mapToInt(ToIntFunction super T> mapper)
返回一个IntStream ,其中包含将给定函数应用于此流的元素的结果。
LongStream mapToLong(ToLongFunction super T> mapper)
返回一个LongStream ,其中包含将给定函数应用于此流的元素的结果。
DoubleStream mapToDouble(ToDoubleFunction super T> mapper)
返回一个DoubleStream ,其中包含将给定函数应用于此流的元素的结果。
特性化流除了包含Strean的大部分方法之外,特有的一些常用方法如下,以IntStream为例子:
< U > Stream< U > mapToObj(IntFunction extends U> mapper)
返回一个对象值Stream ,其中包含将给定函数应用于此流的元素的结果。该方法将特性化流转换为对象流。
Stream< Integer > boxed()
对每个元素进行装箱操作,返回一个Stream< Integer >。该方法将特性化流转换为对象流。
OptionalDouble average()
返回此流的元素的算术平均值的OptionalDouble,如果此流为空,则返回一个空的OptionalDouble。
OptionalInt max()
返回此流的最大元素的OptionalInt,如果此流为空,则返回一个空的OptionalInt。
OptionalInt min()
返回此流的最小元素的OptionalInt,如果此流为空,则返回一个空的OptionalInt。
long count()
返回此流中的元素数。
static IntStream range(int startInclusive,int endExclusive)
返回有序的IntStream,范围是[startInclusive endExclusive)。
static IntStream rangeClosed(int startInclusive,int endInclusive)
返回有序的IntStream,范围是[startInclusive endInclusive]。
相应的,Optional也有三个对应的特性化类OptionalInt、OptionalLong、 OptionalDouble,同样避免了拆装箱操作,他们的方法和Optional都差不多,但是不接受null元素。
使用案例:
/**
* @author lx
*/
public class Specialtest {
@Test
public void test() {
//求和
System.out.println(Stream.of(1, 2, 3, 4, 7).mapToInt(x -> x).sum());
//求最大值
Stream.of(1, 2, 3, 4, 7).mapToInt(x -> x).max().ifPresent(System.out::println);
//求最小值
Stream.of(1, 2, 3, 4, 7).mapToInt(x -> x).min().ifPresent(System.out::println);
//求算术平均数
Stream.of(1, 2, 3, 4, 7).mapToInt(x -> x).average().ifPresent(System.out::println);
}
}
在流水线处理完毕之后,我们总想将此时的流元素使用一个数据结构收集起来,或者得到一个汇总的结果。此时,我们就可以利用Stream中非常强大的collect收集操作了,通过collect操作,我们可以得到自己想要的数据结构或者一个结果。
实际上,只需要将一个通用的Collector收集器传递给collect方法,收集器定义了如何收集元素以及返回什么样的结果,我们几乎可以自己实现前面已经学习的所有终端操作,比如reduce、max、count……!而Java8经帮我们预定义了很多收集器,他们都可以通过Collectors工厂类的静态方法获取,通常我们都会直接将Collectors的所有静态方法都导入进来。当然我们也可以创建属于自己的收集器,完成更加特殊的功能!
collect方法几乎支持reduce方法的所有归纳操作,比如计数、求和、最值等等,并且Collectos已经为这些归纳操作预定义好了收集器,我们直接使用即可!
Collectos的静态方法中定义了计数的收集器!
public static < T > Collector
counting()
返回一个Collector,用于计算输入元素的数量,如果没有元素,则结果为0。相当于:reducing(0L, e -> 1L, Long::sum)操作。
使用案例:
/**
* 计数操作
*/
@Test
public void count() {
//前面我们讲过三种计数操作
//Stream的count方法
System.out.println(Stream.of(1, 2, 3, 4, 7).count());
//特性化流的count方法
System.out.println(Stream.of(1, 2, 3, 4, 7).mapToInt(x -> x).count());
//map+reduce实现计数
System.out.println(Stream.of(1, 2, 3, 4, 7)
.map(d -> 1)
.reduce(0, Integer::sum));
//现在,collect操作也提供计数功能,并且已经提供了预定于的收集器
//counting()静态方法就返回一个用于计数的收集器
System.out.println(Stream.of(1, 2, 3, 4, 7).collect(counting()));
}
Collectos的静态方法中定义了求最值的收集器!
public static < T > Collector
> minBy(Comparator super T> comparator)
返回一个Collector,它根据给定的Comparator产生最小元素,返回Optional< T > 。
public static < T > Collector
> maxBy(Comparator super T> comparator)
返回一个Collector,它根据给定的Comparator产生最大元素,返回Optional< T > 。
使用案例:
/**
* 最值
*/
@Test
public void max_min() {
//前面我们讲过二种最值操作
//特性化流的方法
Stream.of(1, 2, 3, 4, 7).mapToInt(x -> x).max().ifPresent(System.out::println);
//reduce方法
Optional<Integer> maxReduce = Stream.of(1, 2, 3, 4, 7)
//使用方法引用Integer::max来表示求最值的意图
.reduce(Integer::max);
maxReduce.ifPresent(System.out::println);
//现在,collect操作也提供求最值功能,并且已经提供了预定于的收集器
//minBy(Comparator) 静态方法就返回一个用于求最小值的收集器
Stream.of(1, 2, 3, 4, 7).collect(minBy(Integer::compareTo)).ifPresent(System.out::println);
//maxBy(Comparator) 静态方法就返回一个用于求最大值的收集器
Stream.of(1, 2, 3, 4, 7).collect(maxBy(Integer::compareTo)).ifPresent(System.out::println);
Stream.of(1, 2, 3, 4, 7).collect(maxBy(Comparator.comparingInt(x -> x))).ifPresent(System.out::println);
}
Collectors的静态方法中预定义了汇总操作的收集器,比如求和、求平均数,还有一个能够返回所有参数的收集器!
public static < T > Collector
summingInt(ToIntFunction super T> mapper)
返回一个Collector,它产生应用于输入元素的int值函数的和。如果没有元素,结果为0。
public static < T > Collector
summingLong(ToLongFunction super T> mapper)
返回一个Collector,它产生应用于输入元素的long值函数的和。如果没有元素,结果为0。
public static < T > Collector
summingDouble(ToDoubleFunction super T> mapper)
返回一个Collector,它产生应用于输入元素的double值函数的和。如果没有元素,结果为0。
public static < T > Collector
averagingInt(ToIntFunction super T> mapper)
返回一个Collector ,它产生应用于输入元素的int值函数的算术平均值。 如果没有元素,结果为0。
public static < T > Collector
averagingLong(ToLongFunction super T> mapper)
返回一个Collector ,它产生应用于输入元素的long值函数的算术平均值。 如果没有元素,结果为0。
public static < T > Collector
averagingDouble(ToDoubleFunction super T> mapper)
返回一个Collector ,它产生应用于输入元素的double值函数的算术平均值。 如果没有元素,结果为0。
public static < T > Collector
summarizingInt(ToIntFunction super T> mapper)
返回一个Collector ,返回int数据的总数,总和、平均值、最大值和最小值的汇总。
public static < T > Collector
summarizingLong(ToLongFunction super T> mapper)
返回一个Collector ,返回long数据的总数,总和、平均值、最大值和最小值的汇总。
public static < T > Collector
summarizingDouble(ToDoubleFunction super T> mapper)
返回一个Collector ,返回double数据的总数,总和、平均值、最大值和最小值的汇总。
使用案例:
/**
* 汇总
*/
@Test
public void sum() {
//前面我们讲过二种求和操作
//特性化流的方法
System.out.println(Stream.of(1, 2, 3, 4, 7).mapToInt(x -> x).sum());
//reduce方法
Stream.of(1, 2, 3, 4, 7)
//使用方法引用Integer::sum来表示求和的意图
.reduce(Integer::sum)
.ifPresent(System.out::println);
//现在,collect操作也提供求和功能,并且已经提供了预定于的收集器
//summingInt(ToIntFunction) 静态方法就返回一个用于求int数据和的收集器,返回int类型的值
System.out.println(Stream.of(1, 2, 3, 4, 7)
.collect(summingInt(x -> x)));
//summingLong(ToLongFunction) 静态方法就返回一个用于求long数据和的收集器,返回long类型的值
System.out.println(Stream.of(1, 2, 3, 4, 7)
.collect(summingLong(x -> x)));
//summingDouble(ToDoubleFunction) 静态方法就返回一个用于求double数据和的收集器,返回double类型的值
System.out.println(Stream.of(1, 2, 3, 4, 7)
.collect(summingDouble(x -> x)));
//现在,count操作还提供求平均数功能,并且已经提供了预定于的收集器
//averagingInt(ToIntFunction) 静态方法就返回一个用于求int数据算术平均数的收集器
System.out.println(Stream.of(1, 2, 3, 4, 7)
.collect(averagingInt(x -> x)));
//averagingLong(ToLongFunction) 静态方法就返回一个用于求long数据算术平均数的收集器
System.out.println(Stream.of(1, 2, 3, 4, 7)
.collect(averagingLong(x -> x)));
//averagingDouble(ToDoubleFunction) 静态方法就返回一个用于求double数据算术平均数的收集器
System.out.println(Stream.of(1, 2, 3, 4, 7)
.collect(averagingDouble(x -> x)));
//summarizingInt(ToIntFunction) 静态方法就返回一个用于求int数据的总和、平均值、最大值和最小值的收集器
//返回IntSummaryStatistics对象,内部收集了所有的值
System.out.println(Stream.of(1, 2, 3, 4, 7)
.collect(summarizingInt(x -> x)));
//summarizingLong(ToLongFunction) 静态方法就返回一个用于求long数据的总和、平均值、最大值和最小值的收集器
//返回LongSummaryStatistics对象,内部收集了所有的值
System.out.println(Stream.of(1, 2, 3, 4, 7)
.collect(summarizingLong(x -> x)));
//summarizingDouble(ToDoubleFunction) 静态方法就返回一个用于求double数据的总和、平均值、最大值和最小值的收集器
//返回DoubleSummaryStatistics对象,内部收集了所有的值
System.out.println(Stream.of(1, 2, 3, 4, 7)
.collect(summarizingDouble(x -> x)));
}
Collectors预定义了用于连接所有字符串元素的收集器!
public static Collector
joining()
返回一个Collector ,将输入元素按照顺序连接成为一个String。
public static Collector
joining(CharSequence delimiter)
返回一个Collector ,将输入元素按照顺序连接成为一个String,每一个元素的字符串使用delimiter分隔。
public static Collector
joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
返回一个Collector ,将输入元素按照顺序连接成为一个String,每一个元素的字符串使用delimiter分隔。在开始连接之前加上prefix,在连接完成之后加上suffix。
当然,前两个方法也可以直接使用Java8在String API中新增的join方法替代。使用案例:
/**
* 连接字符串
*/
@Test
public void string() {
//join()
System.out.println(Stream.of("校花","小花","晓华","笑话").collect(joining()));
System.out.println(String.join("", "校花","小花","晓华","笑话"));
//joining(CharSequence delimiter)
System.out.println(Stream.of("校花","小花","晓华","笑话").collect(joining("——")));
System.out.println(String.join("——", "校花","小花","晓华","笑话"));
//joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
System.out.println(Stream.of("校花","小花","晓华","笑话").collect(joining("——","开始:","。结束")));
}
实际上,上面的预定义归纳操作内部都是调用的reducing工厂方法,或者说是reducing归纳操作的一些特殊情况,当上面的预定于定义方法不能满足我们的要求,此时我们可以使用reducing定义一个自己的归纳操作!
public static < T > Collector
> reducing(BinaryOperator< T > op)
op:二元操作器,操作两个元素得到一个新元素。第一次计算时两个参数分别取前两个元素,计算的值将会作为下一次计算的第一个参数,后面的元素作为第二个参数。
返回一个Collector。没有初始值,返回一个Optional对象,以表明结果可能不存在。
public static < T > Collector
reducing(T identity, BinaryOperator< T > op)
identity:一个初始值,最终会返回同类型的值。
op:二元操作器,操作两个元素得到一个新元素。第一次计算时第一个参数取初始值,第二个参数取第一个元素,计算的值将会作为下一次计算的第一个参数,后面的元素作为第二个参数。
public static
Collector reducing(U identity, Function super T,? extends U> mapper, BinaryOperator< U > op)
identity:一个初始值,最终会返回同类型的值。
mapper: 一个一元函数,操作一个流元素得到一个结果。
op:二元操作器,操作两个元素得到一个新元素。第一次计算时第一个参数取初始值,第二个参数取第一个元素应用mapper的结果,计算的值将会作为下一次计算的第一个参数,后面元素应用mapper的结果作为第二个参数。
这样看起来,collect方法和reduce的方法有很多归纳功能都是重合的,但是它们仍然有显著的区别,最重要的就是reduce本身被作为不可变的归纳,每一次操作都应该由两个值生成新的值而不是改变原值,但是collect则作为可变归纳,支持并行的操作!
使用案例:
/**
* 自定义
* 看起来和reduce的功能差不多
*/
@Test
public void custom() {
//自定义求和操作
System.out.println(Stream.of(1, 2, 3, 4, 7)
.collect(reducing(0, Integer::sum)));
//自定义计数操作
System.out.println(Stream.of(1, 2, 3, 4, 7)
.collect(reducing(0, x -> 1, Integer::sum)));
//求最值,初始值10
System.out.println(Stream.of(1, 2, 3, 4, 7)
//使用方法引用Integer::max来表示求最值的意图
.collect(reducing(10, Integer::max)));
//求最值
Stream.of(1, 2, 3, 4, 7)
//使用方法引用Integer::max来表示求最值的意图
.collect(reducing(Integer::max))
.ifPresent(System.out::println);
}
collect最重要的收集操作之一就是将流元素转换为集合,当然Collectos的静态方法中也定义了将流元素输出到集合的收集器!有了这些方法,我们可以方便快捷的实现集合转换!
public static < T > Collector
> toList()
返回一个Collector,将所有流元素按顺序收集到一个List中。实际类型是一个ArrayList,不安全!
public static < T > Collector
> toSet()
返回一个Collector,将所有流元素按顺序收集到一个Set中。实际类型是一个HashSet,不安全!
public static
> Collector toCollection(Supplier< C > collectionFactory)
collectionFactory:一个生产者,用于指定集合实际类型,只要是Collection体系中的集合!
返回一个Collector,将所有流元素按顺序收集到一个Collection中。实际类型通过传入的参数自己指定,只要是Collection体系中的集合!
public static
Collector > toMap(Function super T,? extends K> keyMapper, Function super T,? extends U> valueMapper)
keyMapper:map的key的函数,参数就是每一个流元素。
valueMapper:map的value的函数,参数就是每一个流元素。
返回一个Collector ,它将元素收集到一个Map ,其键和值是将所提供的映射函数应用于输入元素(流元素)的结果。实际类型是一个HashMap,不安全!
注意:如果有重复的key,那么将抛出IllegalStateException异常!
public static
Collector > toMap(Function super T,? extends K> keyMapper, Function super T,? extends U> valueMapper, BinaryOperator< U > mergeFunction)
keyMapper:map的key的函数,参数就是每一个流元素。
valueMapper:map的value的函数,参数就是每一个流元素。
mergeFunction:一个二元操作器,处理遇到key冲突的情况!第一个参数是前一个冲突的key的value,第二个参数是后一个冲突的key的value。返回一个结果,作为value。
返回一个Collector,它将元素收集到一个Map,其键和值是将所提供的映射函数应用于输入元素(流元素)的结果。实际类型是一个HashMap,不安全!指定冲突解决策略!
public static
> Collector toMap(Function super T,? extends K> keyMapper, Function super T,? extends U> valueMapper, BinaryOperator< U > mergeFunction, Supplier< M > mapSupplier)
keyMapper:map的key的函数,参数就是每一个流元素。
valueMapper:map的value的函数,参数就是每一个流元素。
mergeFunction:一个二元操作器,处理遇到key冲突的情况!第一个参数是前一个冲突的key的value,第二个参数是后一个冲突的key的value。返回一个结果,作为value。
mapSupplier:一个生产者,用于指定集合实际类型,只要是Map体系中的集合!
返回一个Collector,它将元素收集到一个Map,其键和值是将所提供的映射函数应用于输入元素(流元素)的结果。实际类型通过传入的参数自己指定,只要是Map体系中的集合!指定冲突解决策略!
public static
Collector > toConcurrentMap(Function super T,? extends K> keyMapper, Function super T,? extends U> valueMapper)
keyMapper:map的key的函数,参数就是每一个流元素。
valueMapper:map的value的函数,参数就是每一个流元素。
返回一个Collector ,它将元素收集到一个ConcurrentMap,其键和值是将所提供的映射函数应用于输入元素(流元素)的结果。实际类型是一个ConcurrentHashMap,线程安全!
注意:如果有重复的key,那么将抛出IllegalStateException异常!
public static
Collector > toConcurrentMap(Function super T,? extends K> keyMapper, Function super T,? extends U> valueMapper, BinaryOperator< U > mergeFunction)
keyMapper:map的key的函数,参数就是每一个流元素。
valueMapper:map的value的函数,参数就是每一个流元素。
mergeFunction:一个二元操作器,处理遇到key冲突的情况!第一个参数是前一个冲突的key的value,第二个参数是后一个冲突的key的value。返回一个结果,作为value。
返回一个Collector ,它将元素收集到一个ConcurrentMap,其键和值是将所提供的映射函数应用于输入元素(流元素)的结果。实际类型是一个ConcurrentHashMap,线程安全!指定冲突解决策略!
public static
> Collector toConcurrentMap(Function super T,? extends K> keyMapper, Function super T,? extends U> valueMapper, BinaryOperator< U > mergeFunction, Supplier< M > mapSupplier)
keyMapper:map的key的函数,参数就是每一个流元素。
valueMapper:map的value的函数,参数就是每一个流元素。
mergeFunction:一个二元操作器,处理遇到key冲突的情况!第一个参数是前一个冲突的key的value,第二个参数是后一个冲突的key的value。返回一个结果,作为value。
mapSupplier:一个生产者,用于指定集合实际类型,只要是ConcurrentMap体系中的集合!
返回一个Collector,它将元素收集到一个ConcurrentMap,其键和值是将所提供的映射函数应用于输入元素(流元素)的结果。实际类型通过传入的参数自己指定,只要是ConcurrentMap体系中的集合!指定冲突解决策略!
/**
* @author lx
*/
public class CollectCollection {
/**
* collection
*/
@Test
public void collection() {
//收集全部学生分数ArrayList集合
List<Integer> scoreArrayList = students.stream().map(Student::getScore).collect(toList());
//收集全部学生分数HashSet集合
Set<Integer> scoreHashSet = students.stream().map(Student::getScore).collect(toSet());
//收集全部学生分数LinkedHashSet集合
Set<Integer> scoreLinkedHashSet = students.stream().map(Student::getScore).collect(toCollection(LinkedHashSet::new));
System.out.println(scoreArrayList);
System.out.println(scoreHashSet);
System.out.println(scoreHashSet);
}
/**
* map
*/
@Test
public void map() {
//public static Collector> toMap(Function super T,? extends K> keyMapper, Function super T,? extends U> valueMapper)
//注意:如果有重复的key,那么将抛出IllegalStateException异常
//收集全部学生分数 名字-分数 的HashMap,将会抛出异常
//Map nameStoreHashMap = students.stream().collect(toMap(Student::getName, Student::getScore));
//收集全部学生分数 id-分数 的HashMap,不会抛出异常
Map<Integer, Integer> idStoreHashMap = students.stream().collect(toMap(Student::getId, Student::getScore));
System.out.println(idStoreHashMap);
//public static Collector> toMap(Function super T,? extends K> keyMapper, Function super T,? extends U> valueMapper, BinaryOperator< U > mergeFunction)
//指定key冲突解决策略
//收集全部学生分数 名字-分数 的HashMap,取前一个冲突的value
Map<String, Integer> nameStoreHashMap1 = students.stream().collect(toMap(Student::getName, Student::getScore, (x
, y) -> {
System.out.println(x);
System.out.println(y);
return x;
}));
//收集全部学生分数 名字-分数 的HashMap,取后一个冲突的value
Map<String, Integer> nameStoreHashMap2 = students.stream().collect(toMap(Student::getName, Student::getScore, (x
, y) -> {
System.out.println(x);
System.out.println(y);
return y;
}));
System.out.println(nameStoreHashMap1);
System.out.println(nameStoreHashMap2);
//public static > Collector toMap(Function super T,? extends K> keyMapper, Function super T,? extends U> valueMapper, BinaryOperator< U > mergeFunction, Supplier< M > mapSupplier)
//指定key冲突解决策略以及指定Map类型LinkedHashMap
LinkedHashMap<Integer, String> scoreNameLinkedHashMap = students.stream()
.collect(toMap(Student::getScore, Student::getName, (x, y) -> y, LinkedHashMap::new));
//指定key冲突解决策略以及指定Map类型,TreeMap
TreeMap<Integer, String> scoreNameTreeMap = students.stream()
.collect(toMap(Student::getScore, Student::getName, (x, y) -> y, TreeMap::new));
System.out.println(scoreNameLinkedHashMap);
System.out.println(scoreNameTreeMap);
}
/**
* ConcurrentMap
*/
@Test
public void concurrentMap() {
//public static Collector> toConcurrentMap(Function super T,? extends K> keyMapper, Function super T,? extends U> valueMapper)
//注意:如果有重复的key,那么将抛出IllegalStateException异常
//收集全部学生分数 名字-分数 的HashMap,将会抛出异常
//ConcurrentMap collect = students.stream().collect(toConcurrentMap(Student::getName,Student::getScore));
//收集全部学生分数 id-分数 的ConcurrentHashMap,不会抛出异常
ConcurrentMap<Integer, Integer> idStoreHashMap = students.stream().collect(toConcurrentMap(Student::getId, Student::getScore));
System.out.println(idStoreHashMap);
//public static Collector> toMap(Function super T,? extends K> keyMapper, Function super T,? extends U> valueMapper, BinaryOperator< U > mergeFunction)
//指定key冲突解决策略
//收集全部学生分数 名字-分数 的ConcurrentHashMap,取前一个冲突的value
ConcurrentMap<String, Integer> nameStoreHashMap1 = students.stream().collect(toConcurrentMap(Student::getName, Student::getScore, (x
, y) -> {
System.out.println(x);
System.out.println(y);
return x;
}));
//收集全部学生分数 名字-分数 的HashMap,取后一个冲突的value
ConcurrentMap<String, Integer> nameStoreHashMap2 = students.stream().collect(toConcurrentMap(Student::getName, Student::getScore, (x
, y) -> {
System.out.println(x);
System.out.println(y);
return y;
}));
System.out.println(nameStoreHashMap1);
System.out.println(nameStoreHashMap2);
//public static > Collector toMap(Function super T,? extends K> keyMapper, Function super T,? extends U> valueMapper, BinaryOperator< U > mergeFunction, Supplier< M > mapSupplier)
//指定key冲突解决策略以及指定Map类型ConcurrentHashMap
ConcurrentMap<Integer, String> scoreNameConcurrentHashMap = students.stream()
.collect(toConcurrentMap(Student::getScore, Student::getName, (x, y) -> y, ConcurrentHashMap::new));
//指定key冲突解决策略以及指定Map类型,ConcurrentSkipListMap
ConcurrentMap<Integer, String> scoreNameConcurrentSkipListMap = students.stream()
.collect(toConcurrentMap(Student::getScore, Student::getName, (x, y) -> y, ConcurrentSkipListMap::new));
System.out.println(scoreNameConcurrentHashMap);
System.out.println(scoreNameConcurrentSkipListMap);
}
List<Student> students = new ArrayList<>();
@Before
public void before() {
students.add(new Student(1, 55, "小花"));
students.add(new Student(2, 100, "小华"));
students.add(new Student(3, 85, "晓华"));
students.add(new Student(4, 70, "肖华"));
students.add(new Student(5, 70, "小小"));
students.add(new Student(6, 66, "小小"));
students.add(new Student(7, 60, "小夏"));
students.add(new Student(8, 77, "花花"));
}
static class Student {
private int id;
private int score;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(int id, int score, String name) {
this.id = id;
this.score = score;
this.name = name;
}
public Student(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"age=" + id +
", score=" + score +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
if (getId() != student.getId()) return false;
if (getScore() != student.getScore()) return false;
return getName() != null ? getName().equals(student.getName()) : student.getName() == null;
}
@Override
public int hashCode() {
int result = getId();
result = 31 * result + getScore();
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
return result;
}
}
}
public static
Collector mapping(Function super T,? extends U> mapper, Collector super U,A,R> downstream)
mapper:对每一个流元素都应用的一元函数,返回的值将被作为downstream收集器的初始流元素!
downstream:一个收集器,对转换之后的流元素进行收集操作。
返回一个Collector。mapping方法首先将元素流元素分别应用一元函数转换为相应的数据,然后再使用一个收集器对这些数据进行收集。类似于Stream的map+collect方法的结合。
在上面的集合操作中可能用不到该方法,因为我们可以先直接调用Srteam的map方法转换之后再收集。但是在后面的分组和分区方法中,却是非常的有用,后面会讲到!
public static
Collector collectingAndThen(Collector downstream, Function finisher)
downstream:一个收集器,对流元素进行收集操作。
finisher:对每一个收集结果都应用的一元函数,返回的值将被作为最终返回值!
返回一个Collector。collectingAndThen方法首先对流元素进行收集,然后再使用一个一元函数对这些收集结果进行运算,最后返回运算后的结果。
可以看到,mapping和collectingAndThen方法是相反的,前一个先转换然后收集,后一个先收集然后转换!
/**
* @author lx
*/
public class MappingTest {
/**
* mapping
* 类似于 map + collect
*/
@Test
public void mappingTest() {
//收集全部学生分数ArrayList集合
System.out.println(students.stream().collect(mapping(Student::getScore, toList())));
//收集全部学生分数HashSet集合
System.out.println(students.stream().collect(mapping(Student::getScore, toSet())));
//收集全部学生分数LinkedHashSet集合
LinkedHashSet<Integer> collect = students.stream().collect(mapping(Student::getScore,
toCollection(LinkedHashSet::new)));
System.out.println(collect);
//收集全部学生分数的总和
System.out.println(students.stream().collect(mapping(Student::getScore, reducing(0, Integer::sum))));
}
/**
* collectingAndThen
*/
@Test
public void collectingAndThenTest() {
//收集全部学生总数
Integer collect = students.stream().collect(collectingAndThen(toList(), List::size));
System.out.println(collect);
//收集全部学生分数的总和
Integer collect2 = students.stream().collect(collectingAndThen(mapping(Student::getScore, reducing(Integer::sum)), Optional::get));
System.out.println(collect2);
}
List<Student> students = new ArrayList<>();
@Before
public void before() {
students.add(new Student(1, 55, "小花"));
students.add(new Student(2, 100, "小华"));
students.add(new Student(3, 85, "晓华"));
students.add(new Student(4, 70, "肖华"));
students.add(new Student(5, 70, "小小"));
students.add(new Student(6, 66, "小小"));
students.add(new Student(7, 60, "小夏"));
students.add(new Student(8, 77, "花花"));
}
static class Student {
private int id;
private int score;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(int id, int score, String name) {
this.id = id;
this.score = score;
this.name = name;
}
public Student(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"age=" + id +
", score=" + score +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
if (getId() != student.getId()) return false;
if (getScore() != student.getScore()) return false;
return getName() != null ? getName().equals(student.getName()) : student.getName() == null;
}
@Override
public int hashCode() {
int result = getId();
result = 31 * result + getScore();
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
return result;
}
}
}
前面的集合操作中,我们可以通过toMap等方法将每一个元素通过指定的函数映射成为map的key和value,但有时候我们需要对一批数据根据某些属性进行一个总体的分组,而不是简单对每一个元素都进行操作,此时我们可以使用分组操作!
Collectos的groupingBy静态方法定义了将流元素进行分组的收集器!有了这些方法,我们可以方便快捷的实现集合分组!这个名字看起来就像sql分组操作group by一样,Stream中分组操作和sql中的分组的意义都差不多!
public static
Collector >> groupingBy(Function super T,? extends K> classifier)
classifier:一个分组函数。参数是每一个流元素,返回的值作为生成的map的key,流中具有相同返回值(key)的所有元素组成的List列表作为value。
返回一个Collector,它将元素分组收集到一个Map,key是通过分组函数对流元素进行计算返回的结果,value是具备相同返回结果(key)的流元素List集合。实际Map类型是一个HashMap,线程不安全;实际value类型是一个ArrayList,线程不安全。
public static
Collector > groupingBy(Function super T,? extends K> classifier, Collector super T,A,D> downstream)
classifier:一个分组函数。参数是每一个流元素,返回的值作为生成的map的key,流中具有相同返回值(key)的所有元素组成的List列表作为value。
downstream:一个收集器,用于指定将返回相同的key的流元素收起到一起的规则,比如指定使用什么集合作为value、甚至使用一个归纳操作等等!这里就可以使用mapping方法对原始流数据进行进一步处理之后再收集,完成更加细致的工作!
返回一个Collector,它将元素分组收集到一个Map,key是通过分组函数对流元素进行计算返回的结果,value是具备相同返回结果(key)的流元素应用downstream收集器返回的结果。实际Map类型是一个HashMap,线程不安全;实际value类型根据downstream收集器的类型来确定。
public static
> Collector groupingBy(Function super T,? extends K> classifier, Supplier< M > mapFactory, Collector super T,A,D> downstream)
classifier:一个分组函数。参数是每一个流元素,返回的值作为生成的map的key,流中具有相同返回值(key)的所有元素组成的List列表作为value。
mapFactory:一个生产者,用于指定集合实际类型,只要是Map体系中的集合!
downstream:一个收集器,用于指定将返回相同的key的流元素收起到一起的规则,比如指定使用什么集合作为value、甚至使用一个归纳操作等等!这里就可以使用mapping方法对原始流数据进行进一步处理之后再收集,完成更加细致的工作!
返回一个Collector,它将元素分组收集到一个Map,key是通过分组函数对流元素进行计算返回的结果,value是具备相同返回结果(key)的流元素应用downstream收集器返回的结果。实际Map类型是自己指定的,要求是Map体系中的集合;实际value类型根据downstream收集器的类型来确定。
public static
Collector >> groupingByConcurrent(Function super T,? extends K> classifier)
classifier:一个分组函数。参数是每一个流元素,返回的值作为生成的map的key,流中具有相同返回值(key)的所有元素组成的List列表作为value。
返回一个Collector,它将元素分组收集到一个Map,key是通过分组函数对流元素进行计算返回的结果,value是具备相同返回结果(key)的流元素List集合。实际Map类型是一个ConcurrentHashMap,线程安全;实际value类型是一个ArrayList,线程不安全。
public static
Collector > groupingByConcurrent(Function super T,? extends K> classifier, Collector super T,A,D> downstream)
classifier:一个分组函数。参数是每一个流元素,返回的值作为生成的map的key,流中具有相同返回值(key)的所有元素组成的List列表作为value。
downstream:一个收集器,用于指定将返回相同的key的流元素收起到一起的规则,比如指定使用什么集合作为value、甚至使用一个归纳操作等等!这里就可以使用mapping方法对原始流数据进行进一步处理之后再收集,完成更加细致的工作!
返回一个Collector,它将元素分组收集到一个Map,key是通过分组函数对流元素进行计算返回的结果,value是具备相同返回结果(key)的流元素应用downstream收集器返回的结果。实际Map类型是一个ConcurrentHashMap,线程安全;实际value类型根据downstream收集器的类型来确定。
public static
> Collector groupingByConcurrent(Function super T,? extends K> classifier, Supplier< M > mapFactory, Collector super T,A,D> downstream)
classifier:一个分组函数。参数是每一个流元素,返回的值作为生成的map的key,流中具有相同返回值(key)的所有元素组成的List列表作为value。
mapFactory:一个生产者,用于指定集合实际类型,只要是ConcurrentMap体系中的集合!
downstream:一个收集器,用于指定将返回相同的key的流元素收起到一起的规则,比如指定使用什么集合作为value、甚至使用一个归纳操作等等!这里就可以使用mapping方法对原始流数据进行进一步处理之后再收集,完成更加细致的工作!
返回一个Collector,它将元素分组收集到一个Map,key是通过分组函数对流元素进行计算返回的结果,value是具备相同返回结果(key)的流元素应用downstream收集器返回的结果。实际Map类型是自己指定的,要求是ConcurrentMap体系中的集合,线程安全;实际value类型根据downstream收集器的类型来确定。
/**
* @author lx
*/
public class CollectGrouping {
/**
* groupingBy 一个参数
*/
@Test
public void groupingByOne() {
//对不同等级的学生进行收集分组
Map<Integer, List<Student>> gradeMap = students.stream().collect(groupingBy(Student::getGrade));
System.out.println(gradeMap);
//对不同分数的学生进行收集分组,自定义分组规则
Map<Integer, List<Student>> collect = students.stream().collect(groupingBy(
//通过函数自定义分组规则
student -> {
int score = student.getScore();
if (score >= 90) {
return 1;
} else if (score >= 70) {
return 2;
} else {
return 3;
}
}));
System.out.println(collect);
}
/**
* groupingBy 两个参数
* 第二个Collector参数可以实现各种收集逻辑
*/
@Test
public void groupingByTwo() {
//对不同等级的学生进行收集分组,使用List收集value元素,实际上就是上面单参数groupingBy内部调用的方法
Map<Integer, List<Student>> collectList = students.stream().collect(groupingBy(Student::getGrade,
toList()));
System.out.println(collectList);
System.out.println("=============");
//对不同等级的学生进行收集分组,使用ArrayList收集元素
Map<Integer, ArrayList<Student>> collectArrayList = students.stream().collect(groupingBy(Student::getGrade,
toCollection(ArrayList::new)));
System.out.println(collectArrayList);
System.out.println("=============");
//对不同等级的学生进行收集分组,使用HashSet收集元素
Map<Integer, HashSet<Student>> collectSet = students.stream().collect(groupingBy(Student::getGrade,
toCollection(HashSet::new)));
System.out.println(collectSet);
System.out.println("=============");
//对不同等级的学生进行收集分组,使用Map收集同组学生的id-name
Map<Integer, Map<Integer, String>> collect = students.stream().collect(groupingBy(Student::getGrade,
toMap(Student::getId, Student::getName)));
System.out.println(collect);
System.out.println("=============");
//对不同等级的学生进行收集分组,使用Integer收集同组学生的人数
Map<Integer, Long> collectCounting = students.stream().collect(groupingBy(Student::getGrade,
counting()));
System.out.println(collectCounting);
System.out.println("=============");
//对不同等级的学生进行收集分组,使用TreeMap收集同组学生的id-name并根据id大小倒序排序
Map<Integer, TreeMap<Integer, String>> collect1 = students.stream().collect(groupingBy(Student::getGrade,
toMap(Student::getId, Student::getName, (x, y) -> x,
() -> new TreeMap<>(Comparator.comparingInt(o -> (int) o).reversed()))));
System.out.println(collect1);
}
/**
* groupingBy 三个参数
* 多了一个可以指定外层Map类型的参数
*/
@Test
public void groupingByThr() {
//对不同等级的学生进行收集分组,外层使用TreeMap排序
TreeMap<Integer, List<Student>> collectTreeMap = students.stream().collect(groupingBy(Student::getGrade,
TreeMap::new,
toList()));
System.out.println(collectTreeMap);
}
/**
* groupingByConcurrent 一个参数,和groupingBy差不多,区别就是外层Map是安全的
*/
@Test
public void groupingByConcurrentByOne() {
//对不同等级的学生进行收集分组
ConcurrentMap<Integer, List<Student>> gradeMap = students.stream().collect(groupingByConcurrent(Student::getGrade));
System.out.println(gradeMap);
//对不同分数的学生进行收集分组,自定义分组规则
ConcurrentMap<Integer, List<Student>> collect = students.stream().collect(groupingByConcurrent(
//通过函数自定义分组规则
student -> {
int score = student.getScore();
if (score >= 90) {
return 1;
} else if (score >= 70) {
return 2;
} else {
return 3;
}
}));
System.out.println(collect);
}
/**
* groupingByConcurrent 两个参数,和groupingBy差不多,区别就是外层Map是安全的
*/
@Test
public void groupingByConcurrentByTwo() {
//对不同等级的学生进行收集分组
ConcurrentMap<Integer, List<Student>> gradeMap = students.stream().collect(groupingByConcurrent(Student::getGrade));
System.out.println(gradeMap);
//对不同分数的学生进行收集分组,自定义分组规则
ConcurrentMap<Integer, List<Student>> collect = students.stream().collect(groupingByConcurrent(
//通过函数自定义分组规则
student -> {
int score = student.getScore();
if (score >= 90) {
return 1;
} else if (score >= 70) {
return 2;
} else {
return 3;
}
}));
System.out.println(collect);
}
/**
* grouping或者groupingByConcurrent结合mapping使用,可以完成各种强大的,几乎满足所有业务需求的功能
*/
@Test
public void groupMapping() {
//对不同等级的学生进行收集分组,使用ArrayList收集同组学生的name
Map<Integer, List<Integer>> collectName = students.stream().collect(groupingBy(Student::getGrade,
mapping(Student::getId, toList())));
System.out.println(collectName);
//对不同等级的学生进行收集分组,使用Integer收集同组学生的分数的和
Map<Integer, Integer> collectSum = students.stream().collect(groupingBy(Student::getGrade,
mapping(Student::getId, reducing(0, Integer::sum))));
System.out.println(collectSum);
System.out.println(collectSum);
}
List<Student> students = new ArrayList<>();
@Before
public void before() {
students.add(new Student(1, 55, "小花", 4));
students.add(new Student(2, 100, "小华", 1));
students.add(new Student(3, 85, "晓华", 2));
students.add(new Student(4, 70, "肖华", 2));
students.add(new Student(5, 70, "小小", 2));
students.add(new Student(6, 66, "小小", 3));
students.add(new Student(7, 60, "小夏", 3));
students.add(new Student(8, 77, "花花", 3));
}
static class Student {
private int id;
private int score;
private String name;
private int grade;
public Integer getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
public Student(int id, int score, String name, int grade) {
this.id = id;
this.score = score;
this.name = name;
this.grade = grade;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", score=" + score +
", name='" + name + '\'' +
", grade=" + grade +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
if (getId() != student.getId()) return false;
if (getScore() != student.getScore()) return false;
if (getGrade() != student.getGrade()) return false;
return getName() != null ? getName().equals(student.getName()) : student.getName() == null;
}
@Override
public int hashCode() {
int result = getId();
result = 31 * result + getScore();
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
result = 31 * result + getGrade();
return result;
}
}
}
分区可以看作分组的特例,相比于分组操作可以将流元素分成多组,分区操作最多将流元素分成两个区域。某些时候我们需要将流元素根据是否满足某个或者某些条件来将元素分成两部分,这时就可以使用分区操作了。
分区操作结束后的map只有两条键值对,一条是满足条件的数据,key为true,另一条就是不满足条件的数据,key为false。
public static < T > Collector
>> partitioningBy(Predicate super T> predicate)
predicate:分区断言,根据元素是否满足断言条件来对元素进行分区。
返回一个Collector。它根据断言对输入元素进行判断,并将符合或者不符合条件的元素收集到一个list集合中,最终返回Map
public static
Collector > partitioningBy(Predicate super T> predicate, Collector super T,A,D> downstream)
predicate:分区断言,根据元素是否满足断言条件来对元素进行分区。
downstream:一个收集器,对分区之后的两部分流元素进行其他收集操作。
返回一个Collector。它根据断言对输入元素进行判断,并对符合或者不符合条件的元素继续应用第二个收集器,最终value的类型根据第二个收集器的返回值决定!实际Map类型是一个HashMap,线程不安全;实际value类型根据downstream收集器的类型来确定。
使用案例:
/**
* @author lx
*/
public class CollectPartitioning {
@Test
public void partitioning() {
//根据学生成绩是否大于等于80进行分区
Map<Boolean, List<Student>> collect = students.stream()
.collect(partitioningBy(student -> student.getScore() >= 80));
System.out.println(collect);
//根据学生成绩是否大于等于80进行分区,输出学生名字
Map<Boolean, List<String>> collect1 = students.stream()
.collect(partitioningBy(student -> student.getScore() >= 80, mapping(Student::getName, toList())));
System.out.println(collect1);
//根据学生成绩是否大于等于80进行分区,输出学生id-名字map
Map<Boolean, Map<Integer, String>> collect2 = students.stream()
.collect(partitioningBy(student -> student.getScore() >= 80, toMap(Student::getId, Student::getName)));
System.out.println(collect2);
}
List<Student> students = new ArrayList<>();
@Before
public void before() {
students.add(new Student(1, 55, "小花"));
students.add(new Student(2, 100, "小华"));
students.add(new Student(3, 85, "晓华"));
students.add(new Student(4, 70, "肖华"));
students.add(new Student(5, 70, "小小"));
students.add(new Student(6, 66, "小小"));
students.add(new Student(7, 60, "小夏"));
students.add(new Student(8, 77, "花花"));
}
static class Student {
private int id;
private int score;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(int id, int score, String name) {
this.id = id;
this.score = score;
this.name = name;
}
public Student(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"age=" + id +
", score=" + score +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
if (getId() != student.getId()) return false;
if (getScore() != student.getScore()) return false;
return getName() != null ? getName().equals(student.getName()) : student.getName() == null;
}
@Override
public int hashCode() {
int result = getId();
result = 31 * result + getScore();
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
return result;
}
}
}
前面我们讲的收集器都是Collectors工厂类为我们定义好的,包含了大部分的可能用到的场景,因此我们一般只使用工厂方法即可。但是实际上我们也可以实现自己的收集器,实现自己的逻辑。
实现自定义收集器首先需要实现Collector接口,下面来看看Collector的各项参数和方法的含义:
/**
* 收集器接口,实现该接口可以定义自己的收集器
*
* @param < T > 待收集的流元素类型
* @param 累加器的类型,累加器用于收集流元素
* @param 收集器最终返回的结果类型,结果类型可以就是收集器类型,也可以是其他类型
*/
public interface Collector<T, A, R> {
/**
* @return 返回一个生产者。通过这个生产者的get方法可以获取一个累加器,是一个容器,也可以是对象
*/
Supplier<A> supplier();
/**
* @return 返回一个二元消费者。第一个参数是获取的累加器,第二个参数就是流元素,
* 这里需要将流元素应用到累加器中进行各种自定义操作,比如添加、求和等等,最终还是返回一个累加器
* 这个返回的累加器作为下一次计算的第一个参数
*/
BiConsumer<A, T> accumulator();
/**
* 并行模式的时候会调用该方法,因为并行模式下可能会将流拆分为多个子流分别计算,可能有多个累加器
* 这里需要将累加器的结果进行两两合并等自定义操作,最终会只返回一个累加器
*
* @return 返回一个二元操作器。将两个累加器合并,返回一个累加器,最终只会返回一个累加器。
*/
BinaryOperator<A> combiner();
/**
* @return 返回一个函数。从累加器中获取要返回的最终数据
* 如果累加器就是要返回的数据,那么就不用转换
*/
Function<A, R> finisher();
/**
* 每个收集器实例都有自己的特征,特征是一组描述收集器的对象,框架可以根据特征对收集器的计算进行适当优化
*
* @return 一组不可变的收集器特征集合,集合元素从内部的Characteristics枚举中选取
*/
Set<Characteristics> characteristics();
/**
* 收集器的特征是一个枚举,描述了流是否可以并行归纳,以及可以使用的优化。
*/
enum Characteristics {
/**
* 指示此收集器是多线程的,即accumulator支持多线程调用
* 这意味着结果容器可以支持与来自多个线程的相同结果容器同时调用的累加器函数
*
* 如果收集器没有标为UNORDERED,那它仅在用于无序数据源时才可以并行归纳。
*/
CONCURRENT,
/**
* 指示集合操作不承诺保留输入元素的顺序。
*/
UNORDERED,
/**
* 这表明finisher方法返回的函数是一个恒等函数,即输入什么返回什么。
* 这种情况下,累加器对象将会直接用作归纳过程的最终结果。
*/
IDENTITY_FINISH
}
/**
* 返回一个新的收集器实例,默认具有IDENTITY_FINISH的特征
*
* @param supplier 新收集器的supplier函数
* @param accumulator 新收集器的accumulator函数
* @param combiner 新收集器的combiner函数
* @param characteristics 新收集器的特征集合
* @param < T > 待收集的流元素类型
* @param 累加器的类型,以及收集器最终返回的结果类型,即是同一个类型
* @return 新的累加器实例
* @throws NullPointerException 如果任意参数为null
*/
public static <T, R> Collector<T, R, R> of(Supplier<R> supplier,
BiConsumer<R, T> accumulator,
BinaryOperator<R> combiner,
Characteristics... characteristics) {
Objects.requireNonNull(supplier);
Objects.requireNonNull(accumulator);
Objects.requireNonNull(combiner);
Objects.requireNonNull(characteristics);
//设置特征集合
Set<Characteristics> cs = (characteristics.length == 0)
? Collectors.CH_ID
: Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH,
characteristics));
//返回一个Collectors内部实现的CollectorImpl收集器实例
return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, cs);
}
/**
* 返回一个新的收集器实例
*
* @param supplier 新收集器的supplier函数
* @param accumulator 新收集器的accumulator函数
* @param combiner 新收集器的combiner函数
* @param finisher 新收集器的finisher函数
* @param characteristics 新收集器的特征集合
* @param < T > 待收集的流元素类型
* @param 累加器的类型
* @param 以及收集器最终返回的结果类型
* @return the new {@code Collector}
* @throws NullPointerException if any argument is null
*/
public static <T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A, R> finisher,
Characteristics... characteristics) {
//null校验
Objects.requireNonNull(supplier);
Objects.requireNonNull(accumulator);
Objects.requireNonNull(combiner);
Objects.requireNonNull(finisher);
Objects.requireNonNull(characteristics);
//设置特征集合
Set<Characteristics> cs = Collectors.CH_NOID;
if (characteristics.length > 0) {
cs = EnumSet.noneOf(Characteristics.class);
Collections.addAll(cs, characteristics);
cs = Collections.unmodifiableSet(cs);
}
//返回一个Collectors内部实现的CollectorImpl收集器实例
return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, finisher, cs);
}
}
/**
* 收集器的简单实现类,作为Collectors的内部类
*
* @param < T > 待收集的流元素类型
* @param 累加器的类型,累加器用于收集流元素
* @param 收集器最终返回的结果类型,结果类型可以就是收集器类型,也可以是其他类型
*/
static class CollectorImpl<T, A, R> implements java.util.stream.Collector<T, A, R> {
//保存了传递的函数
private final Supplier<A> supplier;
private final BiConsumer<A, T> accumulator;
private final BinaryOperator<A> combiner;
private final Function<A, R> finisher;
private final Set<Characteristics> characteristics;
//两个构造器,接收各种函数
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A, R> finisher,
Set<Characteristics> characteristics) {
this.supplier = supplier;
this.accumulator = accumulator;
this.combiner = combiner;
this.finisher = finisher;
this.characteristics = characteristics;
}
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Set<Characteristics> characteristics) {
this(supplier, accumulator, combiner, castingIdentity(), characteristics);
}
//下面这些方法被调用的时候,直接返回我们自定义的函数就行了
@Override
public BiConsumer<A, T> accumulator() {
return accumulator;
}
@Override
public Supplier<A> supplier() {
return supplier;
}
@Override
public BinaryOperator<A> combiner() {
return combiner;
}
@Override
public Function<A, R> finisher() {
return finisher;
}
@Override
public Set<Characteristics> characteristics() {
return characteristics;
}
}
/**
* Collectors的的静态方法
* 较少参数的of方法的finisher函数就是这个函数,可以看到就是直接转换类型
*
* @return 返回自身
*/
@SuppressWarnings("unchecked")
private static <I, R> Function<I, R> castingIdentity() {
return i -> (R) i;
}
可以看到,实际上收集器就是通过内部的五个方法进行收集操作的。并且还提供了of方法,这个of方法实际上就是用于返回一个自定义的收集器,因此我们不必再去写一个收集器的实现类,通过of方法,把这几个函数传递进取即可获得一个自定义的收集器实例,这个收集器同样是Collectors在内部帮我们实现的,类型为CollectorImpl!
/**
* @author lx
*/
public class CollectCustom {
@Test
public void test() {
//使用toList
List<Student> collect1 = students.stream().collect(Collectors.toList());
System.out.println(collect1);
//自定义收集器完成toList的功能
List<Student> collect = students.stream().collect(Collector.of((Supplier<List<Student>>) ArrayList::new, List::add, (x, y) -> {
x.addAll(y);
return x;
}));
System.out.println(collect);
//实际上toList方法的源码和此自定义收集器差不多:
//new CollectorImpl<>((Supplier>) ArrayList::new, List::add,(left, right) -> { left.addAll(right); return left; },CH_ID);
//通常Collects提供的预定义收集器都能满足业务
//自定义收集器一般用于完成一些比较独特的业务,比如在收集学生的时候对学生进行评分
Function<Student, Integer> function = o -> {
int score = o.getScore();
if (score >= 90) {
return 1;
} else if (score >= 70) {
return 2;
} else {
return 3;
}
};
List<Student> collect2 = students.stream().collect(Collector.of((Supplier<List<Student>>) ArrayList::new, (x, y) -> {
y.setGrade(function.apply(y));
x.add(y);
}, (x, y) -> {
x.addAll(y);
return x;
}));
System.out.println(collect2);
}
static List<Student> students = new ArrayList<>();
@Before
public void before() {
students.add(new Student(1, 55, "小花"));
students.add(new Student(2, 100, "小华"));
students.add(new Student(3, 85, "晓华"));
students.add(new Student(4, 70, "肖华"));
students.add(new Student(5, 70, "小小"));
students.add(new Student(6, 66, "小小"));
students.add(new Student(7, 60, "小夏"));
students.add(new Student(8, 77, "花花"));
}
static class Student {
private int id;
private int score;
private String name;
private Integer grade;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
public Student(int id, int score, String name) {
this.id = id;
this.score = score;
this.name = name;
}
public Student(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", score=" + score +
", name='" + name + '\'' +
", grade=" + grade +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
if (getId() != student.getId()) return false;
if (getScore() != student.getScore()) return false;
return getName() != null ? getName().equals(student.getName()) : student.getName() == null;
}
@Override
public int hashCode() {
int result = getId();
result = 31 * result + getScore();
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
return result;
}
}
}
随着计算机的发展,市面上很多服务器或者个人电脑都是多核多线程的CPU,在一个多核多线程的CPU中,程序可以做到真正的并行执行,如果我们编写了合理的多线程并行代码,那么对于那种处理大量数据的程序的性能的提升是非常可观的!
在Java7之前,如果我们想要编写并行处理数据的代码,一般来说首先我们需要明确手动将数据分为多个部分,然后对每一部分使用一个线程进行计算,最后将计算的结果进行汇总,在此期间我们必须自己实现线程安全,代码实现起来非常麻烦。
还好在Java7中新增了一个ForkJoinPool,又被称为分支/合并的框架,这个而框架是线程池框架的扩展,可以让我们更加轻松的编写并行数据处理代码。
而在Java8中,新增的Stream流也支持并行的执行任务,并且使用起来比ForkJoinPool更加简单,并行流的底层实际上也是调用的ForkJoinPool,但是它已经帮我们做好了任务分割、结果汇总等操作,我们只需要某些方法启动即可!关于ForkJoinPool的原理我们在前面的文章已经讲解了,类似于分治算法,在此不再赘述!
并行化操作流只需改变一个方法调用。如果已经有一个Stream对象,调用它的parallel方法就能让其拥有并行操作的能力。如果想从一个集合类创建一个流,调用parallelStream 就能立即获得一个拥有并行能力的流。另外sequential则是指定使用串行流。一个流只能应用一种模式,如果同时调用了parallel和sequential 方法,最后调用的那个方法起效。
我们使用串行流和并行流对reduce方法进行测试:
/**
* @author lx
*/
public class ParallelTest {
@Test
public void test1() {
System.out.println("串行流");
//串行流
System.out.println(Stream.of("s", "qq", ";;", ".", ".", ".", ".").reduce("-", (x, y) -> {
System.out.println(Thread.currentThread().getName());
return x + y;
}));
}
@Test
public void test2() {
System.out.println("parallel并行流");
//添加parallel方法,变成并行流
System.out.println(Stream.of("s", "qq", ";;", ".", ".", ".", ".").parallel().reduce("-", (x, y) -> {
System.out.println(Thread.currentThread().getName());
return x + y;
}));
}
@Test
public void test7() {
System.out.println("串行流");
Optional<Integer> reduce = Arrays.asList(1, 2, 3, 4, 5, 6, 7).stream().reduce((x, y) -> {
System.out.println(Thread.currentThread().getName());
return x + y;
});
reduce.ifPresent(System.out::println);
}
@Test
public void test8() {
System.out.println("parallelStream并行流");
Optional<Integer> reduce = Arrays.asList(1, 2, 3, 4, 5, 6, 7).parallelStream().reduce((x, y) -> {
System.out.println(Thread.currentThread().getName());
return x + y;
});
reduce.ifPresent(System.out::println);
}
@Test
public void test3() {
System.out.println("串行流");
//串行流
System.out.println(Arrays.asList("s", "qq", ";;", ".", ".", ".", ".").stream().reduce("-", (x, y) -> {
System.out.println(Thread.currentThread().getName());
return x + y;
}));
}
@Test
public void test4() {
System.out.println("parallelStream并行流");
//并行流
System.out.println(Arrays.asList("s", "qq", ";;", ".", ".", ".", ".").parallelStream().reduce("-", (x, y) -> {
System.out.println(Thread.currentThread().getName());
return x + y;
}));
}
@Test
public void test30() {
System.out.println("串行流");
//串行流
System.out.println(Arrays.asList("s", "qq", ";;", ".", ".", ".", ".").stream().reduce("-", (x, y) -> {
System.out.println(Thread.currentThread().getName());
return x + y;
}, (x, y) -> x + y));
}
@Test
public void test31() {
System.out.println("并行流");
//串行流
System.out.println(Arrays.asList("s", "qq", ";;", ".", ".", ".", ".").parallelStream().reduce("-", (x, y) -> {
System.out.println(Thread.currentThread().getName());
return x + y;
}, (x, y) -> {
System.out.println(Thread.currentThread().getName());
return x + y + x;
}));
}
@Test
public void test5() {
System.out.println("sequential串行流");
//串行流
System.out.println(Arrays.asList("s", "qq", ";;", ".", ".", ".", ".").parallelStream().sequential().reduce("-", (x, y) -> {
System.out.println(Thread.currentThread().getName());
return x + y;
}));
}
@Test
public void test6() {
System.out.println("sequential串行流");
//串行流
System.out.println(Arrays.asList("s", "qq", ";;", ".", ".", ".", ".").stream().parallel().sequential().reduce("-", (x, y) -> {
System.out.println(Thread.currentThread().getName());
return x + y;
}));
}
}
并行虽好,但是一定要注意正确使用,特别是对于我们自己传入的函数,并行模式下不会保证函数的线程安全,因此这要求我们的函数具有天然的线程安全性,如果使用的算法改变了某些共享对象的属性状态,并且我们自己没有保证并行安全,那么就可能造成数据异常!另外,在reduce方法中,并行流操作需要保证初始值与其他之操作之后的值不会改变,并且运算需要满足结合律。
下面是两个复杂案例:
/**
1. @author lx
*/
public class ParallelErr {
/**
* 求总和,long普通变量在串行模式下会丢失数据
*/
@Test
public void test() {
Accumulator accumulator = new Accumulator();
IntStream.rangeClosed(1, 10000).forEach(accumulator::add);
System.out.println(accumulator);
}
/**
* 求总和,long普通变量在并行模式下会丢失数据
*/
@Test
public void test1() {
Accumulator accumulator = new Accumulator();
IntStream.rangeClosed(1, 10000).parallel().forEach(accumulator::add);
System.out.println(accumulator);
}
/**
* 求总和,使用LongAdder累加器,这是一个线程安全的累加器
*/
@Test
public void test2() {
LongAdder longAdder = new LongAdder();
IntStream.rangeClosed(1, 10000).parallel().forEach(longAdder::add);
System.out.println(longAdder);
}
public static class Accumulator {
public static int total = 0;
public void add(int value) {
total += value;
}
@Override
public String toString() {
return "" + total;
}
}
/**
* 要求:计算n的阶乘,然后将结果乘以5后返回
*/
@Test
public void test4() {
System.out.println("安全的串行");
//5的阶乘再乘以5,然后返回
//串行模式下使用reduce没问题
System.out.println(IntStream.rangeClosed(1, 5).reduce(5, (x, y) -> x * y));
System.out.println("错误的并行");
//如果仅仅改成并行操作,那么就会出问题,最终计算结果为375000
//因为并行操作首先会将[1,5]之间的数拆分成为五组数据分别与5相乘,得到:5、10、15、20、25
//然后将结果继续汇总相乘:5*10=50、20*25=500、15 ,然后继续汇总相乘 15*500=7500、50,最后一次汇总:50*7500=375000
System.out.println(IntStream.rangeClosed(1, 5).parallel().reduce(5, (x, y) -> {
System.out.println("x:" + x + " y: " + y + " -> " + (x * y));
return x * y;
}));
System.out.println("collectingAndThen改进");
//改成并行时,首先我们要保证初始值与其他之操作之后的值不会改变,因此这里的初始值只能是1,然后拆分相乘之后我们可以得到:1、2、3、4、5
//随后会将结果继续汇总相乘:1*2=2、4*5=20、3 ,然后继续汇总相乘 30*3=60、2,最后一次汇总:60*2=120
//这样我们就首先把5的阶乘求得了,最后是一个乘以5的操作,这一步我们必须保证不能并行
//因此使用collectingAndThen方法在最后调用乘以5即可,collectingAndThen可以保证最后一步是串行的
Integer collect = IntStream.rangeClosed(1, 5).parallel().boxed().collect(Collectors.collectingAndThen(Collectors.reducing(1, (x, y) -> x * y), x -> x * 5));
System.out.println(collect);
System.out.println("自定义收集器改进");
//当前我们也可以自定义收集器,同样满足我们的要求,而且性能更好
//完整版如下
System.out.println(IntStream.rangeClosed(1, 5).parallel().boxed().collect(Collector.of(new Supplier<List<Integer>>() {
/**
* 返回累加器函数
*/
@Override
public List<Integer> get() {
return new ArrayList<>();
}
}, new BiConsumer<List<Integer>, Integer>() {
/**
* 处理元素函数,添加到集合中
*/
@Override
public void accept(List<Integer> integers, Integer integer) {
integers.add(integer);
}
}, new BinaryOperator<List<Integer>>() {
/**
* 结果并行汇总函数
* 我们将两个集合的第一个元素相乘,然后替换第一个元素,最终结果就是1*2*3*4*5=120
*/
@Override
public List<Integer> apply(List<Integer> integers, List<Integer> integers2) {
Integer integer = integers.get(0);
Integer integer1 = integers2.get(0);
integers.add(0, integer * integer1);
return integers;
}
}, new Function<List<Integer>, Integer>() {
/**
* 返回最终结果函数
* 最后乘以5返回即可
*/
@Override
public Integer apply(List<Integer> integers) {
return integers.get(0) * 5;
}
})));
//自定义收集器简化之后,这里也能看出来自定义收集器功能的强大
System.out.println(IntStream.rangeClosed(1, 5).parallel().boxed().collect(Collector.of(ArrayList::new, List::add, (integers, integers2) -> {
integers.add(0, integers.get(0) * integers2.get(0));
return integers;
}, (Function<List<Integer>, Integer>) integers -> integers.get(0) * 5)));
}
}
除了我们自己要保证安全之外,并行流而并不一定适用与所有操作,通常在生产环境使用并行流之前,我们需要进行性能测试。
一般来说,影响并行化性能的有这么几个因素:
Java8新增的Stream 将一批要处理的数据当作源数据,中间的数据一系列处理看作一个流水线,这一批数据顺序的通过流水线上的每一个节点,一个节点就是一个个的中间处理操作, 比如筛选, 排序,聚合等,流水线上的元素经过每一个节点处理完毕之后最终会通过一个终端操作,获取处理结果!整个流程看起来就像流动的生产线一样,或者像水流在管道中的流动一样,这也是“流”的名字的得来!
Stream API使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和程序结构表达的高阶抽象,比如groupingBy表示分组、max表示求最大值、summing表示求和、forEach表示循环……这种声明式的编程方式,对于传统Java代码结构进行了彻底的改变,比如将外循环变成内循环,Optional容器等等新特性让我们的代码变成更加高效、精简和健壮,极大提高Java程序员的生产力!总之,Java8的Stream值得我们学习!
相关文章:
lambda:Java8—一万字的lambda表达式的详细介绍与应用案例
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!