Java8新特性之Stream初探

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jui121314/article/details/82383383

目录

 

●为什么要用Stream?

●Stream流程拆解

1.构建Stream

2.惰性求值

3.及早求值

●方法引用(Method References)

●可选类(Optional)

●小结


●为什么要用Stream?

上一篇博文Java8新特性之Lambda表达式初探简单介绍了一下Lambda表达式,也许读者会觉得其应用环境比较局限,而且虽说减少了代码量,但也增加了人为阅读理解的难度。首先说一下后者,“阅读理解难”这个问题,笔者认为其实并不存在,任何新的技术刚上手的时候都会有些陌生,等到用习惯了,就能很快看出其含义。举个例子,相信大家在教科书上接触Struts或者SpringMVC框架的时候,最先学会的是用XML配置的方式,而当第一次看到注解@Action或者@Controller的时候可能会不太习惯,但用多了就会发现实在是太解放生产力了!

说回正题,Lambda表达式的第二个显著用途,则是为Java8新特性中的另一个东西:Stream(流)提供服务。此处的Stream与IO流没有任何关系,那它是什么呢?简单地说,可以认为这个东西是对Java集合的一个包装,通过它,可以将传统for循环迭代的操作转移到内部,对程序员透明。什么意思呢,我们来看个实际的应用场景。有一个用户集合,我们需要找出使用@yy.com邮箱的用户,将他们的密码初始化为000000

我们定义如下的用户集合:

//寻找出使用yy邮箱的用户集合,并将其密码初始化为“000000”
List<userInfo> userList = new ArrayList<>();
userList.add(new userInfo(0,"user0","123456","小张","15000000000","[email protected]"));
userList.add(new userInfo(0,"user1","123456","小李","13800000000","[email protected]"));
userList.add(new userInfo(0,"user2","123456","小王","17200000000","[email protected]"));
userList.add(new userInfo(0,"user3","123456","小徐","13900000000","[email protected]"));
userList.add(new userInfo(0,"user4","123456","小赵","15100000000","[email protected]"));
userList.add(new userInfo(0,"user5","123456","小周","18700000000","[email protected]"));
userInfo user ;

看看传统的for循环或者Iterator是怎么实现的:

//for循环,上古时代写法
List<userInfo> resultUserByFor = new ArrayList<>();
for(int i = 0 ; i < userList.size() ;i++){
     user = userList.get(i);
     if(user.getEmail().contains("yy.com")){
            user.setPassword("000000");
            resultUserByFor.add(user);
     }
}

//Iterator写法,外部迭代
List<userInfo> resultUserByIterator = new ArrayList<>();
Iterator<userInfo> iterator = userList.iterator();
while (iterator.hasNext()){
      user = iterator.next();
      if(user.getEmail().contains("yy.com")){
            user.setPassword("000000");
            resultUserByIterator.add(user);
      }
}

二者都有一个显著的特点,程序员自己需要在业务逻辑中显式地去完成集合迭代操作的书写,我们可以形象的称为“外部迭代”。

这对于后续进行代码阅读或者维护的人来说,有一个问题,就是,你得亲自进入循环体,一行一行去看这个业务逻辑。那有没有什么办法可以很直观的告诉阅读或者维护的人,这段代码做了什么呢?答案的肯定的(当然不是写注释…………),这就是Stream的作用——颠覆传统对集合的处理方式,将循环逻辑不直接体现。这能将迭代放在集合内部去进行,实现“内部迭代”,程序员在业务逻辑代码中只需要声明“想做什么”,Stream就会帮你完成“怎么做”这个过程,并将结果返回。

我们来看看代码:

//Stream写法,内部迭代
List<userInfo> resultUserByStream = userList.stream()
                                            .filter(u -> u.getEmail().contains("yy.com"))
                                            .map(u -> {u.setPassword("000000");return u;})
                                            .collect(Collectors.toList());

其实这段代码,一行就可以写完,但为了方便,我们按处理步骤分为四行。发现了吗,使用Stream,程序员可以如同调用普通函数一样完成了整个集合迭代操作,这在JAVA8中也被称为函数式编程(Functional Programming),很形象对吗?和用其他函数一样,告诉程序“想做什么”,不用去管“怎么做”。它还有另外一个名称,聚合操作(aggregate operation),这个后面还会详细说。

最后我们来验证下输出的结果:

System.out.println("------------");
for(userInfo u : resultUserByFor){
      System.out.println(u.getName()+" 密码:"+u.getPassword());
}
System.out.println("------------");
for(userInfo u : resultUserByIterator){
      System.out.println(u.getName()+" 密码:"+u.getPassword());
}
System.out.println("------------");
for(userInfo u : resultUserByStream){
      System.out.println(u.getName()+" 密码:"+u.getPassword());
}
System.out.println("------------");
------------
小李 密码:000000
小王 密码:000000
------------
小李 密码:000000
小王 密码:000000
------------
小李 密码:000000
小王 密码:000000
------------

 

●Stream流程拆解

这部分,我们将详细看看Stream是如何变出这般魔法,咔咔咔三两下就完成了以前需要多行才能实现的功能。

结合场景,我们将其操作分为3步,注意,这也是Stream聚合操作的通用流程——

  1. 构建集合的Stream;
  2. 中间操作,惰性求值;
  3. 返回结果,及早求值。

1.构建Stream

首先,构建集合的Stream,这一步是使用Stream的先决条件,常见以下三种方式:

// 1. Stream的of直接赋值法
Stream stream = Stream.of("小张", "小李", "小王");

// 2. Arrays的stream方法
String [] strArray = new String[] {"小张", "小李", "小王"};
stream = Arrays.stream(strArray);

// 3. 集合(List和Set)的stream方法
List<String> list = Arrays.asList(strArray);
stream = list.stream();

2.惰性求值

接下来是Stream操作的重点,惰性求值。解释一下,之所以这么叫,是因为这一步的操作并不会立即执行(惰性),你可以认为这是对Stream进行一个描述,挑选出符合条件的。如果不研究源码,“怎么挑”,你可以不用管,你只需要声明你想要什么。请再次记住这句话,这是JAVA8函数式编程的核心思想。这一步会用到许多惰性求值函数,它们的参数必须是一个Lambda表达式(Bingo!Lambda表达式再次出场!),即它们的参数是JAVA8内置函数式接口的实现。当然,这一步不是必须的。具体来看一下几个常用的惰性求值函数的具体作用:

惰性求值函数 参数 返回值 作用
map Function<T,R> mapper Stream<R> 一对一映射,将Stream中的值进行转化 / 处理 / 映射得到新的流
filter Predicate<T> predicate Stream<T> 过滤,检查Stream中所有元素是否满足某种条件
flatMap Function<T, Stream<R>> mapper Stream<R> 一对多映射,将Stream中的值(此时为集合,如List)拆分开,进行转化 / 处理 / 映射得到Stream的流
limit/skip long n Stream<T> 限制,返回 / 扔掉Stream中前面的n个值
sorted Stream<T> 排序,对Stream中的值进行排序
distinct Stream<T> 去重,去掉Stream中的重复值

惰性求值函数有一个特点,即返回值还是一个Stream,理解上,可以视为对原始Stream添加的各种描述,筛选出新的Stream。

3.及早求值

最终,我们需要得到的还应该是一个集合或者值,那么就需要用到及早求值函数对Stream进行转化。当然,如果你只想拿到Stream就够了,那这一步也不是必须的。来看几个常用的:

及早求值函数 参数 返回值 作用
reduce

BinaryOperator<T> accumulator

Optional<T>

通用模式,给出一个初始值,将其和Stream中的值进行运算(BinaryOperator),得到新值再和下一个运算(BinaryOperator),最终返回一个值。

之所以叫通用模式,是因为其思路都是一样的,例如累加、拼接、求极值等。

reduce

T identity,

BinaryOperator<T> accumulator

T
reduce

U identity,

BiFunction<U, T, U> accumulator,

BinaryOperator<U> combiner

U
forEach Consumer<T> action void 循环,对Stream中的每个值进行处理
max/min Comparator<T> comparator Optional<T>

极值,取出Stram中的极大 / 极小值,结合.get()使用

collect Collectors.toXX() XX 结果,将处理好的Stream转为XX,如List、Set等
toArray Object[] 结果,将处理好的Stream转为Object数组
anyMatch Predicate<T> predicate boolean 匹配,Stream中有任意一个值满足条件,返回true
allMatch Predicate<T> predicate boolean 匹配,Stream中所有值满足条件,返回true
noneMatch Predicate<T> predicate boolean 匹配,Stream中所有值都不满足条件,返回true
count long 长度,计算Stream中值的个数

 

 

 

 

 

 

 

最后,结合这三步,我们再来尝试一次用聚合操作对集合进行处理,看看代码:

List<Integer> a1= new ArrayList<>();a1.add(2);a1.add(1);a1.add(5);
List<Integer> a2= new ArrayList<>();a2.add(3);a2.add(0);a2.add(1);a2.add(8);
List<Integer> a3= new ArrayList<>();a3.add(7);a2.add(2);

//对三个列表中的数排序、去重,得到新的列表
List<Integer> resultList = Stream.of(a1,a2,a3)  
                                 .flatMap(u -> u.stream()).sorted().distinct()  
                                 .collect(Collectors.toList());  
//打印列表中的值
resultList.stream().forEach(System.out::print);

结果是多少呢?相信各位读者学会上面的知识点已经能一眼看出了。

 

●方法引用(Method References)

在上面的代码中出现了一个新的写法System.out::print,这也是JAVA8的一个新特性,叫做方法引用Method References,等效Lambda表达式() -> System.out.print(),目的还是为了简化代码。具体的格式要求如下:

即,可以使用一个对象的方法,或者一个类的静态方法。举个例子:

 

●可选类(Optional)

再说一下前面章节出现的一个新内容:Optional,这是JAVA8中定义的一个新的类,它相对于一个容器,它可以保存任意T类型的对象,也可以保存个null对象,但它自己并不是空的。好处是,把对象放进这个容器里面,可以很好的避免空指针异常,当你要用这个对象的时候用.get()方法,如果这个对象不存在,则抛出NoSuchElementException的异常。这部分的具体内容,后续文章还会分析。

●小结

未来JAVA7一定会慢慢淡出人们的视野,JAVA8的函数式编程必然会流行起来,就像当年的Windows XP逐渐被Windows 7和Windows 10取代一样。随着人们上手习惯后,新的技术将会体现其新的魅力,进一步提高生产力。言归正传,JAVA8中Stream必然是要配合Lambda使用的,各位读者在学习的时候请按部就班,先将上一篇文章中的内容吃透了,再来看Stream,将会发现这样去写代码会显得格外清爽简洁。相信理解了Stream的思路,也就真正理解了为什么说Lambda表达式能简化代码了。毕竟,习惯了这种直接表达程序员意图的写法,回头去看传统写法确实显得有些低效。今天,你学会了吗?

猜你喜欢

转载自blog.csdn.net/jui121314/article/details/82383383