Java8 API新增了一个新的抽象流Stream,它可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。Stream就是把集合数据看作流,流在管道中传输,我们可在管道中进行排序聚合等操作。
在平时写代码的过程中,涉及到集合操作时候,Stream API可以极大的提高我们的生产力,让我们写出高效率、干净、简洁的代码。Stream API有很多操作可供使用,这里主要介绍常用的几个方法。
基础Stream API
forEach
forEach是迭代流中的每一个元素。
1
2
3
4
5
6
7
8@Test
public void testForeach() {
Random random = new Random();
List list= new ArrayList<>();
//random.ints(-100,100).limit(10).forEach(System.out::println);
random.ints(-100,100).limit(10).forEach(t->list.add(t));
System.out.println(list);
}
上述是用forEach迭代输出每一个-100至100的随机值,输出10个,还可以迭代进入一个新的List集合。
map
map对流中每个元素进行操作,返回一个新的流
1
2
3
4
5
6
7
8
9
10
11
12@Test
public void testMap(){
// 只是对流操作并不会改变原List中的数据
//Stream fruit=Stream.of("apple","orange","banner","pear");
List fruit = Arrays.asList("apple","orange","banner","pear");
fruit.stream().sorted().map(String::toUpperCase).forEach(System.out::println);
System.out.println(fruit);
// 返回新的流
List newfruit = fruit.stream().map(v->v.toUpperCase()).collect(Collectors.toList());
System.out.println(newfruit);
}
上述是对每个元素进行大写字母化,或输出或返回到新的集合中
filter
filter 方法用于通过设置的条件过滤出元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* Long 数组选取不为Null和大于0的值
* @param t
* @return
*/
public static Long[] removeNullAndZero(Long[] t) {
List list = Arrays.asList(t);
return list.stream().filter(v->v!=null && v>0).toArray(Long[] :: new);
}
/**
* 选取String数组 不为Null的值
* @param t
* @return
*/
public static String[] removeNullAndZero(String[] t) {
List list = Arrays.asList(t);
return list.stream().filter(v->v!=null).toArray(String[] :: new);
}
过滤Null或者大于0的元素
1
2
3
4
5
6@Test
public void testFilter() {
List intList = Arrays.asList(8,2,4,1,8,3,10,6,6,15);
List newIntList=intList.stream().filter(i->i>5).sorted().distinct().collect(Collectors.toList());
System.out.println(newIntList);
}
上述是过滤大于5的值,并且排序,取唯一的数字输出到新的集合。
parallelStream
parallelStream 是流并行处理程序的代替方法,相比较Stream是多管道操作。它就是基于ForkJoinPool执行并发任务的。
特点
使用parallelStream可以简洁高效的写出并发代码。
parallelStream并行执行是无序的。
parallelStream提供了更简单的并发执行的实现,但并不意味着更高的性能,它是使用要根据具体的应用场景。如果cpu资源紧张parallelStream不会带来性能提升;如果存在频繁的线程切换反而会降低性能。
任务之间最好是状态无关的,因为parallelStream默认是非线程安全的,可能带来结果的不确定性。
Github上对Parallel做了增强,有兴趣可以点击访问。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15@Test
public void testParallel() {
Random random = new Random();
List list= new ArrayList<>();
random.ints(-10000,10000).limit(10000).forEach(t->list.add(t));
long start = System.currentTimeMillis();
list.stream().filter(e -> e > 1000 && e< 2000).collect(Collectors.toList());
System.out.println("stream : " + (System.currentTimeMillis() - start) + "ms");
start = System.currentTimeMillis();
list.parallelStream().filter(e -> e > 1000 && e < 2000).collect(Collectors.toList());
System.out.println("parallelStream : " + (System.currentTimeMillis() - start) + "ms");
}
测试并发处理速度,上述代码对此做了测试,测试结果parallelStream速度是稍快一点。
Count
Stream API还提供简单的统计操作。
1
2
3
4
5
6
7
8
9
10
11@Test
public void testCount() {
List numList = Arrays.asList(6, 2, 4, 3, 7, 3, 5, 9);
DoubleSummaryStatistics stats = numList.stream().mapToDouble((x) -> x).summaryStatistics();
System.out.println("总个数 : " + stats.getCount());
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());
}
简单的统计的功能。
实际应用
我在实际项目对会有集合进行操作,使用Stream API能够帮我简洁的解决问题。下面是我实际使用场景。
对象类1
2
3
4
5
6
7
8
9
10
11
12
13
14import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private String name;
private Integer gender;
private int age;
private double height;
}
使用场景
构造数据1
2
3
4
5
6
7
8
9
10
11
12static List createPeople(){
List people=new ArrayList();
Person person=new Person("张三",0,30,2.8);
people.add(person);
person=new Person("李四",0,32,1.6);
people.add(person);
person=new Person("王五",1,32,2.0);
people.add(person);
person=new Person("王五",1,33,1.6);
people.add(person);
return people;
}
连接某一属性
需要并行显示,用,连接某一属性
1
2
3
4
5
6
7
8@Test
public void CollectionStreamJoin(){
List people=createPeople();
Stream stream=people.stream();
// 取出对象的name值,并用,连接
String names = stream.map(v->v.getName()).collect(Collectors.joining(","));
System.out.println(names);
}
获取某属性唯一值
根据某一个属性值,删选对象。只保留唯一的属性值
1
2
3
4
5
6
7
8
9@Test
public void distinctList() {
List people=createPeople();
List distinctlist = people.stream()
.collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getName))),
ArrayList::new));
System.out.println(distinctlist);
}
多属性排序
需要用多个属性去进行排序
1
2
3
4
5
6@Test
public void sortField() {
List people=createPeople();
people.sort(Comparator.comparing(Person::getAge).reversed().thenComparing(Person::getHeight));
System.out.println(JSON.toJSONString(people));
}
分组后排序
对象列表先按照一个字段分组,并根据另一个字段的大小来排序,取第一个对象
1
2
3
4
5
6
7
8
9
10
11@Test
public void getOnlyOneByField() {
List people=createPeople();
Map map =new HashMap<>();
map = people.parallelStream()
.collect(Collectors.groupingBy(Person::getName,
Collectors.collectingAndThen(
Collectors.reducing((c1, c2) -> c1.getAge()>c2.getAge()?c1:c2),
Optional::get)));
System.out.println(map);
}
总结
使用Stream API可以让集合操作的代码更为简洁,提高生产力。而且本人在写Stream操作的时候,思想就跟写SQL语句类似,包括分组、聚合、过滤等操作,遇到难的可以在思维上借鉴下。