2.使用流
2.1筛选与切片
过滤:filter()
去重:distinct()
截短:limit(int)
跳过元素:skip(int)和limit(int)互补
2.2映射
一个非常常见的数据处理套路就是从某些对象中选择信息。比如在SQL里,你可以从表中选择一列。 Stream API也通过map和flatMap方法提供了类似的工具。
对流中每一个元素应用函数
流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素.
List<User> userList = Arrays.asList(new User("12", "m", 18), new User("2", "f", 21), new User("3", "m", 27));
//提取所有用户的姓名
List<String> nameList = userList.stream().map(User::getName).collect(Collectors.toList());
nameList.forEach(name -> System.out.println(name));
System.out.println("=====================");
//求出每个用户的姓名长度
List<Integer> nameLengthList = userList.stream().map(User::getName).map(String::length).collect(Collectors.toList());
nameLengthList.forEach(name -> System.out.println(name));
流的扁平化
/**
* 给 定 单 词 列 表["Hello","World"],返回列表["H","e","l", "o","W","r","d"]
*/
List<String> worlds = Arrays.asList("Hello","World");
List<String> list = worlds.stream().map(w -> w.split(""))//将每个单词转换为由其字母构成的数组
.flatMap(Arrays::stream)//将各个生成流扁平化为单个流
.distinct()
.collect(Collectors.toList());
list.forEach(s -> System.out.print(s + " "));
使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。
一言以蔽之, flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
/**
* 给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?
* 例如,给定[1, 2, 3, 4,5],应该返回[1, 4, 9, 16, 25]
*/
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squearsList = numberList.stream().map(n -> n * n).collect(Collectors.toList());
squearsList.forEach(n -> System.out.print(n + " "));
System.out.println("====================");
/**
* 给定两个数字列表,如何返回所有的数对呢?
* 例如,给定列表[1, 2, 3]和列表[3, 4],应该返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]。
* 为简单起见,你可以用有两个元素的数组来代表数对。
*/
List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3, 4);
List<int[]> pairs = numbers1.stream()
.flatMap(i -> numbers2.stream().map(j -> new int[]{i, j}))
.collect(Collectors.toList());
pairs.forEach(pair -> {
System.out.print("[" + pair[0] + "," + pair[1] + "]");
System.out.println();
});
System.out.println("=============");
/**
* 如何扩展前一个例子,只返回总和能被3整除的数对呢?例如(2, 4)和(3, 3)是可以的
*/
List<int[]> pairs2 =
numbers1.stream()
.flatMap(i ->
numbers2.stream()
.filter(j -> (i + j) % 3 == 0)
.map(j -> new int[]{i, j})
)
.collect(Collectors.toList());
pairs2.forEach(pair -> {
System.out.print("[" + pair[0] + "," + pair[1] + "]");
System.out.println();
});
2.3查找和匹配
另一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。 Stream API通过allMatch(匹配所有元素)、 anyMatch(至少匹配一个元素)、 noneMatch(不匹配任何元素)、 findFirst和findAny方法提供了这样的工具。
List<User> userList = Arrays.asList(new User("12", "m", 18), new User("2", "f", 21), new User("3", "m", 27));
//判断所有用户中是否存在name=12的用户
boolean b = userList.stream().anyMatch(u -> Objects.equals("12", u.getName()));
System.out.println(b);
anyMatch、 allMatch和noneMatch这三个操作都用到了我们所谓的短路,这就是大家熟悉的Java中&&和||运算符短路在流中的版本。
短路求值
有些操作不需要处理整个流就能得到结果。例如,假设你需要对一个用and连起来的大布尔表达式求值。不管表达式有多长,你只需找到一个表达式为false,就可以推断整个表达式将返回false,所以用不着计算整个表达式。这就是短路。
对于流而言,某些操作 (例如allMatch、anyMatch、noneMatch、findFirst和findAny)不用处理整个流就能得到结果。只要找到一个元素,就可以有结果了。同样, limit也是一个短路操作:它只需要创建一个给定大小的流,而用不着处理流中所有的元素。在碰到无限大小的流的时候,这种操作就有用了:它们可以把无限流变成有限流。
查找元素
findAny方法将返回当前流中的任意元素。它可以与其他流操作结合使用。
List<User> userList = Arrays.asList(new User("12", "m", 21), new User("2", "f", 21), new User("3", "m", 27));
//查找age=21的任意一个用户
Optional<User> user = userList.stream().filter(u -> Objects.equals(21, u.getAge())).findAny();
System.out.println(user);
//查找age=21的任意一个用户,如果存在,打印姓名
userList.stream().filter(u -> Objects.equals(21, u.getAge()))
.findAny()
.ifPresent(u -> System.out.println(u.getName()));
//查找age=22的任意一个用户,如果存在,直接返回,否则返回一个默认值
User us = userList.stream().filter(u -> Objects.equals(22, u.getAge()))
.findAny()
.orElse(new User("0", "f", 0));
System.out.println(us);
//查找第一个age=21的用户
userList.stream().filter(u -> Objects.equals(21, u.getAge())).findFirst().ifPresent(u -> System.out.print(u));
Optional简介
Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在。在上面的代码中, findAny可能什么元素都没找到。 Java 8的库设计人员引入了Optional,这样就不用返回众所周知容易出问题的null了。
Optional里面几种可以迫使你显式地检查值是否存在或处理值不存在的情形的方法:
- isPresent()将在Optional包含值的时候返回true, 否则返回false。
- ifPresent(Consumer block)会在值存在的时候执行给定的代码块。
- T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。
- T orElse(T other)会在值存在时返回值,否则返回一个默认值。
何时使用findFirst和findAny
你可能会想,为什么会同时有findFirst和findAny呢?答案是并行。找到第一个元素在并行上限制更多。如果你不关心返回的元素是哪个,请使用findAny,因为它在使用并行流时限制较少。
2.4归约
到目前为止,你见到过的终端操作都是返回一个boolean(allMatch之类的)、 void(forEach)或Optional对象(findAny等)。你也见过了使用collect来将流中的所有元素组合成一个List。
在本节中,你将看到如何把一个流中的元素组合起来,使用reduce操作来表达更复杂的查询,比如“计算菜单中的总卡路里”或“菜单中卡路里最高的菜是哪一个”。此类查询需要将流 中所有元素反复结合起来,得到一个值,比如一个Integer。这样的查询可以被归类为归约操作(将流归约成一个值)。用函数式编程语言的术语来说,这称为折叠(fold),因为你可以将这个操作看成把一张长长的纸(你的流)反复折叠成一个小方块,而这就是折叠操作的结果。
List<Integer> numbers = Arrays.asList(4,5,3,9);
/**
* 1.元素求和
*/
//有初始值
Integer sum = numbers.stream().reduce(0, (a, b) -> a + b);
Integer sum2 = numbers.stream().reduce(0, Integer::sum);
System.out.println(sum2);
Integer product = numbers.stream().reduce(1, (a, b) -> a * b);
System.out.println(product);
//无初始值,reduce还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象(考虑到没有任何元素):
Optional<Integer> sum3 = numbers.stream().reduce((a, b) -> (a + b));
System.out.println(sum3);
/**
* 最大/小值
*/
Optional<Integer> max = numbers.stream().reduce(Integer::max);
System.out.println(max);
reduce接受两个参数:
- 一个初始值,这里是0;
- 一个BinaryOperator来将两个元素结合起来产生一个新值,这里我们用的是 lambda (a, b) -> a + b。
reduce操作是如何作用于一个流的: Lambda反复结合每个元素,直到流被归约成一个值。
List<Apple> list = Arrays.asList(new Apple("r"),new Apple("g"));
//计算list的元素个数的三种方式
int size0 = list.size();
long size1 = list.stream().count();
//map和reduce的连接通常称为map-reduce模式
Integer size3 = list.stream().map(a -> 1).reduce(0, (a, b) -> a + b);
System.out.println(size0 + " " + size1 + " " + size3);
流操作:无状态和有状态
诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。这些操作一般都是无状态的:它们没有内部状态(假设用户提供的Lambda或方法引用没有内部可变状态)。但诸如reduce、sum、max等操作需要内部状态来累积结果。在上面的情况下,内部状态很小。在我们的例子里就是一个int或double。不管流中有多少元素要处理,内部状态都是有界的.相反,诸如sort或distinct等操作一开始都和filter和map差不多——都是接受一个流,再生成一个流(中间操作),但有一个关键的区别。从流中排序和删除重复项时都需要知道先前的历史。例如,排序要求所有元素都放入缓冲区后才能给输出流加入一个项目,这一操作的存储要求是无界的。要是流比较大或是无限的,就可能会有问题(把质数流倒序会做什么呢?它应当返回最大的质数,但数学告诉我们它不存在)。我们把这些操作叫作有状态操作。
List<Apple> list = Arrays.asList(new Apple("r",1),new Apple("g",1),new Apple("r",3),new Apple("g",2));
//按照颜色给苹果分类
Map<String, List<Apple>> colorGroupList = list.stream().collect(Collectors.toMap(Apple::getColor, apple -> Lists.newArrayList(apple),
(List<Apple> newValueList, List<Apple> oldValueList) -> {
oldValueList.addAll(newValueList);
return oldValueList;
}));
colorGroupList.forEach((k,v) -> {
System.out.println(k);
v.forEach(a -> System.out.println(a));
System.out.println("========================");
});
//打印不同颜色的苹果的重量总和
colorGroupList.forEach((k,v) -> System.out.println(k + "=" + v.stream().map(Apple::getWeight).reduce(0,Integer::sum)));
public class Trader {
private final String name;
private final String city;
public Trader(String n, String c){
this.name = n;
this.city = c;
}
getter/setter()...
@Override
public String toString() {
return "Trader{" +
"name='" + name + '\'' +
", city='" + city + '\'' +
'}';
}
}
public class Transaction {
private final Trader trader;
private final int year;
private final int value;
public Transaction(Trader trader, int year, int value){
this.trader = trader;
this.year = year;
this.value = value;
}
getter/setter()...
@Override
public String toString() {
return "Transaction{" +
"trader=" + trader +
", year=" + year +
", value=" + value +
'}';
}
}
public static void main(String[] args) throws IOException {
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario","Milan");
Trader alan = new Trader("Alan","Cambridge");
Trader brian = new Trader("Brian","Cambridge");
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
//1.找出2011年发生的所有交易,并按交易额排序(从低到高)
List<Transaction> sortedTransList = transactions.stream()
.filter(t -> Objects.equals(2011, t.getYear()))
.sorted(Comparator.comparing(Transaction::getValue))
.collect(Collectors.toList());
sortedTransList.forEach(t -> System.out.println(t));
System.out.println("================");
//2.交易员都在哪些不同的城市工作过?
List<String> cityList = transactions.stream()
.map(t -> t.getTrader().getCity())
.distinct()
.collect(Collectors.toList());
cityList.forEach(c -> System.out.println(c));
System.out.println("================");
Set<String> citySet = transactions.stream()
.map(t -> t.getTrader().getCity())
.collect(Collectors.toSet());
cityList.forEach(c -> System.out.println(c));
System.out.println("================");
//3.查找所有来自于剑桥的交易员,并按姓名排序
List<Trader> traderList = transactions.stream()
.map(Transaction::getTrader)
.filter(t -> Objects.equals("Cambridge", t.getCity()))
.distinct()
.sorted(Comparator.comparing(Trader::getName))
.collect(Collectors.toList());
traderList.forEach(t -> System.out.println(t));
System.out.println("================");
//4.返回所有交易员的姓名字符串,按字母顺序排序
String name = transactions.stream()
.map(t -> t.getTrader().getName())//提取所有交易员姓名,生成一个Strings构成的Stream
.distinct()
//.sorted(String::compareTo)
.sorted()
//.collect(Collectors.toList());
.reduce("",(n1,n2) -> n1 + n2);
//nameList.forEach(s -> System.out.println(s));
System.out.println(name);
System.out.println("=================-");
/**
* 上面的方案效率不高,所有字符串都被反复连接,每次迭代的时候都要建立一个新的String对象
*/
String traderStr =
transactions.stream()
.map(transaction -> transaction.getTrader().getName())
.distinct()
.sorted()
.collect(Collectors.joining());
System.out.println(traderStr);
System.out.println("=================-");
//5.有没有交易员是在米兰工作的?
Optional<Trader> optional = transactions.stream()
.map(Transaction::getTrader)
.distinct()
.filter(trader -> Objects.equals("Milan", trader.getCity()))
.findAny();
System.out.println(optional.isPresent());
boolean match = transactions.stream()
.anyMatch(t -> Objects.equals("Milan", t.getTrader().getCity()));
System.out.println(match);
System.out.println("=================");
//6.打印生活在剑桥的交易员的所有交易额
transactions.stream()
.filter(t -> Objects.equals("Cambridge", t.getTrader().getCity()))
.map(Transaction::getValue)
.forEach(System.out::println);
Integer total = transactions.stream()
.filter(t -> Objects.equals("Cambridge", t.getTrader().getCity()))
.map(Transaction::getValue)
.reduce(0, Integer::sum);
System.out.println(total);
System.out.println("==================");
//7.所有交易中,最高的交易额是多少?
Optional<Integer> o = transactions.stream()
.map(Transaction::getValue)//提取每项交易的交易额
.reduce(Integer::max);//计算生成的流中的最大值
System.out.println(o.get());
System.out.println("====================");
//8.找到交易额最小的交易。(通过反复比较每个交易的交易额,找出最小的交易)
Optional<Transaction> smallestTransaction = transactions.stream()
.reduce((t1, t2) -> t1.getValue() < t2.getValue() ? t1 : t2);
System.out.println(smallestTransaction.get());
Optional<Transaction> smallestTransaction2 = transactions.stream()
.min(Comparator.comparing(Transaction::getValue));
System.out.println(smallestTransaction2.get());
}
2.5数值流
/**
* 以下代码的问题:它有一个暗含的装箱成本.每个Integer都必须拆箱成一个原始类型,再进行求和
*/
List<Apple> numbers = Arrays.asList(new Apple("a",1),new Apple("b",2),new Apple("c",3));
Integer totalWeight = numbers.stream().map(Apple::getWeight).reduce(0, Integer::sum);
System.out.println(totalWeight);
原始类型流特化
Java 8引入了三个原始类型特化流接口:IntStream, DoubleStream, LongStream分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。每个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max。此外还有在必要时再把它们转换回对象流的方法。要记住的是,这些特化的原因并不在于流的复杂性,而是装箱造成的复杂性——即类似int和Integer之间的效率差异。
映射到数值流
将流转换为特化版本的常用方法是mapToInt、mapToDouble和mapToLong。这些方法和前
面说的map方法的工作方式一样,只是它们返回的是一个特化流,而不是Stream<T>。
List<Apple> numbers = Arrays.asList(new Apple("a",1),new Apple("b",2),new Apple("c",3));
IntStream intStream = numbers.stream().mapToInt(Apple::getWeight);
Integer totalWeight = intStream.sum();//如果流是空的,sum()默认返回0
System.out.println(totalWeight);
/*
下面的操作是不行的,因为流只能使用一次java.lang.IllegalStateException: stream has already been operated upon or closed
OptionalInt max = intStream.max();
max.ifPresent(a -> System.out.println(a));
intStream.min().ifPresent(b -> System.out.println(b));
intStream.average().ifPresent(c -> System.out.println(c));*/
numbers.stream().mapToInt(Apple::getWeight).max().ifPresent(a -> System.out.println(a));
numbers.stream().mapToInt(Apple::getWeight).min().ifPresent(b -> System.out.println(b));
numbers.stream().mapToInt(Apple::getWeight).average().ifPresent(c -> System.out.println(c));
转换回对象流
同样,一旦有了数值流,你可能会想把它转换回非特化流。要把原始流转换成一般流(每个int都会装箱成一个Integer),可以使用boxed方法.
List<Apple> numbers = Arrays.asList(new Apple("a",1),new Apple("b",2),new Apple("c",3));
IntStream intStream = numbers.stream().mapToInt(Apple::getWeight);
Stream<Integer> boxed = intStream.boxed();
System.out.println(boxed.reduce(0,Integer::sum));
默认值OptionalInt
求和的那个例子很容易,因为它有一个默认值:0。但是,如果你要计算IntStream中的最大元素,就得换个法子了,因为0是错误的结果。如何区分没有元素的流和最大值真的是0的流呢?前面我们介绍了Optional类,这是一个可以表示值存在或不存在的容器。Optional可以用Integer、String等参考类型来参数化。对于三种原始流特化,也分别有一个Optional原始类型特化版本:OptionalInt、OptionalDouble和OptionalLong。
List<Apple> numbers = new ArrayList<>();
IntStream intStream = numbers.stream().mapToInt(Apple::getWeight);
OptionalInt optionalInt = intStream.max();
int i = optionalInt.orElse(1);//如果没有元素,则设置默认的最大值为0
System.out.println(i);
数值范围
和数字打交道时,有一个常用的东西就是数值范围。比如,假设你想要生成1和100之间的所有数字。Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:range和rangeClosed。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但range是不包含结束值的,而rangeClosed则包含结束值。
IntStream intStream1 = IntStream.rangeClosed(1, 100)
.filter(n -> n % 2 == 0);
System.out.println(intStream1.count());//50
IntStream intStream2 = IntStream.range(1, 100)
.filter(n -> n % 2 == 0);
System.out.println(intStream2.count());//49
System.out.println("======================");
//数值流应用:勾股数
/**
* ,flatMap()
* 首先,创建一个从1到100的数值范围来生成a的值。对每
*个给定的a值,创建一个三元数流。要是把a的值映射到三元数流的话,就会得到一个由流构成的
*流。flatMap方法在做映射的同时,还会把所有生成的三元数流扁平化成一个流。这样你就得到
*了一个三元数流。还要注意,我们把b的范围改成了a到100。没有必要再从1开始了,否则就会造
*成重复的三元数,例如(3,4,5)和(4,3,5)。
*/
Stream<int[]> pythagoreanTriples = IntStream.rangeClosed(1, 100)
.boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.filter(b -> Math.sqrt(a * a + b * b) % 1 == 0)
.mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)})
);
pythagoreanTriples.limit(5)
.forEach(t ->
System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
System.out.println("===================");
/**
* 进一步改善
* 先生成所有的三元数(a*a, b*b, a*a+b*b),然后再筛选符合条件的
*/
Stream<double[]> pythagoreanTriples2 =
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a ->
IntStream.rangeClosed(a, 100)
.mapToObj(
b -> new double[]{a, b, Math.sqrt(a*a + b*b)})//产生三元数
.filter(t -> t[2] % 1 == 0));//元组中的第三个元素必须是整数
pythagoreanTriples2.limit(5)
.forEach(t ->
System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
2.6构建流
希望到现在,我们已经让你相信,流对于表达数据处理查询是非常强大而有用的。到目前为止,你已经能够使用stream方法从集合生成流了。此外,我们还介绍了如何根据数值范围创建数值流。但创建流的方法还有许多!本节将介绍如何从值序列、数组、文件来创建流,甚至由生成函数来创建无限流!
由值创建流
你可以使用静态方法Stream.of,通过显式值创建一个流。它可以接受任意数量的参数。
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
由数组创建流
你可以使用静态方法Arrays.stream从数组创建一个流。它接受一个数组作为参数。
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
由文件生成流
Java中用于处理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API。java.nio.file.Files中的很多静态方法都会返回一个流。例如,一个很有用的方法是Files.lines,它会返回一个由指定文件中的各行构成的字符串流。
/**
* 统计一个文件中有多少各不相同的词
* 使用Files.lines得到一个流,其中的每个元素都是给定文件中的一行。然后,你
* 可以对line调用split方法将行拆分成单词。应该注意的是,你该如何使用flatMap产生一个扁
* 平的单词流,而不是给每一行生成一个单词流。最后,把distinct和count方法链接起来,数
* 数流中有多少各不相同的单词。
*/
long uniqueWords = 0;
try(Stream<String> lines = //流会自动关闭
Files.lines(Paths.get("D://data.txt"), Charset.defaultCharset())) {
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
}catch (IOException e){
}
System.out.println(uniqueWords);
由函数生成流:创建无限流
Stream API提供了两个静态方法来从函数生成流: Stream.iterate和Stream.generate。这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由iterate和generate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。
//打印前10个从0开始的正偶数
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
System.out.println("==================");
//用iterate方法生成斐波纳契元组序列中的前20个元素
Stream.iterate(new int[]{0,1},t -> new int[]{t[1],t[0] + t[1]})
.limit(20)
.forEach(t -> System.out.println("(" + t[0] + "," + t[1] +")"));
System.out.println("=====================");
Stream.iterate(new int[]{0, 1},
t -> new int[]{t[1],t[0] + t[1]})
.limit(10)
.map(t -> t[0])
.forEach(System.out::println);
System.out.println("======================");
/**
* 使用generate()打印序列
* IntSupplier的实例有可变的状态:它在两个实例变量中
* 记录了前一个斐波纳契项和当前的斐波纳契项。 getAsInt在调用时会改变对象的状态,由此在
* 每次调用时产生新的值。相比之下, 使用iterate的方法则是纯粹不变的:它没有修改现有状态,
* 但在每次迭代时会创建新的元组。
* 应该始终采用不变的方法,以便并行处理流,并保持结果正确。
*/
IntSupplier fib = new IntSupplier(){
private int previous = 0;
private int current = 1;
@Override
public int getAsInt(){
int oldPrevious = this.previous;
int nextValue = this.previous + this.current;
this.previous = this.current;
this.current = nextValue;
return oldPrevious;
}
};
IntStream.generate(fib).limit(10).forEach(System.out::println);
小结:
-
Streams API可以表达复杂的数据处理查询。
-
你可以使用filter、 distinct、 skip和limit对流做筛选和切片。
-
你可以使用map和flatMap提取或转换流中的元素。
-
你 可 以 使 用 findFirst 和 findAny 方 法 查 找 流 中 的 元 素 。 你 可 以 用allMatch 、noneMatch和anyMatch方法让流匹配给定的谓词。这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流。
-
你可以利用reduce方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大元素。
-
filter和map等操作是无状态的,它们并不存储任何状态。 reduce等操作要存储状态才能计算出一个值。sorted和distinct等操作也要存储状态,因为它们需要把流中的所有元素缓存起来才能返回一个新的流。这种操作称为有状态操作。
-
流有三种基本的原始类型特化: IntStream、 DoubleStream和LongStream。它们的操作也有相应的特化。
-
流不仅可以从集合创建,也可从值、数组、文件以及iterate与generate等特定方法创建。
-
无限流是没有固定大小的流。