学习任务:

视频学习

流水线式的处理需求

传统方法实现

Stream流方式实现

Stream流与for的区别

集合提供了常用的数据结构,在生产中很多时候需要对集合中的数据多次连续处理,比如下边的例子:

对一个字符串数组进行处理,需求如下:

1)找出长度大于4的字符串。

2)将长度大于4的字符串转成大写字母。

3)遍历已经转成大写字母的字符串。

根据需求可知对字符串数组的处理包括三个过程,如下图:

image-20201021182815998

三个过程组成一个流水线,每个过程是流水线的一个阶段。

针对上边的需求使用for循环实现有两种方案:

1)每次处理产生一个新的集合

package com.pbteach.javase.oop.stream.test1;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

/**
 * Stream流入门程序
 * 
 * @author 攀博课堂(www.pbteach.com)
 *
 */
public class StreamFirstTest1 {
	//传统方法每次处理生成一个新集合
	public static void test1() {
		String[] strs = new String[] {"hello","www","pbteach","com"};
		//定义集合存放长度大于4的字符串
		List<String> list_filter =new ArrayList<>();
		for (int i = 0; i < strs.length; i++) {
			//判断长度
			if(strs[i].length()>4) {
				list_filter.add(strs[i]);
			}
		}
		//定义集合存放转成大写字符的
		List<String> list_uppercase =new ArrayList<>();
		list_filter.forEach(x->{
			//转成大写
			String str_index = x.toUpperCase();
			list_uppercase.add(str_index);
		});
		//遍历list_uppercase集合
		list_uppercase.forEach(System.out::println);
	}
	public static void main(String[] args) {
			test1();
    	}
    }

输出:

HELLO
PBTEACH

本方案的问题很明显,共需三次遍历,为了处理数据特创建两个集合对象,效率低下。

2)使用一个for循环

为了解决第一个方案的问题,使用一个for循环完成处理。

package com.pbteach.javase.oop.stream.test1;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

/**
 * Stream流入门程序
 * 
 * @author 攀博课堂(www.pbteach.com)
 *
 */
public class StreamFirstTest1 {
	//传统方法用一个for循环
	public static void test2() {
		String[] strs = new String[] {"hello","www","pbteach","com"};
		for (int i = 0; i < strs.length; i++) {
			//判断长度
			if(strs[i].length()>4) {
				//转成大写
				String str_index = strs[i].toUpperCase();
				System.out.println(str_index);
			}
		}
	}
	
	public static void main(String[] args) {
		test2();
	}

}

使用一个for循环处理字符串其效率比第一种方案有所提升,但也存在问题:每个阶段的处理逻辑参合在一起,可扩展性不强,任意一个处理逻辑需要变更则需要修改方法的代码,比如:转大写改为转小写需要修改代码,找到长度大于5的单词也要修改代码。

Java在Jdk8推出Stream流编程库,它是基于Lambda函数式编程技术而设计,简化对集合的操作,不仅使代码更优美还可以利用计算机多核进行并行计算。Stream流所采用的编程方式也是当前比较流行的流式编程技术,流式编程在大数据领域应用较多,比如:Scala、Apache Flink、Spark等大数据编程技术都是采用的流式编程。

下边的代码是采用Stream流完成字符串处理的需求:

	//stream流方式实现需求
	public static void test3() {
		String[] strs = new String[] {"hello","www","pbteach","com"};
		//使用stream流完成字符串数组的处理
		Stream.of(strs).filter(x->x.length()>4).map(x->x.toUpperCase()).forEach(System.out::println);
	}
	

上边的代码仅用一行就实现了字符串处理的需求,这就是Stream编程的魅力,可以看到这里用到了Lambda表达式、方法引用。

下边来分析这一行代码,为了方便分析将其拆分为四行,如下:

Stream.of(strs)//创建Stream流对象
.filter(x->x.length()>4)//过滤字符串长度大于4的字符串
.map(x->x.toUpperCase())//将字符串转成大写
.forEach(System.out::println);//遍历每个字符串

1)Stream.of(strs):返回一个Stream对象(Stream是一个接口),此对象包括一个迭代器可对数组元素进行迭代。

2)filter(x->x.length()>4):filter方法是Stream接口中的方法,它的功能是对集合中的元素进行过虑,根据传入Lamdba表达式判断是否过虑,此方法返回一个Stream对象。

3)map(x->x.toUpperCase()):map方法是Stream接口中的方法,它的功能是对集合中的元素进行转换,根据传入的Lambda表达式进行转换,此方法返回一个Stream对象。

以上三个方法都是返回Stream对象,所以在编写代码时可以采用链式编程的快捷。

什么是链式编程?下边是连续多次调用append方法的例子,整个语句像一个链儿。

StringBuffer builder = new StringBuffer();
builder.append("hello").append("www").append("pbteach").append("com");
如实现这种连续调用append方法呢?查看append的源代码可以每个append方法返回StringBuffer对象本身
@Override
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

所以Stream接口的很多方法也直接返回了Stream对象,所以可以使用链式编程。

4)forEach(System.out::println):forEach方法是Stream接口中的方法,它的功能是对集合中的元素进行遍历,根据传入的Lambda表达式执行具体的逻辑。

通过分析代码发现Stream实现与for循环实现不同:

1)for循环是一种外部迭代方式,stream是在内部迭代

使用for循环需要程序员手动编写迭代过程,并且在迭代过程中融入很多处理逻辑,业务需求和代码逻辑参合一起不易代码维护。

Stream也经历了迭代但是无需程序员自己实现,并且将代码逻辑和业务需求进行了分离,比如:filter方法是根据传入的代码逻辑对集合的元素进行过虑,不论过滤规则如何改变迭代过程无需修改。

2)for循环实现了“怎么做”,Stream实现了“做什么”

使用for循环方式,程序员需要实现具体的处理细节,而Stream方式只需要告诉方法做什么。

比如:过虑长度大于4的字符串,分别对比两者的实现方法:

for循环实现:从数组中取出数,判断字符串长度,从头到尾实现了过虑字符串的逻辑。

stream实现:只需要向filter方法传入过虑代码逻辑即可,无需从数组取数和判断,相当于告诉filter过虑什么数据。

提问-攀博课堂
我要提问 不会就问,有效沟通
关注公众号,加入微信群交流提问。 攀博课堂官方公众号
问答列表,查看本知识点所有问题