JDK1.8 关于stream接口的一系列流操作
java.util.stream
包汇总: java.util.stream
新的java.util.stream包提供了“支持在流上的函数式风格的值操作”(引用javadoc)的工具。
java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。活动流Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。
Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个串行Stream或者并行Stream。
串行Stream如下所示:
Stream<T> stream = collection.stream();
一个流就像一个地带器。这些值“流过”(模拟水流)然后他们离开。一个流可以只被遍历一次,然后被丢弃。流也可以无限使用。
流Stream的操作可以是 串行执行 或者 并行执行。 它们可以使用其中一种方式开始,然后切换到另外的一种方式,使用stream.sequential()或stream.parallel()来达到这种切换。
串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。
所以,你想用一个流来干什么?这里是在javadoc包里给出的例子:
int sumOfWeights = blocks.stream().filter(b -> b.getColor() == RED) .mapToInt(b -> b.getWeight()) .sum();
注意,上面的代码使用了一个原始的流,以及一个只能用在原始流上的sum()方法。下面马上就会有更多关于原始流的细节:
流提供了流畅的API,可以进行数据转换和对结果执行某些操作。流操作既可以是“中间的”也可以是“末端的”。
- 中间的 -中间的操作保持流打开状态,并允许后续的操作。上面例子中的filter和map方法就是中间的操作。这些操作的返回数据类型是流;它们返回当前的流以便串联更多的操作。
- 末端的 - 末端的操作必须是对流的最终操作。当一个末端操作被调用,流被“消耗”并且不再可用。上面例子中的sum方法就是一个末端的操作。
通常,处理一个流涉及了这些步骤:
- 从某个源头获得一个流。
- 执行一个或更多的中间的操作。
- 执行一个末端的操作。
可能你想在一个方法中执行所有那些步骤。那样的话,你就要知道源头和流的属性,而且要可以保证它被正确的使用。你可能不想接受任意的Stream<T>实例作为你的方法的输入,因为它们可能具有你难以处理的特性,比如并行的或无限的。
有几个更普通的关于流操作的特性需要考虑:
- 有状态的 - 有状态的操作给流增加了一些新的属性,比如元素的唯一性,或者元素的最大数量,或者保证元素以排序的方式被处理。这些典型的要比无状态的中间操作代价大。
- 短路 - 短路操作潜在的允许对流的操作尽早停止,而不去检查所有的元素。这是对无限流的一个特殊设计的属性;如果对流的操作没有短路,那么代码可能永远也不会终止。
对每个Sttream方法这里有一些简短的,一般的描述。查阅javadoc获取更详尽的解释。下面给出了每个操作的重载形式的链接。
中间的操作:
- filter 1 - 排除所有与断言不匹配的元素。
- map 1 2 3 4 - 通过Function对元素执行一对一的转换。
- flatMap 1 2 3 4 5 - 通过FlatMapper将每个元素转变为无或更多的元素。
- peek 1 - 对每个遇到的元素执行一些操作。主要对调试很有用。
- distinct 1 - 根据.equals行为排除所有重复的元素。这是一个有状态的操作。
- sorted 1 2 - 确保流中的元素在后续的操作中,按照比较器(Comparator)决定的顺序访问。这是一个有状态的操作。
- limit 1 - 保证后续的操作所能看到的最大数量的元素。这是一个有状态的短路的操作。
- substream 1 2 - 确保后续的操作只能看到一个范围的(根据index)元素。像不能用于流的String.substring一样。也有两种形式,一种有一个开始索引,一种有一个结束索引。二者都是有状态的操作,有一个结束索引的形式也是一个短路的操作。
末端的操作:
- forEach 1 - 对流中的每个元素执行一些操作。
- toArray 1 2 - 将流中的元素倾倒入一个数组。
- reduce 1 2 3 - 通过一个二进制操作将流中的元素合并到一起。
- collect 1 2 - 将流中的元素倾倒入某些容器,例如一个Collection或Map.
- min 1 - 根据一个比较器找到流中元素的最小值。
- max 1 -根据一个比较器找到流中元素的最大值。
- count 1 - 计算流中元素的数量。
- anyMatch 1 - 判断流中是否至少有一个元素匹配断言。这是一个短路的操作。
- allMatch 1 - 判断流中是否每一个元素都匹配断言。这是一个短路的操作。
- noneMatch 1 - 判断流中是否没有一个元素匹配断言。这是一个短路的操作。
- findFirst 1 - 查找流中的第一个元素。这是一个短路的操作。
- findAny 1 - 查找流中的任意元素,可能对某些流要比findFirst代价低。这是一个短路的操作。
如 javadocs中提到的 , 中间的操作是延迟的(lazy)。只有末端的操作会立即开始流中元素的处理。在那个时刻,不管包含了多少中间的操作,元素会在一个传递中处理(通常,但并不总是)。(有状态的操作如sorted() 和distinct()可能需要对元素的二次传送。)
那么接下来,简介下相关流操作的使用:
测试对象A
1 public class A { 2 private int id; 3 private String userName; 4 private String sex; 5 private String job; 6 ..... 7 ....//省略get和set方法 8 }
一、代替for循环
在List<A> aList中,查找userName为hanmeimei的对象A。
1、查找集合中的第一个对象——filter
aList.stream() .filter(...) .findFirst()
1 Optional<A> firstA= aList.stream() .filter(a -> "hanmeimei".equals(a.getUserName())) .findFirst();
关于Optional,java API中给了解释。
1 //容器对象,该对象可以包含或不包含非空值。如果存在一个值,isPresent()将返回true,并且 get()将返回该值。 2 A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.
所以,我们可以这样子使用:
1 if (firstA.isPresent()) { 2 A a = firstA.get(); //获取对象 3 }else { 4 //没有查到的逻辑 5 }
2、查找满足条件的对象,并返回集合——filter、collect
aList.stream() .filter(...) .collect(Collectors.toList())
List<A> firstA= aList.stream() .filter(a -> "hanmeimei".equals(a.getUserName())) .collect(Collectors.toList());
3、对象列表 - > 字符串列表,即:获取对象集合中所有的userName的集合。——map
注意:stream().map( A::getXXX ) 等价于 stream().map(t -> t.getXXX)
aList.stream.map(...).collect(Collectors.toList())
1 //方法一 2 List<String> idList = aList.stream.map(A::getUserName).collect(Collectors.toList()); 3 //方法二 4 List<String> collect = staff.stream().map(x -> x.getUserName()).collect(Collectors.toList());
4、对象集合 - > 其他对象集合 , 此示例说明如何将 A
对象集合转换为 D对象集合。
1 public static void main(String[] args) {
2
3 List<A> voList= Arrays.asList(
4 new A("mkyong", "男", "医生"),
5 new A("jack", "女", "护士"),
6 new A("lawrence", "女", "科学家")
7 );
8
9 // convert inside the map() method directly.
10 List<D> result = voList.stream().map(temp -> {
11 D obj = new D();
12 obj.setName(temp.getUserName());
13 obj.setSex(temp.getSex());
14 if ("mkyong".equals(temp.getUserName())) {
15 obj.setExtra("this field is for mkyong only!");
16 }
17 return obj;
18 }).collect(Collectors.toList());
19
20 System.out.println(result);
21
22 }
输出结果为:
D{name='mkyong', sex="男", extra='this field is for mkyong only!'},
D{name='jack', sex="女", extra='null'},
D{name='lawrence', sex="女", extra='null'}
二、List转Map
id为key,A对象为value,可以这么做:
1 /** 2 * List -> Map 3 * 需要注意的是: 4 * toMap 如果集合对象有重复的key,会报错Duplicate key .... 5 * 对象a1,对象a2的id都为1。 6 * 可以用 (k1,k2)->k1 来设置,如果有重复的key,则保留key1,舍弃key2 7 */ 8 Map<Integer, A> aMap = aList.stream().collect(Collectors.toMap(A::getId, a -> a,(k1,k2)->k1));
更多内容稍后再补。