下面来看一下借助Java 8StreamAPl,什么才叫优雅:

public class StreamIteratorDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("张无忌");
		list.add("张三丰");
		list.add("周芷若");
		list.add("赵敏");
		list.add("张三");

		list.stream()
				.filter(name->name.startsWith("张"))
				.filter(name->name.length() == 3)
				.forEach(name-> System.out.println(name));
	}
}

流式思想概述

注意:请暂时忘记对传统IO流的固有印象!

整体来看,流式思想类似于工厂车间的“生产流水线”

当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能

及便利性,我们应该首先拼好一个“模型”步骤方案,然后再按照方案去

执行它。

java 流式请求怎么理解 java流式布局特点_java

这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。

这里的filter、map、skip都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。

备注:Stream流其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。

Stream(流)是一个来自数据源的元素队列

  • 元素是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算。
  • 数据源流的来源。可以是集合,数组等。

和以前的Collection操作不同,Stream操作还有两个基础的特征:

  • Pipelining:中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格(fluent style)。这样做可以对操作进行优化,比如延迟执行(laziness)和短路(short-circuiting)。
  • 内部迭代:以前对集合遍历都是通过Iterator或者增强for的方式,显式的在集合外部进行迭代,这叫做外部迭代。Stream提供了内部迭代的方式,流可以直接调用遍历方法。

当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→数据转换一执行操作获取想要的结果,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

获取流

java.uti1.stream.stream<T>Java8新加入的最常用的流接口。(这并不是一个函数式接口)获取一个流非常简单,有以下几种常用的方式:

  • 所有的Collection集合都可以通过stream默认方法获取流;
  • stream接口的静态方法of可以获取数组对应的流。

根据Collection获取流

首先,java.util.collection 接口中加入了default方法stream用来获取流,所以其所有实现类均可获取流。

public class GetStreamDemo {
	public static void main(String[] args) {
		// 把集合转为Stream流,通过stream方法获取
		List<String> list = new ArrayList<>();
		Stream<String> listStream = list.stream();

		Set<String> set = new HashSet<>();
		Stream<String> setStream = set.stream();
	}
}

根据Map获取流

// 间接将map转为stream
Map<String, String> map = new HashMap<>();
Set<String> mapKeySet = map.keySet();
Stream<String> keySetStream = mapKeySet.stream();

Collection<String> mapValues = map.values();
Stream<String> collectionStream = mapValues.stream();

Set<Map.Entry<String, String>> mapEntrySet = map.entrySet();
Stream<Map.Entry<String, String>> entrySetStream = mapEntrySet.stream();

根据数组获取Stream

int[] helloInt = {1, 2, 3, 4, 5};
Stream<int[]> helloIntStream = Stream.of(helloInt);

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

String[] helloString = {"a", "bb", "cc"};
Stream<String> helloStringStream = Stream.of(helloString);

常用方法

java 流式请求怎么理解 java流式布局特点_java_02

流模型的操作很丰富,这里介绍一些常用的APl。这些方法可以被分成两种:

  • 延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
  • 终结方法:返回值类型不再是stream接口自身类型的方法,因此不再支持类似stringBuilder那样的链式调用。本小节中,终结方法包括 countforEach方法。

备注:本小节之外的更多方法,请自行参考API文档。

逐一处理:forEach

public class StreamForEachDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("张三");
		list.add("李四");
		list.add("王五");
		list.add("赵六");
		list.add("二麻子");

		Stream<String> stream = list.stream();
		// 使用forEach对流进行遍历
		stream.forEach(name -> System.out.println(name));

	}
}

过滤:filter

可以通过filter方法将一个流转换成另一个子集流。方法签名:

Stream<T> filter(Predicate<? super T> predicate);

该接口接收个Predicate函数式接口参数(可以是一个Lambda或方

法引用)作为筛选条件。

java 流式请求怎么理解 java流式布局特点_System_03

此前已经学习过java.util.stream.Predicate函数式接口,其中的方法为:

boolean test(T t)

该方法将会产生一个boolean值结果,代表指定的条件是否满足。如

果结果为true,那么Stream流的filter方法将会留用元素;如果

结果为false,那么filter方法将会舍弃元素。

Stream流中的filter方法基本使用的代码如:

public class StreamFilterDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("张三");
		list.add("张三丰");
		list.add("张二狗");
		list.add("赵六");
		list.add("二麻子");

		// 执行filter后会返回Stream流,可以继续对Stream流进行操作,链式调用
		Stream<String> listStream = list.stream()
				.filter(name->name.startsWith("张"))
				.filter(name->name.length() == 3);

		listStream.forEach(name -> System.out.println(name));
	}
}

Stream属于管道流,只能被消费一次,第一个Stream流被调用完毕,数据就会流转到下一个Stream上,而这时第一个Stream流已经使用完毕就关闭了,所以第一个Stream流不能再调用方法。

// 对上述代码消费两次
listStream.forEach(name -> System.out.println(name));
listStream.forEach(name -> System.out.println(name));

执行结果为:

张三丰
张二狗
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

映射:map

如果需要将流中的元素映射到另一个流中,可以使用map方法。方法签名:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。

java 流式请求怎么理解 java流式布局特点_java 流式请求怎么理解_04

此前我们已经学习过java.util.stream.Function函数式接口,其中唯一的抽象方法为:

R apply(T t);

这可以将一种T类型转换成为R类型,而这种转换的动作,就称为“映射”。

Stream流中的map方法基本使用的代码如:

public class StreamMapDemo {
	public static void main(String[] args) {
		// 获取Stream
		Stream<String> stream = Stream.of("5", "12", "34");
		//将字符串Stream转为Integer类型的Stream
		Stream<Integer> integerStream = stream.map(str -> Integer.parseInt(str));

		integerStream.forEach(num -> System.out.println(num));
	}
}

统计个数:count

正如旧集合Collection当中的size方法一样,流提供count方法来数一数其中的元素个数:

long count();

该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。基本使用:

public class StreamCountDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("A man");
		list.add("needs to");
		list.add("know");
		list.add("when to stand up");
		list.add("for himself");

		long count = list.stream().count();
		System.out.println(count);
	}
}

结果为:

5

截取:limit

limit方法可以对流进行截取,只取用前n个。方法签名:

Stream<T> limit(long maxsize);

参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。基本使用:

java 流式请求怎么理解 java流式布局特点_java_05

public class StreamLimitDemo {
	public static void main(String[] args) {
		// 获取一个Stream流
		String[] strings = {"万界神主","斗罗大陆","斗破苍穹"};
		Stream<String> stringStream = Stream.of(strings);
		// 使用limit方法对Stream流中的元素截取只要前2个
		stringStream.limit(2).forEach(name-> System.out.println(name));
	}
}

运行结果:

万界神主
斗罗大陆

跳过:skip

如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:

Stream<T> skip(long n);

如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:

java 流式请求怎么理解 java流式布局特点_Stream_06

/**
 * Stream流中的Skip方法:用于跳过元素
 * 如果希望跳过前几个元素,可以使用skip方法获取一个截取后的新流
 * 如果流的当前长度大于n,则跳过前n个,否则会得到一个长度为0的空流
 * @author guqin
 * @date 2019-07-23 20:53
 */
public class StreamSkipDemo {
	public static void main(String[] args) {
		String[] strings = {"万界神主","斗罗大陆","斗破苍穹","天行九歌"};
		Stream<String> stringStream = Stream.of(strings);

		stringStream.skip(2)
				.forEach(name-> System.out.println(name));
	}
}

运行结果:

斗破苍穹
天行九歌

组合:concat

如果有两个流,希望合并成为一个流,那么可以使用 stream 接口的静态方法concat

static <T> Stream<T> concat(Stream<? extends T) a, Stream<? extends T) b);

备注:这是一个静态方法,与java.lang.String当中的concat方法是不同的。

该方法的基本使用代码如:

public class StreamConcatDemo {
	public static void main(String[] args) {
		Stream<String> stringStream1 = Stream.of("万界神主","斗罗大陆");
		Stream<String> stringStream2 = Stream.of("斗破苍穹","天行九歌");

		Stream.concat(stringStream1, stringStream2)
				.forEach(name -> System.out.println(name));
	}
}

运行结果:

万界神主
斗罗大陆
斗破苍穹
天行九歌