Java Stream详解

294 阅读8分钟

Java Stream详解

一.什么是流

流是从支持数据处理操作的源生成的元素序列,源可以是数组、文件、集合、函数。流不是集合元素,它不是数据结构并不保存数据,它的主要目的在于计算。

二.生成流的常用方式

1. 通过集合创建

//创建一个集合
List<User> list = new ArrayList<>();
list.add(new User("张三",18,"男"));
list.add(new User("李四",18,"男"));
list.add(new User("王五",18,"女"));
//得到一个顺序流
Stream<User> stream = list.stream();
//得到一个并行了流
Stream<User> stream2 = list.parallelStream();

2.通过数组

//先创建一个数组
int[] arr = new int[]{1, 2, 3, 4, 5, 6};
//使用数组Arrays工具类获取Stream流
IntStream stream = Arrays.stream(arr);

注意: 通过Arrays.stream()方法生成流,并且该方法生成的流是数值流【即IntStream】而不是 Stream。补充一点使用数值流可以避免计算过程中拆箱装箱,提高性能

Stream API提供了mapToInt、mapToDouble、mapToLong三种方式将对象流【即Stream 】转换成对应的数值流,同时提供了boxed方法将数值流转换为对象流.

3.通过Stream.of()方法

Stream<Integer> stream = Stream.of(1,2,3,4,5,6);

4.通过Stream.iterate()和Stream.generate()

iterate方法接受两个参数,第一个为初始化值,第二个为进行的函数操作,因为iterator生成的流为无限流,通过limit方法对流进行了截断,只生成10个偶数。

//创建了10个元素从0到18
Stream<Integer> s =  Stream.iterate(0, t -> t + 2).limit(10);
        

generate方法接受一个参数,方法参数类型为Supplier ,由它为流提供值。generate生成的流也是无限流,因此通过limit对流进行了截断。

//创建了10个随机数
Stream<Double> s2 =  Stream.generate(Math::random).limit(10);

三.流程

1)第一步:把集合转换为流stream
2)第二步:操作stream流
stream流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果
123

四.操作符

1.中间操作符

(1) filter

功能:通过设置的条件过滤出符合条件的元素

示例:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq");
List<String> result = strings.stream()
    .filter(str -> str.contains("ab"))
    .collect(Collectors.toList());
result.stream().forEach(System.out::println);
​
//输出结果: abc abd

(2) distinct

功能:返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流,可以理解为去重

示例:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
List<String> result = strings.stream()
    .distinct()
    .collect(Collectors.toList());
​
result.stream().forEach(System.out::println);
​
//输出结果: abc abd b ef ghq

(3) limit

功能:返回一个长度为n的流。

示例:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
List<String> result = strings.stream()
    .limit(3)
    .collect(Collectors.toList());
​
result.stream().forEach(System.out::println);
​
//输出结果: abc abd b

(4) skip

功能:返回一个删除前n个元素的流。

示例:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
List<String> result = strings.stream()
    .skip(3)
    .collect(Collectors.toList());
​
result.stream().forEach(System.out::println);
​
//输出结果: ef ghq ef ghq

(5) map

功能:接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”)。

示例:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
List<String> result = strings.stream()
    .map(str -> str.concat("_Ego"))
    .collect(Collectors.toList());
​
result.stream().forEach(System.out::println);
​
//输出结果: abc_Ego abd_Ego b_Ego ef_Ego ghq_Ego ef_Ego ghq_Ego

(6) flatMap

功能:使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。

示例:

 //strings中的每一个字符串都会经getStrStream()转换为对应的流,flatMap会将所有的这些流合并成一个单个的流,最后再由终止操作符输出为一个集合    
public static void main(String[] args) {
    List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
    List<Character> result = strings.stream()
        .flatMap(str -> getStrStream(str))             
        .collect(Collectors.toList());
​
    result.stream().forEach(System.out::println);
}
​
/**
* 将字符串转换为对应的Stream
* @param str 待转换的字符串
* @return
*/
public static Stream<Character> getStrStream(String str){
    List<Character> characterList = new ArrayList<>();
    for (int i = 0; i < str.length(); i++) {
        characterList.add(str.charAt(i));
    }
    return characterList.stream();
}
​
//输出结果: a b c a b d b e f g h q e f g h q

(7) sorted

功能:返回排序后的流

示例:

/*
*示例1
*/
List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
List<String> result = strings.stream()
    .sorted()           
    .collect(Collectors.toList());
​
result.stream().forEach(System.out::println);
​
//输出结果abc abd b ef ef ghq ghq
​
/*
*示例2,根据对象的属性值进行比较
*/
List<User> userList = new ArrayList<>();
userList.add(new User("张三",19,"男"));
userList.add(new User("李四",18,"男"));
userList.add(new User("王五",20,"女"));
List<User> result = userList.stream()
    .sorted(Comparator.comparing(User::getAge))
    .collect(Collectors.toList());
​
result.stream().forEach(System.out::println);
/*输出结果:
*   User{name='李四', sex='男', age=18}
*   User{name='张三', sex='男', age=19}
*   User{name='王五', sex='女', age=20}
*/
​

2.终止操作符

(1) anyMatch

功能:检查是否至少匹配一个元素,若为真返回true,反之返回false

示例:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
​
boolean result = strings.stream()
    .anyMatch(str -> str.contains("gh"));
​
System.out.println(result);
​
//输出结果:true

(2) allMatch

功能:检查是否匹配所有元素,若为真返回true,反之返回false

示例:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
​
boolean result = strings.stream()
    .allMatch(str -> str.contains("gh"));
​
System.out.println(result);
​
//输出结果:false

(3) noneMatch

功能:检查是否没有元素匹配,若为真返回true,反之返回false

示例:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
​
boolean result = strings.stream()
    .noneMatch(str -> str.contains("gh"));
​
System.out.println(result);
​
//输出结果:false

(4) findAny

功能:返回当前流中的任意一个元素。

示例:

 List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
​
Optional<String> result = strings.stream()
    .findAny();
if(result.isPresent())
    System.out.println(result);
​
//输出结果:Optional[ghq]

注意: 在普通的流中,findAny并不会随机输出,而是按照顺序输出,因为该流是有顺序的串行流,按照最优执行会按照顺序,如果要随机选择,需要改成并行流。另外:findAny不是为了随机而随机,而是为了进行最快速的选择,所以最好不要用在随机选择的场景。

(5) findFirst

功能:返回当前流中的第一个元素。

示例:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
​
Optional<String> result = strings.stream()
    .findFirst();
if(result.isPresent())
    System.out.println(result);
​
//输出结果:Optional[abc]

(6) forEach

功能:遍历流中的元素。

示例:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
​
strings.stream()
    .forEach(System.out::println);  
​
//输出结果: abc abd b ef ghq ef ghq

(7) collect

功能:将流转换为其它形式(如list,map,set)。

示例1,转为list

/*
*示例1:转为list
*/
List<String> result = strings.stream()
    .map(str -> str.concat("_Ego"))
    .collect(Collectors.toList());
​
result.stream().forEach(System.out::println);
​
//输出结果: abc_Ego abd_Ego b_Ego ef_Ego ghq_Ego ef_Ego ghq_Ego

示例2,转为map

/*
*示例2:转为map
*/
 List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
      
//(key1, key2) -> key2此种情况下,当k重复时,选取后一个键值
Map<String, String> result = strings.stream()
    .collect(Collectors.toMap(str -> str.concat("_k"), str -> str.concat("_v"), (key1, key2) -> key2));
​
for (String key: result.keySet()) {
    System.out.println("key: " + key +"   value: "+ result.get(key));
}
/*输出结果:
key: ghq_k   value: ghq_v
key: ef_k   value: ef_v
key: abc_k   value: abc_v
key: abd_k   value: abd_v
key: b_k   value: b_v
*/

示例3,自定义一个Map来接收,不使用Collectors.toMap()

//如果对转换后的顺序有要求,这里还可以使用LinkedHashMap
HashMap<String, String> result = strings.stream()
                .collect(HashMap::new, (map, str) -> map.put(str + "_k", str + "_v"), HashMap::putAll);
for (String key: result.keySet()) {
    System.out.println("key: " + key +"   value: "+ result.get(key));
}
/*输出结果: 
key: ghq_k   value: ghq_v
key: ef_k   value: ef_v
key: abc_k   value: abc_v
key: abd_k   value: abd_v
key: b_k   value: b_v
*/

示例4,转为set:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
​
Set<String> result = strings.stream()
    .collect(Collectors.toSet());
​
result.stream().forEach(System.out::println);
//输出结果:ef ghq abd b abc

(8) count

功能:返回流中元素总个数。

示例:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
​
long count = strings.stream().count();
​
System.out.println(count);
​
//输出结果:7

(9) reduce

功能:可以将流中元素反复结合起来,得到一个新的值。

示例1,不指定第一次执行时相加的元素:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
​
//reduce方法接受两个参数,acc参数为表达式执行结果的缓存,也就是说说表达式这一次的执行结果会被作为下一次执行的参数,第二个参数item是stream中的每个元素
Optional<String> result = strings.stream()
    .reduce((acc, item) -> acc + item);
​
if(result.isPresent())
    System.out.println(result);
​
//输出结果: Optional[abcabdbefghqefghq]

示例2,指定第一次执行时相加的元素

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
//与前一种方法的区别是,首次执行时表达式中的acc参数为指定的"1"
String result = strings.stream()
    .reduce("1", (acc, item) -> acc + item);
​
System.out.println(result);
//输出结果: 1abcabdbefghqefghq

另外,我们会注意到第一种方法返回的结果由Optional进行了包装,而第二种方法没有,这是因为存在Stream为空的情况,若Stream流中的对象为Integer类型,如果不进行包装就直接返回,由于Integer类型允许为null,所以无法判断究竟是值本来就为空还是Stream为空,所以需要Optional进行包装。而第二种方法由于指定了初始值,所以不会出现返回结果为空的情况,当Stream为空时,reduce将直接返回初始值。

示例3,将计算结果转为一个新的类型

由于前两种实现有一个缺陷,即计算结果必须与Stream中的元素类型相同,如上面的代码示例,Stream中的元素为String类型,那么计算结果也必须为String,这导致了灵活性的不足。下面的方法不讲将计算结果与Stream中 的元素类型绑死,大大提升了灵活性。

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
​
StringBuffer result = strings.stream()
    .reduce(new StringBuffer("Ego_"), (acc, item) -> new StringBuffer(acc + item), (acc, item) -> null);
​
System.out.println(result);
//输出结果:Ego_abcabdbefghqefghq

第三个参数是用来处理多线程中每个线程结果如何合并的问题,举个例子

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
​
StringBuffer result = strings.stream().parallel()
    .reduce(new StringBuffer("Ego_"), (acc, item) -> new StringBuffer(acc + item), (acc, item) -> new StringBuffer(acc.append(item)));
​
System.out.println(result);
//输出结果: Ego_abcEgo_abdEgo_bEgo_efEgo_ghqEgo_efEgo_ghq

问题是,Ego_这个初始字符串被相加了7次,推想一下,使用并行流进行计算时,开启了7个线程进行计算

Thread-1: Ego_ + abc = Ego_abc
Thread-2: Ego_ + abd = Ego_abd
Thread-3: Ego_ + b = Ego_b
Thread-4: Ego_ + ef = Ego_ef
Thread-5: Ego_ + ghq = Ego_ghq
Thread-6: Ego_ + ef = Ego_ef
Thread-7: Ego_ + ghq = Ego_ghq
​
最后将总结果进相加,即获取到:Ego_abcEgo_abdEgo_bEgo_efEgo_ghqEgo_efEgo_ghq

Ego_这个初始字符串被重复计算了多次,这是值得注意的地方,要解决这个问题只需在两个元素相加之后的结果中去除初始元素即可,代码如下:

List<String> strings = Arrays.asList("abc","abd","b","ef","ghq","ef","ghq");
​
StringBuffer result = strings.stream().parallel()
    .reduce(new StringBuffer("Ego_"), (acc, item) -> new StringBuffer((acc + item).substring(4,(acc + item).length())), (acc, item) -> new StringBuffer(acc.append(item)));
​
System.out.println(result);
//输出结果:abcabdbefghqefghq

\