14.流式编程

流式编程

声明式编程是一种编程风格,它声明了要做什么,而不是指明(每一步)如何做。

流是懒加载的,这代表着它只在绝对必要时才计算。可以将流看作延迟列表。由于计算延迟,流能够表示非常大(甚至无限)的序列,而不需要考虑内存问题。

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。同样,针对floatdouble也有类似名字的操作。
  • 为了从Map集合中产生流数据,首先调用entrySet产生一个对象流,每个对象都包含一个键以及与其相关联的值。
14.2.1随机数流

Random类只能生成基本类型intlongdouble的流。幸运的是,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()也可以产生IntStreamLongStreamDoubleStream

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()的默认结果,因此不存在空结果的风险)。
对于数字流IntStreamLongStreamDoubleStreamaverage()会将结果包装在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不为空,应用FunctionOptional中的内容,并返回结果。否则直接返回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
    }
}

映射函数的返回结果会自动包装成为OptionalOptional.empty会被直接跳过。
OptionalflatMap()应用于已生成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):更复杂的使用形式(暂不介绍)。通常,可以显式地组合mapreduce来更简单的表达它。
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());
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值