流式编程
声明式编程是一种编程风格,它声明了要做什么,而不是指明(每一步)如何做。
流是懒加载的,这代表着它只在绝对必要时才计算。可以将流看作延迟列表。由于计算延迟,流能够表示非常大(甚至无限)的序列,而不需要考虑内存问题。
14.1流支持
流操作的类型有三种:创建流,修改流元素(中间操作),消费流元素(终端操作)。最后一种类型通常意味着收集流元素(通常是汇入一个集合)。
14.2流创建
可以通过
Stream.of()
很容易地将一组元素转化成为流。
public class Bubble {
public final int i;
public Bubble(int n) {
i = n;
}
@Override
public String toString() {
return "Bubble(" + i + ")";
}
private static int count = 0;
public static Bubble bubbler() {
return new Bubble(count++);
}
}
public class StreamOf {
public static void main(String[] args) {
Stream.of(new Bubble(1), new Bubble(2), new Bubble(3)).forEach(System.out::println);
Stream.of("It's ", "a ", "wonderful ", "day ", "for ", "pie!").forEach(System.out::print);
System.out.println();
Stream.of(3.14159, 2.718, 1.618).forEach(System.out::println);
}
}
除此之外,每个集合都可以通过
stream()
方法来产生一个流:
public class CollectionToStream {
public static void main(String[] args) {
List<Bubble> bubbles = Arrays.asList(new Bubble(1), new Bubble(2), new Bubble(3));
System.out.println(bubbles.stream().mapToInt(b -> b.i).sum());
Set<String> w = new HashSet<>(Arrays.asList("It's a wonderful day for pie!".split(" ")));
w.stream().map(x -> x + " ").forEach(System.out::print);
System.out.println();
Map<String, Double> m = new HashMap<>();
m.put("pi", 3.14159);
m.put("e", 2.718);
m.put("phi", 1.618);
m.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue()).forEach(System.out::println);
}
}
- 中间操作
map()
会获取流中的所有元素,并且对流中元素应用操作从而产生新的元素,并将其传递到后续的流中。mapToInt()
将一个对象流转换成包含整型数字的IntStream
。同样,针对float
和double
也有类似名字的操作。- 为了从
Map
集合中产生流数据,首先调用entrySet
产生一个对象流,每个对象都包含一个键以及与其相关联的值。
14.2.1随机数流
Random
类只能生成基本类型int
,long
,double
的流。幸运的是,boxed
流操作将会自动地把基本类型包装成对应的装箱类型。
public class RandomWords implements Supplier<String> {
List<String> words = new ArrayList<>();
Random rand = new Random(47);
RandomWords(String fname) throws IOException {
List<String> lines = Files.readAllLines(Paths.get(fname));
// 略过第一行
for (String line : lines.subList(1, lines.size())) {
for (String word : line.split("[ .?,]+")) {
words.add(word.toLowerCase());
}
}
}
@Override
public String get() {
return words.get(rand.nextInt(words.size()));
}
@Override
public String toString() {
return words.stream().collect(Collectors.joining(" "));
}
public static void main(String[] args) throws Exception {
System.out.println(Stream.generate(new RandomWords("Cheese.dat"))
.limit(10)
.collect(Collectors.joining(" ")));
}
}
collect()
操作根据参数来结合所有的流元素。当使用Collectors.joining()
作为参数时,将得到一个String
类型的结果,该结果是流中的所有元素被joining()
的参数隔开。Stream.generate()
可以把任意Supplier<T>
用于生成T
类型的流。
14.2.2int
类型的范围
IntStream
类提供了range()
方法用于生成整型序列的流。编写循环时,这个方法会更加便利:
public class Ranges {
public static void main(String[] args) {
// 传统方法
int result = 0;
for (int i = 10; i < 20; i++) {
result += i;
}
System.out.println(result);
// for-in循环
result = 0;
for (int i : IntStream.range(10, 20).toArray()) {
result += i;
}
System.out.println(result);
// 使用流
System.out.println(IntStream.range(10, 20).sum());
}
}
repeat()
可以用来替换简单的for
循环:
public class Repeat {
public static void repeat(int n, Runnable action) {
IntStream.range(0, n).forEach(i -> action.run());
}
}
public class Looping {
static void hi() {
System.out.println("Hi!");
}
public static void main(String[] args) {
Repeat.repeat(3, () -> System.out.println("Looping!"));
Repeat.repeat(2, Looping::hi);
}
}
14.2.3generate()
public class Generator implements Supplier<String> {
Random rand = new Random(47);
char[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
@Override
public String get() {
return "" + letters[rand.nextInt(letters.length)];
}
public static void main(String[] args) {
String word = Stream.generate(new Generator())
.limit(30)
.collect(Collectors.joining());
System.out.println(word);
}
}
如果要创建包含相同对象的流,只需要创建一个生成那些对象的lambda到
generate()
中:
public class Bubbles {
public static void main(String[] args) {
// bubbler()与Supplier<Bubble>是接口兼容的,可以将其方法引用直接作为参数传递
Stream.generate(Bubble::bubbler)
.limit(5)
.forEach(System.out::println);
}
}
14.2.4iterate()
Stream.iterate()
产生的流的第一个元素是种子(iterate
方法的第一个参数),然后将种子传递给方法(iterate
方法的第二个参数)。方法运行的结果被添加到流(作为流的下一个元素),并被存储起来,作为下次调用iterate()
时的第一个参数,以此类推。
// 生成一个斐波那契数列
public class Fibonacci {
int x = 1;
Stream<Integer> numbers() {
return Stream.iterate(0, i -> {
int result = x + i;
x = i;
return result;
});
}
public static void main(String[] args) {
new Fibonacci().numbers()
.skip(20) // 过滤前20个
.limit(10) // 然后取10个
.forEach(System.out::println);
}
}
14.2.5流的建造者模式
在建造者模式中,首先创建一个
builder
对象,然后将创建流所需的多个信息传递给它,最后builder
对象执行创建流的操作。Stream
库提供了这样的Builder
。
public class FileToWordsBuilder {
Stream.Builder<String> builder = Stream.builder();
// 构造器会添加文件中的所有单词,除了第一行,但是并没有调用build()。
// 只要不调用stream()方法,就可以继续向builder对象中添加单词
public FileToWordsBuilder(String filePath) throws Exception {
Files.lines(Paths.get(filePath))
.skip(1) // 略过开头的注释行
.forEach(line -> {
for (String w : line.split("[ .?,]+")) {
builder.add(w);
}
});
}
Stream<String> stream() {
return builder.build();
}
public static void main(String[] args) throws Exception {
new FileToWordsBuilder("Cheese.dat")
.stream()
.limit(7)
.map(w -> w + " ")
.forEach(System.out::print);
}
}
14.2.6Arrays
Arrays
类中含有一个名为stream()
的静态方法用于把数组转换成为流。同样的,根据参数类型的不同,stream()
也可以产生IntStream
,LongStream
和DoubleStream
。
14.3中间操作
中间操作用于从一个流中获取对象,并将对象作为另一个流从后端输出,以连接到其他操作。
14.3.1跟踪和调试
peek()
操作的目的是帮助调试。它允许无修改地查看流中的元素。
public class FileToWords {
public static Stream<String> stream(String filePath) throws Exception {
return Files.lines(Paths.get(filePath))
.skip(1)
.flatMap(line -> Pattern.compile("\\W+").splitAsStream(line));
}
}
public class Peeking {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat")
.skip(21)
.limit(4)
.map(w -> w + " ")
.peek(System.out::print)
.map(String::toUpperCase)
.peek(System.out::print)
.map(String::toLowerCase)
.forEach(System.out::print);
}
}
// 输出:
// Well WELL well it IT it s S s so SO so
14.3.2流元素排序
sorted()
还有另一种形式的实现:传入一个Comparator
参数。
14.3.3移除元素
distinct()
:用于消除流中的重复元素。相比创建一个Set
集合来消除重复,该方法的工作量要少得多。filter(Predicate)
:过滤操作,保留如下元素:若元素传递给过滤函数产生的结果为true
。
14.3.4应用函数到元素
map(Function)
:将函数操作应用在输入流的元素中,并将返回值传递到输出流中。mapToInt(ToIntFunction)
:操作同上,但结果是IntStream
。mapToLong(ToLongFunction)
:操作同上,但结果是LongStream
。mapToDouble(ToDoubleFunction)
:操作同上,但结果是DoubleStream
。
public class FunctionMap {
static String[] elements = { "12", "", "23", "45" };
static Stream<String> testStream() {
return Arrays.stream(elements);
}
static void test(String descr, Function<String, String> func) {
System.out.println("---( " + descr + " )---");
testStream().map(func)
.forEach(System.out::println);
}
public static void main(String[] args) {
test("add brackets", s -> "[" + s + "]");
test("Increment", s -> {
try {
return Integer.parseInt(s) + 1 + "";
} catch (NumberFormatException e) {
return s;
}
});
test("Replace", s -> s.replace("2", "9"));
test("Take last digit", s -> s.length() > 0 ? s.charAt(s.length() - 1) + "" : s);
}
}
也可以产生和接收类型完全不同的类型,从而改变流的数据类型:
public class FunctionMap2 {
public static void main(String[] args) {
Stream.of(1, 5, 7, 9, 11, 13)
.map(Numbered::new)
.forEach(System.out::println);
}
}
class Numbered {
final int n;
Numbered(int n) {
this.n = n;
}
@Override
public String toString() {
return "Numbered(" + n + ")";
}
}
如果使用
Function
返回的结果是数值类型的一种,必须使用合适的mapTo
数值类型进行替代。
public class FunctionMap3 {
public static void main(String[] args) {
Stream.of("5", "7", "9")
.mapToInt(Integer::parseInt)
.forEach(n -> System.out.format("%d ", n));
System.out.println();
Stream.of("17", "19", "23")
.mapToLong(Long::parseLong)
.forEach(n -> System.out.format("%d ", n));
System.out.println();
Stream.of("17", "1.9", ".23")
.mapToDouble(Double::parseDouble)
.forEach(n -> System.out.format("%f ", n));
}
}
14.3.5在map()
中组合流
flatMap()
做了两件事:将产生流的函数应用在每个元素上(与map()
所做的相同),然后将每个流都扁平化为元素,因而最终产生的仅仅是元素。flatMap(Function)
:当Function
产生流时使用。flatMapToInt(Function)
:当Function
产生IntStream
时使用。flatMapToLong(Function)
:当Function
产生LongStream
时使用。flatMapToDouble(Function)
:当Function
产生DoubleStream
时使用。
public class StreamOfStreams {
public static void main(String[] args) {
Stream.of(1, 2, 3)
.map(i -> Stream.of("Gonzo", "Kermit", "Beaker"))
.map(e -> e.getClass().getName())
.forEach(System.out::println);
}
}
// 输出结果(希望得到的是字符串流,但与实际不符,可以使用flatMap()解决这个问题):
// java.util.stream.ReferencePipeline$Head
// java.util.stream.ReferencePipeline$Head
// java.util.stream.ReferencePipeline$Head
// 从映射返回的每个流都会自动扁平为组成它的字符串
public class FlatMap {
public static void main(String[] args) {
Stream.of(1, 2, 3)
.flatMap(i -> Stream.of("Gonzo", "Fozzie", "Beaker"))
.forEach(System.out::println);
}
}
public class StreamOfRandoms {
static Random rand = new Random(47);
public static void main(String[] args) {
Stream.of(1, 2, 3, 4, 5)
.flatMapToInt(i -> IntStream.concat(rand.ints(0, 100).limit(i), IntStream.of(-1)))
.forEach(n -> System.out.format("%d ", n));
}
}
// 输出结果:
// 58 -1 55 93 -1 61 61 29 -1 68 0 22 7 -1 88 28 51 89 9 -1
// 使用flatMap()来解决将文件划分为单词流的任务中,需要将整个文件读入行列表中的问题
public class FileToWords {
public static Stream<String> stream(String filePath) throws Exception {
return Files.lines(Paths.get(filePath))
.skip(1)
.flatMap(line -> Pattern.compile("\\W+").splitAsStream(line));
}
}
// 因为有了真正的流,所以每次需要一个新的流时,都必须从头开始创建,因为流不能被复用
public class FileToWordsTest {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat")
.limit(7)
.forEach(s -> System.out.format("%s ", s));
System.out.println();
FileToWords.stream("Cheese.dat")
.skip(7)
.limit(2)
.forEach(s -> System.out.format("%s ", s));
}
}
14.4Optional
类
一些标准流操作返回
Optional
对象,因为它们并不能保证预期结果一定存在。包括:
findFirst()
返回一个包含第一个元素的Optional
对象,如果流为空则返回Optional.empty
。findAny()
返回包含任意元素的Optional
对象,如果流为空则返回Optional.empty
。max()
和min()
返回一个包含最大值或最小值的Optional
对象,如果流为空则返回Optional.empty
。
reduce()
不再以identity
形式开头,而是将其返回值包装在Optional
中(identity
对象成为其他形式的reduce()
的默认结果,因此不存在空结果的风险)。
对于数字流IntStream
、LongStream
和DoubleStream
,average()
会将结果包装在Optional
以防止流为空。
public class OptionalsFromEmptyStreams {
public static void main(String[] args) {
// 如果使用Stream.empty(),java并不知道它的数据类型
System.out.println(Stream.<String>empty().findFirst());
System.out.println(Stream.<String>empty().findAny());
System.out.println(Stream.<String>empty().max(String.CASE_INSENSITIVE_ORDER));
System.out.println(Stream.<String>empty().min(String.CASE_INSENSITIVE_ORDER));
System.out.println(Stream.<String>empty().reduce((s1, s2) -> s1 + s2));
System.out.println(IntStream.empty().average());
}
}
public class OptionalBasics {
static void test(Optional<String> optString) {
if (optString.isPresent()) {
System.out.println(optString.get());
} else {
System.out.println("Nothing inside!");
}
}
public static void main(String[] args) {
test(Stream.of("Epithets").findFirst());
test(Stream.<String>empty().findFirst());
}
}
当接收到
Optional
对象时,应首先调用isPresent()
检查其中是否包含元素。如果存在,可使用get()
获取。
14.4.1便利函数
有许多便利函数可以解包
Optional
:
ifPresent(Consumer)
:当值存在时调用Consumer
,否则什么也不做。orElse(otherObject)
:如果值存在则直接返回,否则生成otherObject
。orElseGet(Supplier)
:如果值存在则直接返回,否则使用Supplier
函数生成一个可替代对象。orElseThrow(Supplier)
:如果值存在则直接返回,否则使用Supplier
函数生成一个异常。
public class Optionals {
static void basics(Optional<String> optString) {
if (optString.isPresent()) {
System.out.println(optString.get());
} else {
System.out.println("Nothing inside!");
}
}
static void ifPresent(Optional<String> optString) {
optString.ifPresent(System.out::println);
}
static void orElse(Optional<String> optString) {
System.out.println(optString.orElse("Nada"));
}
static void orElseGet(Optional<String> optString) {
System.out.println(optString.orElseGet(() -> "Generated"));
}
static void orElseThrow(Optional<String> optString) {
try {
System.out.println(optString.orElseThrow(() -> new Exception("Supplied")));
} catch (Exception e) {
System.out.println("Caught " + e);
}
}
static void test(String testName, Consumer<Optional<String>> cos) {
System.out.println(" === " + testName + " === ");
cos.accept(Stream.of("Epithets").findFirst());
cos.accept(Stream.<String>empty().findFirst());
}
public static void main(String[] args) {
test("basics", Optionals::basics);
test("ifPresent", Optionals::ifPresent);
test("orElse", Optionals::orElse);
test("orElseGet", Optionals::orElseGet);
test("orElseThrow", Optionals::orElseThrow);
}
}
14.4.2创建Optional
当在自己的代码中加入
Optional
时,可以使用下面的静态方法:
empty()
:生成一个空Optional
。of(value)
:将一个非空值包装到Optional
里。ofNullable(value)
:针对一个可能为空的值,为空时自动生成Optional.empty
,否则将值包装在Optional
中。
public class CreatingOptionals {
static void test(String testName, Optional<String> opt) {
System.out.println(" === " + testName + " === ");
System.out.println(opt.orElse("Null"));
}
public static void main(String[] args) {
test("empty", Optional.empty());
test("of", Optional.of("Howdy"));
try {
// 不能通过传递null到of()来创建Optional对象
test("of", Optional.of(null));
} catch (Exception e) {
System.out.println(e);
}
test("ofNullable", Optional.ofNullable("Hi"));
test("ofNullable", Optional.ofNullable(null));
}
}
14.4.3Optional
对象操作
当流管道生成了
Optional
对象,下面的方法可使得Optional
的后续能做更多的操作:
filter(Predicate)
:对Optional
中的内容应用Predicate
并将结果返回。如果Optional
不满足Predicate
,将Optional
转化为空Optional
。如果Optional
已经为空,则直接返回空Optional
。map(Function)
:如果Optional
不为空,应用Function
于Optional
中的内容,并返回结果。否则直接返回Optional.empty
。flatMap(Function)
:同map()
,但是提供的映射函数将结果包装在Optional
对象中,因此flatMap()
不会在最后进行任何包装。以上方法都不适用于数值型
Optional
。
public class OptionalFilter {
static String[] elements = { "Foo", "", "Bar", "Baz", "Bingo" };
static Stream<String> testStream() {
return Arrays.stream(elements);
}
static void test(String descr, Predicate<String> pred) {
System.out.println("---( " + descr + " )---");
for (int i = 0; i <= elements.length; i++) {
System.out.println(testStream()
.skip(i)
.findFirst()
.filter(pred));
}
}
public static void main(String[] args) {
test("true", str -> true);
test("false", str -> false);
test("str != \"\"", str -> str != "");
test("str.length() == 3", str -> str.length() == 3);
test("startsWith(\"B\")", str -> str.startsWith("B"));
}
}
同
map()
一样,Optional.map()
执行一个函数。它仅在Optional
不为空时才执行这个映射函数,并将Optional
的内容提取出来,传递给映射函数。
public class OptionalMap {
static String[] elements = { "12", "", "23", "45" };
static Stream<String> testStream() {
return Arrays.stream(elements);
}
static void test(String descr, Function<String, String> func) {
System.out.println("---( " + descr + " )---");
for (int i = 0; i <= elements.length; i++) {
System.out.println(testStream()
.skip(i)
.findFirst() // produces an Optional
.map(func));
}
}
public static void main(String[] args) {
// If Optional is not empty, map() first extracts
// the contents which it then passes to the function
test("Add brackets", s -> "[" + s + "]");
test("Increment", s -> {
try {
return Integer.parseInt(s) + 1 + "";
} catch (NumberFormatException e) {
return s;
}
});
test("Replace", s -> s.replace("2", "9"));
test("Take last digit", s -> s.length() > 0 ? s.charAt(s.length() - 1) + "" : s);
// After the function is finished, map() wraps the
// result in an Optional before returning it
}
}
映射函数的返回结果会自动包装成为
Optional
。Optional.empty
会被直接跳过。
Optional
的flatMap()
应用于已生成Optional
的映射函数,所以flatMap()
不会像map()
那样将结果封装在Optional
中。
public class OptionalFlatMap {
static String[] elements = { "12", "", "23", "45" };
static Stream<String> testStream() {
return Arrays.stream(elements);
}
static void test(String descr, Function<String, Optional<String>> func) {
System.out.println("---( " + descr + " )---");
for (int i = 0; i <= elements.length; i++) {
System.out.println(testStream()
.skip(i)
.findFirst()
.flatMap(func));
}
}
public static void main(String[] args) {
test("Add brackets", s -> Optional.of("[" + s + "]"));
test("Increment", s -> {
try {
return Optional.of(Integer.parseInt(s) + 1 + "");
} catch (NumberFormatException e) {
return Optional.of(s);
}
});
test("Replace", s -> Optional.of(s.replace("2", "9")));
test("Take last digit", s -> Optional.of(s.length() > 0 ? s.charAt(s.length() - 1) + "" : s));
}
}
同
map()
,flatMap()
将提取非空Optional
的内容并将其应用在映射函数。唯一的区别就是flatMap()
不会把结果包装在Optional
中,因为映射函数已经被包装过了。很显然,Optional.flatMap()
是为那些自己已经生成Optional
的函数而设计的。
14.4.4Optional
流
假设生成器可能产生
null
值,那么当用它来创建流时,会自然地想到用Optional
来包装元素。
public class Signal {
private final String msg;
public Signal(String msg) {
this.msg = msg;
}
public String getMsg() {
return msg;
}
@Override
public String toString() {
return "Signal(" + msg + ")";
}
static Random rand = new Random(47);
public static Signal morse() {
switch (rand.nextInt(4)) {
case 1: {
return new Signal("dot");
}
case 2: {
return new Signal("dash");
}
default: return null;
}
}
public static Stream<Optional<Signal>> stream() {
return Stream.generate(Signal::morse)
.map(signal -> Optional.ofNullable(signal));
}
}
// 当使用这个流的时候,必须要弄清楚如何解包Optional
public class StreamOfOptionals {
public static void main(String[] args) {
Signal.stream()
.limit(10)
.forEach(System.out::println);
System.out.println("---");
Signal.stream()
.limit(10)
// 使用filter来保留那些非空Optional
.filter(Optional::isPresent)
// 在map中使用get来获取元素
.map(Optional::get)
.forEach(System.out::println);
}
}
14.5终端操作
以下操作将会获得流的最终结果。至此无法再继续往后传递流。可以说,终端操作总是在流管道中所做的最后一件事。
14.5.1数组
toArray()
:将流转换成适当类型的数组。toArray(generator)
:在特殊情况下,生成自定义类型的数组。
public class RandInts {
private static int[] rints = new Random(47).ints(0, 1000).limit(100).toArray();
public static IntStream rands() {
return Arrays.stream(rints);
}
}
14.5.2循环
forEach(Consumer)
常见如System.out::println
作为Consumer
函数。forEachOrdered(Consumer)
:保证forEach
按照原始流顺序操作。
14.5.3集合
collect(Collector)
:使用Collector
收集流元素到结果集合中。collect(Supplier, BiConsumer, BiConcumer)
:同上,第一个参数Supplier
创建了一个新的结果集合,第二个参数BiConsumer
将下一个元素收集到结果集合中,第三个参数BiConsumer
用于将两个结果集合合并起来。
假设现在为了保证元素有序,将元素存储在
TreeSet
中。Collectors
里面没有特定的toTreeSet()
,但是可以通过将集合的构造函数引用传递给Collectors.toCollection()
,从而构建任何类型的集合。例如,将一个文件中的单词收集到TreeSet
集合中。
public class TreeSetOfWords {
public static void main(String[] args) throws IOException {
// 转换成为由行组成的流
Set<String> words2 = Files.lines(Paths.get("TreeSetOfWords.java"))
.flatMap(s -> Arrays.stream(s.split("\\W+")))
.filter(s -> !s.matches("\\d+")) // no numbers
.map(String::trim)
.filter(s -> s.length() > 2)
.limit(100)
.collect(Collectors.toCollection(TreeSet::new));
}
}
也可以在流中生成
Map
:
public class MapCollector {
public static void main(String[] args) {
Map<Integer, Character> map = new RandomPair().stream()
.limit(8)
.collect(Collectors.toMap(Pair::getI, Pair::getC));
System.out.println(map);
}
}
class Pair {
public final Character c;
public final Integer i;
Pair(Character c, Integer i) {
this.c = c;
this.i = i;
}
public Character getC() {
return c;
}
public Integer getI() {
return i;
}
@Override
public String toString() {
return "Pair(" + c + ", " + i + ")";
}
}
class RandomPair {
Random rand = new Random(47);
// An infinite iterator of random capital letters
Iterator<Character> capChars = rand.ints(65, 91)
.mapToObj(i -> (char) i)
.iterator();
public Stream<Pair> stream() {
return rand.ints(100, 1000).distinct()
// 在java中,不能直接以某种方式组合两个流,所以创建了一个整数流,
// 并且使用mapToObj将整数流转化称为Pair流
.mapToObj(i -> new Pair(capChars.next(), i));
}
}
14.5.4组合
reduce(BinaryOperator)
:使用BinaryOperator
来组合所有流中的元素。因为流可能为空,其返回值为Optional
。reduce(identity, BinaryOperator)
:功能同上,但是使用identity
作为其组合的初始值。因此,如果流为空,identity
就是结果。reduce(identity, BiFunction, BinaryOperator)
:更复杂的使用形式(暂不介绍)。通常,可以显式地组合map
和reduce
来更简单的表达它。
public class Reduce {
public static void main(String[] args) {
Stream.generate(Frobnitz::supply)
.limit(10)
.peek(System.out::println)
.reduce((fr0, fr1) -> fr0.size < 50 ? fr0 : fr1)
.ifPresent(System.out::println);
}
}
class Frobnitz {
int size;
Frobnitz(int sz) {
size = sz;
}
@Override
public String toString() {
return "Frobnitz(" + size + ")";
}
// generator:
static Random rand = new Random(47);
static final int BOUND = 100;
static Frobnitz supply() {
return new Frobnitz(rand.nextInt(BOUND));
}
}
14.5.5匹配
allMatch(Predicate)
:如果流的每个元素提供给Predicate
都返回true
,结果返回为true
。在第一个false
时,则停止执行计算。anyMatch(Predicate)
:如果流的任意一个元素提供给Predicate
返回true
,结果返回为true
。在第一个true
时停止执行计算。noneMatch(Predicate)
:如果流的每个元素提供给Predicate
都返回false
时,结果返回为true
。在第一个true
时停止执行计算。
public class Matching {
static void show(Matcher match, int val) {
System.out.println(
match.test(
IntStream.rangeClosed(1, 9)
.boxed()
// 用来展示在测试短路之前测试进行到了哪一步
.peek(n -> System.out.format("%d ", n)),
n -> n < val)
);
}
public static void main(String[] args) {
show(Stream::allMatch, 10);
show(Stream::allMatch, 4);
show(Stream::anyMatch, 2);
show(Stream::anyMatch, 0);
show(Stream::noneMatch, 5);
}
}
interface Matcher extends BiPredicate<Stream<Integer>, Predicate<Integer>> {
}
14.5.6查找
findFirst()
:返回第一个流元素的Optional
,如果流为空返回Optional.empty
。findAny()
:返回含有任意流元素的Optional
,如果流为空返回Optional.empty
。
public class SelectElement {
public static void main(String[] args) {
System.out.println(RandInts.rands().findFirst().getAsInt());
System.out.println(RandInts.rands().parallel().findFirst().getAsInt());
System.out.println(RandInts.rands().findAny().getAsInt());
System.out.println(RandInts.rands().parallel().findAny().getAsInt());
}
}
如果必须选择流中最后一个元素,那就使用
reduce()
:
public class LastElement {
public static void main(String[] args) {
OptionalInt last = IntStream.range(10, 20).reduce((n1, n2) -> n2);
System.out.println(last.orElse(-1));
// non-numeric object:
Optional<String> lastObj = Stream.of("one", "two", "three").reduce((n1, n2) -> n2);
System.out.println(lastObj.orElse("Nothing there!"));
}
}
14.5.7信息
count()
:流中的元素个数。max(Comparator)
:根据所传入的Comparator
所决定的最大元素。min(Comparator)
:根据所传入的Comparator
所决定的最小元素。
public class Informational {
public static void main(String[] args) throws Exception {
System.out.println(FileToWords.stream("Cheese.dat")
.count());
System.out.println(FileToWords.stream("Cheese.dat")
.min(String.CASE_INSENSITIVE_ORDER)
.orElse("NONE"));
System.out.println(FileToWords.stream("Cheese.dat")
.max(String.CASE_INSENSITIVE_ORDER)
.orElse("NONE"));
}
}
14.5.8数字流信息
average()
:求取流元素平均值。max()
和min()
:数值流操作无需Comparator
。sum()
:对所有流元素进行求和。summaryStatistics()
:生成可能有用的数据。目前并不清楚这个方法存在的必要性。
public class NumericStreamInfo {
public static void main(String[] args) {
System.out.println(RandInts.rands().average().getAsDouble());
System.out.println(RandInts.rands().max().getAsInt());
System.out.println(RandInts.rands().min().getAsInt());
System.out.println(RandInts.rands().sum());
System.out.println(RandInts.rands().summaryStatistics());
}
}