什么是Stream?
stream它并不是一个容器,它只是对容器的功能进行了增强,添加了很多便利的操作,例如查找、过滤、分组、排序等一系列的操作。并且有串行、并行两种执行模式,并行模式充分的利用了多核处理器的优势,使用fork/join框架进行了任务拆分,同时提高了执行速度。简而言之,Stream就是提供了一种高效且易于使用的处理数据的方式。
图解:
特性:
-
stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
-
stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
-
stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。
Stream的创建
1、通过 java.util.Collection.stream() 方法用集合创建流
List<String> list = Arrays.asList("a", "b", "c");
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();
2、使用java.util.Arrays.stream(T[] array)方法用数组创建流
int[] array={1,3,5,6,8};
IntStream stream = Arrays.stream(array);
3、使用Stream的静态方法:of()、iterate()、generate()
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
//iterate,generate这两个操作可以创建所谓的无限流,这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。
//使用无限流一定要配合limit截断,不然会无限制创建下去。
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::println);
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);
//输出结果:
//0 3 6 9
//0.6796156909271994
//0.1914314208854283
//0.8116932592396652
顺序流和并行流的区别
stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选集合中的奇数,两者的处理不同之处:
如果流中的数据量足够大,并行流可以加快处速度。除了直接创建并行流,还可以通过parallel()把顺序流转换成并行流,例如:
Optional findFirst = list.stream().parallel().filter(x->x>6).findFirst();
Stream中间操作
anyMatch/allMatch/noneMatch
判断集合中的元素是否满足这个条件,这个方法是一个短路的中间操作,既然是短路操作,那就不需要执行完流中全>部元素,只要达到要求就中断操作。
public static void main(String [] args) {
List<Integer> list = Arrays.asList(1, 2, 1, 1, 1);
boolean anyMatch = list.stream().anyMatch(f -> f == (1));//集合当中的元素有一个满足该条件就返回true
boolean allMatch = list.stream().allMatch(f -> f == (1));//集合当中的元素全部满足该条件才返回true
boolean noneMatch = list.stream().noneMatch(f -> f == (1));//集合当中的元素全部不满足该条件才会返回true
System.out.println(anyMatch); // true
System.out.println(allMatch); // false
System.out.println(noneMatch); // false
}
foreach
Stream也是支持类似集合的遍历和匹配元素的,只是
Stream中的元素是以Optional类型存在的。
Integer[] array = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8};
Arrays.stream(array).forEach(System.out::println);
findFirst
该方法会取出流当中第一个元素并且返回Optional类,可作为兜底数据或者抛出异常。
Integer[] array = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8};
//筛选出数值大于8的并且取出第一个元素,如果没有找见则会返回0
Integer integer1 = Arrays.stream(array).filter(l -> l > 8).findFirst().orElse(0);
System.out.println(integer1);// 结果: 0
//筛选出数值大于5的并且取出第一个元素,如果没有找见则会返回0
Integer integer2 = Arrays.stream(array).filter(l -> l > 5).findFirst().orElse(0);
System.out.println(integer2);// 结果: 6
findAny
就是从流当中随机取出任意元素
public static void main(String[] args)
Integer[] array = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8};
for (int i = 0; i < 10; i++) {
testFindFirst(array);
testFindAny(array);
}
}
//测试FindFirst
public void testFindFirst(Integer... array) {
Integer integer1 = Arrays.stream(array).findFirst().get();
System.out.println(integer1);
}
//测试FindAny
public void testFindAny(Integer... array) {
Integer integer1 = Arrays.stream(array).findAny().get();
System.out.println(integer1);
}
执行结果:
根据结果可以看出这两个方法执行的结果都是一样的,findFirst方法我们可以理解,就是找第一个元素。每次输出的内容一样没有问题。但是findAny不是说是找任意一个元素吗,怎么每次输出的也是一样啊。
这是因为对
array这个集合做流化处理使用的是stream,这是串行流。如果我们的array是有序的,那findAny的任意一个都是第一个了。既然有串行流,那试试并行流吧。
public static void main(String[] args) {
Integer[] array = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8};
for (int i = 0; i < 20; i++) {
testFindFirst(array);
testFindAny(array);
}
}
//测试FindFirst
public static void testFindFirst(Integer... array) {
Integer integer1 = Arrays.stream(array).findFirst().get();
System.out.println("测试FindFirst的执行结果为:" + integer1);
}
//测试FindAny
public static void testFindAny(Integer... array) {
//更改为并行流
Integer integer1 = Arrays.asList(array).parallelStream().findAny().get();
System.out.println("测试FindAny的执行结果为:" + integer1);
}
执行结果:(执行结果需要多执行几次,在这里加大了循环次数,并且需要多运行几次,否则由于多线程的原因可能导致数据显示没变化)
这次的执行结果输出的不再都是第一个元素了,而是任意的一个元素了。
筛选(filter)
筛选是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
personList.add(new Person("Anni", 8200, 24, "female", "New York"));
personList.add(new Person("Owen", 9500, 25, "male", "New York"));
personList.add(new Person("Alisa", 7900, 26, "female", "New York"));
List<String> fiterList = personList.stream()
.filter(x -> x.getSalary() > 8000)
.map(Person::getName)
.collect(Collectors.toList());
System.out.print("高于8000的员工姓名:" + fiterList);
}
}
//运行结果:
//高于8000的员工姓名:[Tom, Anni, Owen]
聚合(max/min/count)
方便了我们对集合、数组的数据统计工作。
//案例一:获取String集合中最长的元素。
public class StreamTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd");
Optional<String> max = list.stream().max(Comparator.comparing(String::length));
System.out.println("最长的字符串:" + max.get());
}
}
//输出结果:最长的字符串:weoujgsd
//案例二:获取集合的最大值
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(7, 6, 9, 4, 11, 6);
// 自然排序
Optional<Integer> max = list.stream().max(Integer::compareTo);
// 自定义排序
Optional<Integer> max2 = list.stream().max(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
System.out.println("自然排序的最大值:" + max.get());
System.out.println("自定义排序的最大值:" + max2.get());
}
}
//输出结果:
自然排序的最大值:11
自定义排序的最大值:11
//案例三:获取集合对象当中员工的工资的最大值
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
personList.add(new Person("Anni", 8200, 24, "female", "New York"));
personList.add(new Person("Owen", 9500, 25, "male", "New York"));
personList.add(new Person("Alisa", 7900, 26, "female", "New York"));
Optional<Person> max = personList.stream().max(Comparator.comparingInt(Person::getSalary));
System.out.println("员工工资最大值:" + max.get().getSalary());
}
}
//输出结果:员工工资最大值:9500
//案例四:获取集合当中元素个数
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(7, 6, 4, 8, 2, 11, 9);
long count = list.stream().filter(x -> x > 6).count();
System.out.println("list中大于6的元素个数:" + count);
}
}
//输出结果:list中大于6的元素个数:4
(map/flatMap)
映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:
map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流,可合并两个相同类型的集合。
public class StreamTest {
public static void main(String[] args) {
String[] strArr = { "abcd", "bcdd", "defde", "fTr" };
List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList());
List<Integer> intList = Arrays.asList(1, 3, 5, 7, 9, 11);
List<Integer> intListNew = intList.stream().map(x -> x + 3).collect(Collectors.toList());
System.out.println("每个元素大写:" + strList);
System.out.println("每个元素+3:" + intListNew);
}
}
//输出结果:
//每个元素大写:[ABCD, BCDD, DEFDE, FTR]
//每个元素+3:[4, 6, 8, 10, 12, 14]
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
personList.add(new Person("Anni", 8200, 24, "female", "New York"));
personList.add(new Person("Owen", 9500, 25, "male", "New York"));
personList.add(new Person("Alisa", 7900, 26, "female", "New York"));
// 不改变原来员工集合的方式
List<Person> personListNew = personList.stream().map(person -> {
Person personNew = new Person(person.getName(), 0, 0, null, null);
personNew.setSalary(person.getSalary() + 10000);
return personNew;
}).collect(Collectors.toList());
System.out.println("一次改动前:" + personList.get(0).getName() + "-->" + personList.get(0).getSalary());
System.out.println("一次改动后:" + personListNew.get(0).getName() + "-->" + personListNew.get(0).getSalary());
// 改变原来员工集合的方式
List<Person> personListNew2 = personList.stream().map(person -> {
person.setSalary(person.getSalary() + 10000);
return person;
}).collect(Collectors.toList());
System.out.println("二次改动前:" + personList.get(0).getName() + "-->" + personListNew.get(0).getSalary());
System.out.println("二次改动后:" + personListNew2.get(0).getName() + "-->" + personListNew.get(0).getSalary());
}
}
//输出结果:
//一次改动前:Tom–>8900
//一次改动后:Tom–>18900
//二次改动前:Tom–>18900
//二次改动后:Tom–>18900
public class StreamTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("m,k,l,a", "1,3,5,7");
List<String> listNew = list.stream().flatMap(s -> {
// 将每个元素转换成一个stream
String[] split = s.split(",");
Stream<String> s2 = Arrays.stream(split);
return s2;
}).collect(Collectors.toList());
System.out.println("处理前的集合:" + list);
System.out.println("处理后的集合:" + listNew);
}
}
//输出结果:
//处理前的集合:[m-k-l-a, 1-3-5]
//处理后的集合:[m, k, l, a, 1, 3, 5]
peek
peek方法主要用于Stream流中的调试
peek和map的异同之处
- 两个函数都是中间操作,都非常的‘懒’,没有对Stream的终止操作,两个函数都不会工作。
- peek函数的存在仅仅是为了debug,而map是Stream的一个核心函数,两个函数的地位不同。
- 两个函数的返回值都是一个新的Stream,但是两个函数的参数(peek是Consumer,map是Function)起作用的时机不同。map的Function在生成新的Stream之前被执行,新Stream中的元素是上游Stream中元素经Function作用后的值。peek函数的Consumer工作在生成Stream之后,下一节详细讲解两个函数执行时机。
样例
以下面一段函数(来自peek函数的官方注释)为例解释peek和map两个函数工作机制的不同:
List<String> list = Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("第一次Peek得到的值: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("第二次Peek得到的值: " + e))
.collect(Collectors.toList());
System.out.println("List集合:" + list);
//输出如下:
//第一次Peek得到的值: three
//第二次Peek得到的值: THREE
//第一次Peek得到的值: four
//第二次Peek得到的值: FOUR
//List集合:[THREE, FOUR]
工作示意图如下
工作流程概述:
1、初始Stream包含四个字符串:one,two,three和four
2、Stream遇到的第一个中间操作是filter,filter是只保留长度大于3的字符串,经过过滤后filter返回是一个仅包含两个字符串three和four的Stream1,谓词工作在Stream1生成之前。
3、Stream遇到的第二个中间操作是peek,peek的Consumer是打印Stream中的字符串,peek直接生成一个和上游Stream1包含相同元素的Stream2,peek函数的Consumer工作在生成Stream2之后。
4、Stream遇到的第三个中间操作是map,map的Function将字符串转换为全大写,Function作用于上游Stream2的每一个元素,并生成新的Stream3。
5、Stream遇到的第四个中间操作是peek,peek的Consumer依然只是打印Stream4中的字符串,Consumer依然工作在Stream4生成之后。
peek和map修改Stream的元素
map函数对Stream中元素执行的是映射操作,会以新的元素(map的结果)填充新的Stream,严格的讲map不是修改原来的元素。peek只能消费Stream中的元素,是否可以更该Stream中的元素,取决于Stream中的元素是否是不可变对象。如果是不可变对象,则不可修改Stream中的元素;如果是可变对象,则可以修改对象的值,但是无法修改对象的引用。
- 不可变对象场景:
适当的修改上面的样例:
List<String> list = Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(s -> {
s = s + "-" + s;
System.out.println("第一次peek得到的值:" + s);
})
.map(String::toUpperCase)
.peek(e -> System.out.println("第二次peek得到的值:" + e))
.collect(Collectors.toList());
System.out.println("该集合的值"+list);
//输出结果:
//第一次peek得到的值:three-three
//第二次peek得到的值:THREE
//第一次peek得到的值:four-four
//第二次peek得到的值:FOUR
//该集合的值[THREE, FOUR]
由输出结果可知peek并没有修改Stream的元素,list的值依然是:[THREE, FOUR]
- 可变对象
定义一个简单的Java对象Company:
class Company {
private String name;
private int age;
// 省略构造函数,getter/setter和toString方法
}
public static void main(String[] args) {
Company apple = new Company("apple", 44);
Company huawei = new Company("huawei", 33);
Company qualcomm = new Company("Qualcomm ", 35);
List<Company> list = Stream.of(apple, huawei, qualcomm)
.filter(company -> company.getAge() < 35)
.peek(company -> company.setAge(company.getAge() - 10))
.map(company -> new Company(company.getName().toUpperCase(),company.getAge()))
.peek(e -> System.out.println("Peek得到的值为: " + e))
.collect(Collectors.toList());
System.out.println("list集合的最终结果:"+list);
}
//输出的结果如下:
//Peek得到的值为: Person{name='HUAWEI', age=23}
//list集合的最终结果:[Person{name='HUAWEI', age=23}]
排序(sorted)
- sorted():自然排序,流中元素需实现Comparable接口
- sorted(Comparator com):Comparator排序器自定义排序
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Sherry", 9000, 24, "female", "New York"));
personList.add(new Person("Tom", 8900, 22, "male", "Washington"));
personList.add(new Person("Jack", 9000, 25, "male", "Washington"));
personList.add(new Person("Lily", 8800, 26, "male", "New York"));
personList.add(new Person("Alisa", 9000, 26, "female", "New York"));
// 按工资升序排序(自然排序)
List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
.collect(Collectors.toList());
// 按工资倒序排序
List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
.map(Person::getName).collect(Collectors.toList());
// 先按工资再按年龄升序排序
List<String> newList3 = personList.stream()
.sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName)
.collect(Collectors.toList());
// 先按工资再按年龄自定义排序(降序)
List<String> newList4 = personList.stream().sorted((p1, p2) -> {
if (p1.getSalary() == p2.getSalary()) {
return p2.getAge() - p1.getAge();
} else {
return p2.getSalary() - p1.getSalary();
}
}).map(Person::getName).collect(Collectors.toList());
System.out.println("按工资升序排序:" + newList);
System.out.println("按工资降序排序:" + newList2);
System.out.println("先按工资再按年龄升序排序:" + newList3);
System.out.println("先按工资再按年龄自定义降序排序:" + newList4);
}
}
运行结果:
按工资升序排序:[Lily, Tom, Sherry, Jack, Alisa]
按工资降序排序:[Sherry, Jack, Alisa, Tom, Lily]
先按工资再按年龄升序排序:[Lily, Tom, Sherry, Jack, Alisa]
先按工资再按年龄自定义降序排序:[Alisa, Jack, Sherry, Tom, Lily]
提取/组合
流也可以进行合并(concat)、去重(distinct)、限制(limit)、跳过(skip)等操作。
跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补。
截断流,使其元素不超过给定数量。如果元素的个数小于maxSize,那就获取所有元素。
public class StreamTest {
public static void main(String[] args) {
String[] arr1 = { "a", "b", "c", "d" };
String[] arr2 = { "d", "e", "f", "g" };
Stream<String> stream1 = Stream.of(arr1);
Stream<String> stream2 = Stream.of(arr2);
// concat:合并两个流 distinct:去重
List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
// limit:限制从流中获得前n个数据
List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
// skip:跳过前n个数据
List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());
System.out.println("流合并:" + newList);
System.out.println("limit:" + collect);
System.out.println("skip:" + collect2);
}
}
运行结果:
流合并:[a, b, c, d, e, f, g]
limit:[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
skip:[3, 5, 7, 9, 11]
Stream流终止操作
1.收集(collect):collect主要依赖java.util.stream.Collectors类内置的静态方法。
- 返回List集合:toList
- 返回Set集合:toSet
- 返回Map集合:toMap
- 返回自定义集合:toCollection
- 返回线程安全,高效,并发的集合:toConcurrentMap
- 返回不可修改的List集合:toUnmodifiableList //用于创建只读List集合。任何试图对此不可修改List集合进行更改的尝试都将导致UnsupportedOperationException。
- 返回不可修改的Set集合:toUnmodifiableSet //用于创建只读Set集合。任何试图对此不可修改Set集合进行更改的尝试都将导致UnsupportedOperationException。它会删除重复元素。
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 6, 3, 4, 6, 7, 9, 6, 20);
List<Integer> listNew = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toList());
Set<Integer> set = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet());
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
personList.add(new Person("Anni", 8200, 24, "female", "New York"));
Map<?, Person> map = personList.stream().filter(p -> p.getSalary() > 8000)
.collect(Collectors.toMap(Person::getName, p -> p));
System.out.println("toList:" + listNew);
System.out.println("toSet:" + set);
System.out.println("toMap:" + map);
}
}
//运行结果:
//toList:[6, 4, 6, 6, 20]
//toSet:[4, 20, 6]
//toMap:{Tom=mutest.Person@5fd0d5ae, Anni=mutest.Person@2d98a335}
//案例二:转换为Map集合,Map集合的Key不可以重复,Value可以重复
List<User> users = new ArrayList<>();
users.add(new User(1,"小明","xiaoming",18,"西安"));
users.add(new User(2,"小白","xiaobai",23,"北京"));
users.add(new User(3,"小红","xiaohong",54,"上海"));
users.add(new User(3,"小红","xiaohong",54,"上海"));
users.add(new User(4,"小红","xiaohong11",54,"上海"));
users.add(new User(5,"小红","xiaohong",54,"上海"));
//使用toMap()函数之后,返回的就是一个Map,会需要key和value。
//toMap()的参数
//第一个参数就是用来生成key值的,
//第二个参数就是用来生成value值的。
//第三个参数用在key值冲突的情况下:如果新元素产生的key在Map中已经出现过了,第三个参数就会定义解决的办法。
Map<Integer, User> map = users.stream().collect(Collectors.toMap(User::getId, u -> u,(k,v)->k));
map.forEach((k,v)->{
System.out.println("K:"+k);
System.out.println("V:"+v);
});
//代码解释:
//在users.stream().collect(Collectors.toMap(User::getId, u -> u,(k,v)->k));中,
//User::getId 以User的id作为key
//u -> u User实体作为value
//(k,v)->k 如果出现key值冲突,永远取第一个
输出结果:
K:1
V:User(id=1, username=小明, password=xiaoming, age=18, address=西安)
K:2
V:User(id=2, username=小白, password=xiaobai, age=23, address=北京)
K:3
V:User(id=3, username=小红, password=xiaohong, age=54, address=上海)
K:4
V:User(id=4, username=小红, password=xiaohong11, age=54, address=上海)
K:5
V:User(id=5, username=小红, password=xiaohong, age=54, address=上海)
//当你不想覆盖掉重复项数据,这个时候就可以用 Collectors.groupingBy这个方法了,此返回值为Map<Integer, List>,也就是
//变成了id依旧是key,而value则变成User对象的集合了。
Map<Integer, List<User>> map1 = users.stream().collect(Collectors.groupingBy(User::getId));
map1.forEach((k,v)->{
System.out.println("K:"+k);
System.out.println("V:"+v);
});
输出结果:
K:1
V:[User(id=1, username=小明, password=xiaoming, age=18, address=西安)]
K:2
V:[User(id=2, username=小白, password=xiaobai, age=23, address=北京)]
K:3
V:[User(id=3, username=小红, password=xiaohong, age=54, address=上海), User(id=3, username=小红, password=xiaohong, age=54, address=上海)]
K:4
V:[User(id=4, username=小红, password=xiaohong11, age=54, address=上海)]
K:5
V:[User(id=5, username=小红, password=xiaohong, age=54, address=上海)]
//案例三:转换为自定义集合
List<User> users = new ArrayList<>();
users.add(new User(1, "小明", "xiaoming", 18, "西安"));
users.add(new User(2, "小白", "xiaobai", 23, "北京"));
users.add(new User(3, "小红", "xiaohong", 54, "上海"));
users.add(new User(3, "小红", "xiaohong", 54, "上海"));
users.add(new User(4, "小红", "xiaohong11", 54, "上海"));
users.add(new User(5, "小红", "xiaohong", 54, "上海"));
//转为LinkedList集合
LinkedList<User> linkedList = users.stream().collect(Collectors.toCollection(LinkedList::new));
//转为TreeSet集合
TreeSet<User> treeSet = users.stream().collect(Collectors.toCollection(TreeSet::new));
//转为CopyOnWriteArrayListResult集合:高并发List集合
List<User> copyOnWriteArrayListResult = users.stream().collect(Collectors.toCollection(CopyOnWriteArrayList::new));
接合(joining):
joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
String names = personList.stream().map(p -> p.getName()).collect(Collectors.joining(","));
System.out.println("所有员工的姓名:" + names);
List<String> list = Arrays.asList("A", "B", "C");
String string = list.stream().collect(Collectors.joining("-"));
System.out.println("拼接后的字符串:" + string);
}
}
运行结果:
所有员工的姓名:Tom,Jack,Lily
拼接后的字符串:A-B-C
统计(count/averaging)
Collectors提供了一系列用于数据统计的静态方法:
- 计数:count
- 平均值:averagingInt、averagingLong、averagingDouble
- 最值:maxBy、minBy
- 求和:summingInt、summingLong、summingDouble
- 统计以上所有:summarizingInt、summarizingLong、summarizingDouble
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
// 求总数
Long count = personList.stream().collect(Collectors.counting());
// 求平均工资
Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
// 求最高工资
Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));
// 求工资之和
Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary));
// 一次性统计所有信息
DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));
System.out.println("员工总数:" + count);
System.out.println("员工平均工资:" + average);
System.out.println("员工工资总和:" + sum);
System.out.println("员工工资所有统计:" + collect);
}
}
运行结果:
员工总数:3
员工平均工资:7900.0
员工工资总和:23700
员工工资所有统计:DoubleSummaryStatistics{count=3, sum=23700.000000,min=7000.000000, average=7900.000000, max=8900.000000}
归约(reduce):
归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
// 求和方式1
Optional<Integer> sum = list.stream().reduce((x, y) -> x + y);
// 求和方式2
Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
// 求和方式3
Integer sum3 = list.stream().reduce(0, Integer::sum);
// 求乘积
Optional<Integer> product = list.stream().reduce((x, y) -> x * y);
// 求最大值方式1
Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y);
// 求最大值写法2
Integer max2 = list.stream().reduce(1, Integer::max);
System.out.println("list求和:" + sum.get() + "," + sum2.get() + "," + sum3);
System.out.println("list求积:" + product.get());
System.out.println("list求和:" + max.get() + "," + max2);
}
}
//输出结果:
//list求和:29,29,29
//list求积:2112
//list求和:11,11
//案例二:求所有员工的工资之和和最高工资。
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
personList.add(new Person("Anni", 8200, 24, "female", "New York"));
personList.add(new Person("Owen", 9500, 25, "male", "New York"));
personList.add(new Person("Alisa", 7900, 26, "female", "New York"));
// 求工资之和方式1:
Optional<Integer> sumSalary = personList.stream().map(Person::getSalary).reduce(Integer::sum);
// 求工资之和方式2:
Integer sumSalary2 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(),
(sum1, sum2) -> sum1 + sum2);
// 求工资之和方式3:
Integer sumSalary3 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(), Integer::sum);
// 求最高工资方式1:
Integer maxSalary = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
Integer::max);
// 求最高工资方式2:
Integer maxSalary2 = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
(max1, max2) -> max1 > max2 ? max1 : max2);
System.out.println("工资之和:" + sumSalary.get() + "," + sumSalary2 + "," + sumSalary3);
System.out.println("最高工资:" + maxSalary + "," + maxSalary2);
}
}
//输出结果:
//工资之和:49300,49300,49300
//最高工资:9500,9500
归约(reducing)
Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持。
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
// 每个员工减去起征点后的薪资之和(这个例子并不严谨,但一时没想到好的例子)
Integer sum = personList.stream().collect(Collectors.reducing(0, Person::getSalary, (i, j) -> (i + j - 5000)));
System.out.println("员工扣税薪资总和:" + sum);
// stream的reduce
Optional<Integer> sum2 = personList.stream().map(Person::getSalary).reduce(Integer::sum);
System.out.println("员工薪资总和:" + sum2.get());
}
}
运行结果:
员工扣税薪资总和:8700
员工薪资总和:23700
分组(partitioningBy/groupingBy)
- 分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
- 分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, "male", "New York"));
personList.add(new Person("Jack", 7000, "male", "Washington"));
personList.add(new Person("Lily", 7800, "female", "Washington"));
personList.add(new Person("Anni", 8200, "female", "New York"));
personList.add(new Person("Owen", 9500, "male", "New York"));
personList.add(new Person("Alisa", 7900, "female", "New York"));
// 将员工按薪资是否高于8000分组
Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000));
// 将员工按性别分组
Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex));
// 将员工先按性别分组,再按地区分组
Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
System.out.println("员工按薪资是否大于8000分组情况:" + part);
System.out.println("员工按性别分组情况:" + group);
System.out.println("员工按性别、地区:" + group2);
}
}
//输出结果:
//员工按薪资是否大于8000分组情况:{false=[mutest.Person@2d98a335, mutest.Person@16b98e56, mutest.Person@7ef20235], true=[mutest.Person@27d6c5e0, mutest.Person@4f3f5b24, mutest.Person@15aeb7ab]}
//员工按性别分组情况:{female=[mutest.Person@16b98e56, mutest.Person@4f3f5b24, mutest.Person@7ef20235], male=[mutest.Person@27d6c5e0, mutest.Person@2d98a335, mutest.Person@15aeb7ab]}
//员工按性别、地区:{female={New York=[mutest.Person@4f3f5b24, mutest.Person@7ef20235], Washington=[mutest.Person@16b98e56]}, male={New York=[mutest.Person@27d6c5e0, mutest.Person@15aeb7ab], Washington=[mutest.Person@2d98a335]}}