Java-8-流(1)
外部迭代与内部迭代
Java 程序员在使用集合类时,一个通用的模式是在集合上进行迭代,然后处理返回的每一
个元素
在数字集合里面统计大于100的数有几个
public static void main(String[] args) {
List<Integer> data = Number_Data.createData();
int count = 0;
for (Integer integer : data){
if (integer > 100){
count++;
}
}
System.out.println(count);
}
尽管这样的操作可行,但存在几个问题。每次迭代集合类时,都需要写很多样板代码。将for 循环改造成并行方式运行也很麻烦,需要修改每个 for 循环才能实现
for 循环的样板代码模糊了代码的本意,程序员必须阅读整个循环体才能理解。若是单一的 for 循环,倒也问题不大,但面对一个满是循环(尤其是嵌套循环)的庞大代码库时,负担就重了
for 循环其实是一个封装了迭代的语法糖看看它的工作原理。首先调用 iterator 方法,产生一个新的 Iterator 对象,进而控制整个迭代过程,这就是外部迭代。迭代过程通过显式调用 Iterator 对象的 hasNext 和 next方法完成迭代
使用迭代器在数字集合里面统计大于100的数有几个
int num = 0;
Iterator<Integer> integerIterator = data.iterator();
while (integerIterator.hasNext()){
if (integerIterator.next() > 100){
num++;
}
}
System.out.println(num);
简单理解外部迭代就是由用户来决定”做什么“和”怎么做“的操作
另一种方法就是内部迭代
使用内部迭在数字集合里面统计大于100的数有几个
List<Integer> data = Number_Data.createData();
long count = data.stream()
.filter(integer -> integer > 100)
.count();
System.out.println(count);
每种操作都对应 Stream 接口的一个方法。为了找出来大于100的数有几个,需要对 Stream 对象进行过滤: filter 。过滤在这里是指“只保留通过某项测试的对象”。测试由一个函数完成,根据数字是否大于100,该函数返回 true 或者 false 。由于 Stream API 的函数式编程风格,我们并没有改变集合的内容,而是描述出 Stream 里的内容。 count() 方法计算给定 Stream 里包含多少个对象
内部迭代我们只需要提供”做什么“,把”怎么做“的任务交给了 JVM
使用内部迭代可以带来的好处:
-
用户只需要关注问题,无需关注如何解决问题的细节
-
使得 JVM可以利用短路、并行等对性能的提升变成可能
Stream
Stream(流)是一个来自数据源的元素队列并支持聚合操作
-
元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
-
数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
-
聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
以前的Collection操作不同, Stream操作还有两个基础的特征:
-
Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
-
内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
在 Java 8 中, 集合接口有两个方法来生成流:
-
stream() − 为集合创建串行流。
-
parallelStream() − 为集合创建并行流。
流的操作类型
-
Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历
-
Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?
其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。
流的构造与转换
public class M3 {
public static void main(String[] args) {
Stream stream1;
Stream stream2;
Stream stream3;
//由单独的值构成
Stream<String> stringStream = Stream.of("sjd","kjfu","4545");
//由数组构成
String[] strings = new String[]{"a","ab","abc"};
stream1 = Stream.of(strings);
stream2 = Arrays.stream(strings);
// //由集合构成,最常用了
List<String> list = Arrays.asList(strings);
stream3 = list.stream();
//对于基本数值型,目前有三种对应的包装类型的Stream:IntStream、LongStream、DoubleStream
IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
System.out.println("========================================");
IntStream.range(1, 3).forEach(System.out::println);
System.out.println("========================================");
IntStream.rangeClosed(1, 3).forEach(System.out::println);
}
}
自己构造流
Stream.generate
Stream.generate通过实现 Supplier 接口,你可以自己来控制流的生成
public static void main(String[] args) {
// //生成100以内的30个随机整数,用来构造测试随机数不失为一种简便的方式
Stream.generate(() ->
new Random().nextInt(100))
.limit(30).forEach(System.out::println);
System.out.println("=================================");
IntStream.generate(() ->
(int) (System.nanoTime() % 100)).limit(20).forEach(System.out::println);
System.out.println("=================================");
// //random其实提供了更方便的ints()方法
new Random().ints().limit(10).forEach(System.out::println);
}
Stream.generate() 还接受自己实现的 Supplier。例如在构造海量测试数据