关于程序员:体验Java8的流式编程

33次阅读

共计 19280 个字符,预计需要花费 49 分钟才能阅读完成。

介绍

JDK 8 不止新增了 Lambda 表达式,还有 Stream 流,程序员通过 Stream 流来简化对数据的解决。其本质就是计算。

能够这么了解:流就是数据通道,用于操作数据源所生成的元素序列。

咱们来相熟一下 Stream 流:

public class StringSorting {public static void main(String[] args) {
        Stream.of("Java", "Python", "C++",
                "C", "Shell", "Ruby",
                "Scala", "Groovy", "Kotlin",
                "Clojure", "Jython", "C#",
                "JavaScript", "SQL")
                .sorted()   // 天然序排序
                .forEach(s -> System.out.printf("%s", s));  // 打印输出
    }
}

输入后果:

C C# C++ Clojure Groovy Java JavaScript Jython Kotlin Python Ruby SQL Scala Shell 

由下面的例子,咱们能够得悉应用 Stream 的步骤:

  1. 创立流。例如下面的 Stream.of()
  2. 对流的两头操作。例如下面的 sorted(),在流中对数据进行排序。
  3. 最终操作。例如下面的 forEach(),将流通过该办法进行打印。

流创立

流的创立次要有以下几种形式:

以下几种形式能够创立 Stream 流:

  • Arrays 数组工具类提供的 stream() 静态方法。
  • Stream 类中 of()generate()iterate()empty() 静态方法。
  • Stream 类中的 builder() 办法。
  • Collection 汇合提供的 stream() 办法与 parallelStream() 办法。

还有其它的一些产生流的办法就不一一列举了。上面解说一下上述创立 Stream 流的办法

Arrays.stream() 静态方法

以下是 Arrays 对于产生流的办法 stream() 及重载办法:

public class Arrays {
    ... 省略...
    public static <T> Stream<T> stream(T[] array) {...}
    public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {...}
    public static IntStream stream(int[] array) {...}
    public static IntStream stream(int[] array, int startInclusive, int endExclusive) {...}
    public static LongStream stream(long[] array) {...}
    public static LongStream stream(long[] array, int startInclusive, int endExclusive) {...}
    public static DoubleStream stream(double[] array) {...}
    public static DoubleStream stream(double[] array, int startInclusive, int endExclusive) {...}
    ... 省略...
}

由以上办法可知,通过重载模式,能够满足咱们调用的根本类型与泛型的数组转化为 Stream 流。

上面是应用 Arrays.stream() 来将数组转换为流的例子:

public class ArraysStream {public static void main(String[] args) {Arrays.stream(new double[] {3.1415926, 9.8, 3.3333})
                .forEach(System.out::println);
    }
}

Stream 的静态方法

Stream 流中共有 of()generate()iterate() 三个静态方法。

咱们先看一下 Stream.of() 静态方法的源码:

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    ... 省略...
    @SafeVarargs
    @SuppressWarnings("varargs") // Creating a stream from an array is safe
    public static<T> Stream<T> of(T... values) {return Arrays.stream(values);
    }
    ... 省略...
}

由以上源码可知,应用 Stream.of() 静态方法,是将可变元素传递到 Arrays 数组工具类的静态方法 stream() 去转换为流,其实质还是调用 Arrays.stream() 办法。

上面咱们来看一下,应用 Stream.of() 来将可变参数数组转化为流的例子:

public class StreamOf {public static void main(String[] args) {
        // 将一组数字转换为流
        Stream.of(9.8, 3.1415926, 3.33333)
                .forEach(System.out::println);
    }
}

Stream.of() 静态方法的源码和例子如上所述。·

Stream 还有产生有限元素的静态方法用于产生流,它就是generate() 静态方法:

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    ... 省略...
    public static<T> Stream<T> generate(Supplier<? extends T> s) {...}
    ... 省略...
}

应用 generate() 办法创立的流,长度是有限的,它的参数是 Supplier 函数接口。

public class StreamGenerate {public static void main(String[] args) {Stream.generate(Math::random)
                .limit(10)
                .forEach(i -> System.out.printf("%f", i));
    }
}

输入后果:

0.792192 0.625073 0.983115 0.372405 0.294238 0.790635 0.688823 0.238186 0.096708 0.434963 

咱们在看另一个创立有限流的静态方法 Stream.iterate()

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    ... 省略...
    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {...}
    
    public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next) {}
    ... 省略...
}

上述两个静态方法都是 iterate 用来创立有限流的,与 generate 办法不同的是,它是通过函数 f 迭代给指定的种子而产生有限间断有序的Stream

public class StreamIterate {
    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 StreamIterate().numbers()
                .skip(20)           // 过滤前 20 个
                .limit(10)          // 而后取 10 个
                .forEach(i -> System.out.printf("%d", i));
    }
}

输入后果:

6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 

Stream 类中还有一个创立不含任何元素的 Stream 流:

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    ... 省略...
    public static<T> Stream<T> empty() {...}
    ... 省略...
}

创立空流的例子如下:

public class StreamEmpty {public static void main(String[] args) {Stream<Object> empty = Stream.empty();
    }
}

Stream 的结构器模式

Stream 类还能够应用构建器 builder() 办法创立流。

public class StreamBuilder {public static void main(String[] args) {Stream.builder()
                .add("Jan").add("Feb").add("Mar")
                .add("Apr").add("May").add("Jun")
                .build()
                .forEach(s -> System.out.printf("%s", s));
    }
}

输入后果:

Jan Feb Mar Apr May Jun 

Collection 系列

Java 8 中,扩大了 Collection 接口,增加了两个默认办法,来创立流:

public interface Collection<E> extends Iterable<E> {
    ... 省略...
    default Stream<E> stream() {...}
    default Stream<E> parallelStream() {...}
}

下面的两个默认办法,stream() 是转化为程序流,paralletlStream() 转换为并行流。

上面咱们看一下两个默认办法的应用例子:

public class ListStream {public static void main(String[] args) {String[] months = {"Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jun", "Jun", "Feb", "Jan"};
        // 汇合创立
        List<String> list = Arrays.asList(months);
        list.stream().forEach(s -> System.out.format("%s", s));
    }
}

输入后果:

Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec 

但将 List 对象的 stream() 办法变换为 parallelStream() 办法后,

输入后果:

Aug Jul Jun Feb Dec Oct Nov Apr Mar May Jan Sep

从下面咱们能够看出,stream()parallelStream() 的区别:

stream() 是转化为程序流,它应用主线程,是单线程的;parallelStream() 是转化为并行流,它是多个线程同时运行的。因而,stream() 是按程序输入的,而parallelStream() 不是。

那咱们如何将 Map 关系映射表转化为流呢?

看上面的例子:

public class MapStream {public static void main(String[] args) {Map<String, Integer> users = new HashMap<>();
        users.put("小赵", 18);
        users.put("小钱", 29);
        users.put("小孙", 20);
        users.put("小李", 29);

        users.entrySet().stream().forEach(entry -> System.out.format("%s", entry));
        System.out.println();
        users.keySet().stream().forEach(key -> System.out.format("%s", key));
        System.out.println();
        users.values().stream().forEach(value -> System.out.format("%d", value));
    }
}

输入后果:

小孙 =20 小李 =29 小钱 =29 小赵 =18 
小孙 小李 小钱 小赵 
20 29 29 18 

从下面的例子就可得悉,咱们失去 MapEntity 汇合,Key 的汇合和 Value 的汇合,在将它们转化为流去解决。

两头操作

两头操作就是对上述创立的流进行解决,解决完返回的还是流,以便于其它操作。

咱们来看一下都有什么两头操作:peek()sorted()unordered()distinct()filter()map()flatMap()limit()skip() 等。上面咱们来具体介绍这些操作。

peek

peek 操作的办法如下:

Stream<T> peek(Consumer<? super T> action);

次要目标是接管一个 Consumer 函数提供的逻辑去对流中的元素进行操作。

peek() 操作的目标是帮忙调试。它容许你无批改地查看流中的元素。代码示例:

public class Peeking {public static void main(String[] args) {Stream.of("Hello", "Hello World!", "I'm hello")
                .peek(System.out::println)
                .collect(Collectors.toList());
    }
}

输入后果:

Hello
Hello World!
I'm hello

再看下一个例子:

public class Peeking {public static void main(String[] args) {List<String> list  = Stream.of("Hello", "Hello World!", "I'm hello")
                .map(String::toUpperCase)
                .collect(Collectors.toList());
        System.out.println(list);

        list = Stream.of("Hello", "Hello World!", "I'm hello")
                .peek(String::toUpperCase)
                .collect(Collectors.toList());
        System.out.println(list);
    }
}

输入后果:

[HELLO, HELLO WORLD!, I'M HELLO]
[Hello, Hello World!, I'm hello]

能够看出应用map() 操作对元素进行了转换,而应用 peek() 并没有对元素进行转换。

因而,peek() 个别用于不想扭转流中元素自身的类型或者只想操作元素的外部状态时;而 map() 则用于扭转流中元素自身类型,即从元素中派生出另一种类型的操作。

peek() 实用于对 Stream<T> 中 泛型的某些属性进行批处理的时候应用。

sorted

sorted 操作的办法如下:

Stream<T> sorted();
// 或者
Stream<T> sorted(Comparator<? super T> comparator);

sorted() 能够应用无参办法,也能够传入 Comparator 参数。代码示例:

public class SortedComparator {public static void main(String[] args) throws Exception {FileToWords.stream("Cheese.dat")
                .skip(10)
                .limit(10)
                .sorted(Comparator.reverseOrder())
                .map(w -> w + " ")
                .forEach(System.out::print);
    }
}

输入后果:

you what to the that sir leads in district And

sorted() 预设了一些默认的比拟器。这里咱们应用的是反转 “ 天然排序 ”。当然你也能够把 Lambda 函数作为参数传递给 sorted()

distinct

distinct 的办法如下:

Stream<T> distinct();

它的作用次要是去重:

public class DistinctOperator {public static void main(String[] args) {Stream.of(45, 31, 21, 98, 31, 55, 982, 45, 54)
                .distinct().forEach(n -> System.out.format("%d", n));
    }
}

输入后果:

45 31 21 98 55 982 54 

filter

filter 的办法如下:

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

该办法是过滤操作,把不想要的数据过滤掉,留下想要的的数据。会依据传入的 Predicate 函数,将不合乎的数据过滤掉,留下合乎 Predicate 函数的数据。

咱们看上面的例子来相熟一下 filter() 办法的应用:

class Month {
    private String name;
    private int id;
    public Month(String name, int id) {this.name = name;this.id = id;}
    public String getName() { return name;}
    public void setName(String name) {this.name = name;}
    public int getId() { return id;}
    public void setId(int id) {this.id = id;}
    @Override
    public String toString() {return "Month{" + "name='" + name + '\'' + ", id=" + id + '}';
    }
}
public class FilterOperator {public static void main(String[] args) {String[] strings = new String[]{"Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        Month[] months = new Month[strings.length];
        for (int i = 0; i < strings.length; i++) {months[i] = new Month(strings[i], i+1);
        }
        Stream.of(months)
                .filter(s -> "J".equals(s.getName().substring(0, 1)))
                .forEach(System.out::println);
    }
}

输入后果:

Month{name='Jan', id=1}
Month{name='Jun', id=6}
Month{name='Jul', id=7}

从后果中能够看到,但凡首字母与 J 相等的对象都被打印进去了。

map

map 的办法如下:

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

它接管一个 Function 函数,作用是将一个类型的对象转换为另一个类型的对象。

上面咱们编写一个例子来看一下它的操作形式:

class Student {
    private String name;
    private int age;
    public Student(String name, int age) {this.name = name;this.age = age;}
    public String getName() { return name;}
    public void setName(String name) {this.name = name;}
    public int getAge() { return age;}
    public void setAge(int age) {this.age = age;}
    @Override
    public String toString() {return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}
public class MapOperator {public static void main(String[] args) {String[] strings = new String[]{"小赵", "小钱", "小孙",
                "小李", "小周", "小吴", "小郑", "小王"};
        int[] ints = new int[] {20 , 18, 31, 28, 20, 25, 31, 20};
        Student[] students = new Student[strings.length];
        for (int i = 0; i < strings.length; i++) {students[i] = new Student(strings[i], ints[i]);
        }
        Stream.of(students).map(Student::getName).forEach(s -> System.out.printf("%s", s));
        System.out.println();
        Stream.of(students).mapToInt(Student::getAge).forEach(i -> System.out.printf("%d", i));
        System.out.println();}
}

输入后果:

小赵 小钱 小孙 小李 小周 小吴 小郑 小王 
20 18 31 28 20 25 31 20 

从下面的例子可知,第一个输入是从 Student 对象数组中将所有学生的姓名都获取进去并打印,第二个是打印年龄。

从第二个能够看出,源码曾经封装好了转换为根本类型的 mapToInt 办法,返回的后果为 IntStream,其它所有对于 map 操作的源码如下:

// 依据 `ToIntFunction` 函数,获取 `IntStream` 流
IntStream mapToInt(ToIntFunction<? super T> mapper);
// 依据 `ToLongFunction` 函数,获取 `LongStream` 流
LongStream mapToLong(ToLongFunction<? super T> mapper);
// 依据 `ToDoubleFunction` 函数,获取 `DoubleStream` 流
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

flatMap

flatMap 的办法如下:

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

该办法接管一个 Function 函数作为参数,将流中的每个值都转换成另一个流,而后把所有流连接成一个流,是扁平化操作。

例子:

class Employee {
    private int id;
    private String name;
    private double salary;
    public Employee(int id, String name, double salary) {this.id = id;this.name = name;this.salary = salary;}
    public int getId() { return id;}
    public void setId(int id) {this.id = id;}
    public String getName() { return name;}
    public void setName(String name) {this.name = name;}
    public double getSalary() { return salary;}
    public void setSalary(double salary) {this.salary = salary;}
    @Override
    public String toString() {return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", salary=" + salary + '}';
    }
}
public class FlatMapOperator {public static void main(String[] args) {Employee[] employees1 = new Employee[] {new Employee(1, "小赵", 1500),
                new Employee(2, "小钱", 3500),
                new Employee(3, "小孙", 500),
                new Employee(4, "小李", 4500),
        };
        Employee[] employees2 = new Employee[] {new Employee(5, "小周", 9000),
                new Employee(6, "小吴", 6200),
                new Employee(7, "小郑", 1850),
                new Employee(8, "小王", 3210),
        };
        Stream.of(employees1, employees2).flatMap(e -> Arrays.stream(e)).forEach(System.out::println);
    }
}

输入后果:

Employee{id=1, name='小赵', salary=1500.0}
Employee{id=2, name='小钱', salary=3500.0}
Employee{id=3, name='小孙', salary=500.0}
Employee{id=4, name='小李', salary=4500.0}
Employee{id=5, name='小周', salary=9000.0}
Employee{id=6, name='小吴', salary=6200.0}
Employee{id=7, name='小郑', salary=1850.0}
Employee{id=8, name='小王', salary=3210.0}

从后果上看,flatMap() 将两个数组转换成流并合并在一起。

flatMap() 也有封装好的具体类型的办法,源码如下:

IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);

limit

limit 的办法如下:

Stream<T> limit(long maxSize);

它的作用是依据 maxSize 参数的大小,截取流,使其元素个数不超过 maxSize

咱们给一个例子来相熟 limit() 的应用:

public class LimitOperator {public static void main(String[] args) {String[] strings = new String[]{"Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        Stream.of(strings).limit(5).forEach(s -> System.out.printf("%s", s));
    }
}

输入后果:

Jan Feb Mar Apr May 

skip

skip 的办法如下:

Stream<T> skip(long n);

跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素有余 n 个,则返回一个空流。与 limit 互补。

public class SkipOperator {public static void main(String[] args) {String[] strings = new String[]{"Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        Stream.of(strings).skip(10).forEach(s -> System.out.printf("%s", s));
    }
}

输入后果:

Nov Dec

终端操作

终端操作就是为了完结流的两头操作,并返回后果。至此无奈再持续往后传递流。

终端操作都有哪些操作呢?咱们简略来看下:forEach()forEachOrdered()toArray()collect()reduce()min()max()count()anyMatch()allMatch()noneMatch()findFirst()findAny()等。上面具体介绍一下这些终端操作的应用。

forEach

forEach 的办法如下:

void forEach(Consumer<? super T> action);

次要是进行编译操作,咱们传递进去一个 Consumer 函数,Stream 在外部进行迭代。

public class ForEachOperator {public static void main(String[] args) {String[] strings = new String[]{"Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        Stream.of(strings).forEach(s -> System.out.printf("%s", s));
    }
}

forEachOrdered

forEachOrdered 的办法如下:

void forEachOrdered(Consumer<? super T> action);

它的作用与 forEach() 一样,然而在并行流上的区别较大

public class ForEachOrderedOperator {public static void main(String[] args) {String[] strings = new String[]{"Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        Stream.of(strings).parallel().forEach(s -> System.out.printf("%s", s));
        System.out.println();
        Stream.of(strings).parallel().forEachOrdered(s -> System.out.printf("%s", s));
    }
}

输入后果:

Aug May Dec Oct Jun Feb Apr Nov Mar Jan Jul Sep 
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec 

从输入后果可知,应用并行流进行操作时,forEachOrdered() 会严格依照程序进行输入,而forEach() 不会。

toArray

toArray 的办法如下:

Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);

toArray 有两个办法,一个无参,一个传入

咱们给出一个例子,来相熟应用 toArray() 办法:

public class ToArrayOperator {public static void main(String[] args) {
        List<String> strings = Arrays.asList("Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");

        // 应用 toArray() 将流转化为数组
        Object[] objects = strings.stream()
                .filter(s -> "J".equals(s.substring(0, 1))).toArray();
        System.out.println(Arrays.toString(objects));

        // 应用 toArray() 将流转化为特定的数组
        String[] strings1 = strings.stream()
                .filter(s -> "A".equals(s.substring(0, 1))).toArray(size -> {return new String[size];
                });
        System.out.println(Arrays.toString(strings1));

        // 下面的写法能够应用办法援用
        String[] strings3 = strings.stream()
                .filter(s -> "M".equals(s.substring(0, 1))).toArray(String[]::new);
        System.out.println(Arrays.toString(strings3));
    }
}

输入后果:

[Jan, Jun, Jul]
[Apr, Aug]
[Mar, May]

从后果上看,咱们晓得 toArray() 办法就是将流转换为数组。

collect

collect 的办法如下:

<R, A> R collect(Collector<? super T, A, R> collector);

<R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);

collect 是收集操作。通过流将其转换为其它罕用的数据结构并收集,比方转换成 ListMap 等操作。

从上述源码中,看出 collect 办法一共有两个办法。

第一个办法从接管的参数能够看出,collect 办法是通过 java.util.stream.Collector 类来收集流元素到后果集中的。

public class CollectOperator {public static void main(String[] args) {
        List<String> months1 = Stream.of("Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jun", "Jun", "Feb", "Jan")
                .filter(s -> "J".equals(s.substring(0, 1)))
                .collect(Collectors.toList());
        System.out.println(months1);
    }
}

输入后果:

[Jan, Jun, Jul, Jun, Jun, Jan]

从后果中看到,咱们通过 filter 过滤掉首字母不是 J 的字符串,并通过 collect 转换为 List 收集起来,然而收集的汇合中有反复的元素,因而,咱们将其转换成 Set 在收集。

public class CollectOperator {public static void main(String[] args) {
        Set<String> months = Stream.of("Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jun", "Jun", "Feb", "Jan")
                .filter(s -> "J".equals(s.substring(0, 1)))
                .collect(Collectors.toSet());
        System.out.println(months);
    }
}

输入后果:

[Jul, Jun, Jan]

输入的后果不是有序的,为了保障元素的有序,咱们将元素存储在 TreeSet 中。Collectors 中没有 toTreeSet(),因而咱们通过 Collectors.toCollection(Supplier<C> collectionFactory) 来构建咱们须要的汇合类型。

Stream.of("Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 
                "Jun", "Jun", "Feb", "Jan")
                .filter(s -> "J".equals(s.substring(0, 1)))
                .collect(Collectors.toCollection(TreeSet::new));

输入后果:

[Jan, Jul, Jun]

collect 也能够生成 Map。代码如下:

class Pair {
    private final Character c;
    private final Integer i;
    Pair(Character c) {this.c = c;this.i = (int) c; }
    public Character getC() { return c;}
    public Integer getI() { return i;}
    @Override
    public String toString() { return "Pair{" + "c=" + c + ", i=" + i + '}'; }
}
public class CollectOperator {public static void main(String[] args) {Map<Character, Integer> charTables = Stream.of('c', 'a', 'z', 'o', 'y')
                .map(c -> new Pair(c)).collect(Collectors.toMap(Pair::getC, Pair::getI));
        System.out.println(charTables.toString());
    }
}

输入后果:

{a=97, c=99, y=121, z=122, o=111}

对于第二个办法,参数 Supplier 是一个生成指标类型实例的办法,代表着存储类型的对象;BiConsumer<R, ? super T> accumulator 参数是将操作指标数据填充到 Supplier 生成的指标类型的实例中去的办法,代表着如何将元素增加到容器中;而 BiConsumer<R, R> combiner 是将多个 Supplier 生成的实例整合到一起的办法,代表着规约操作,将多个后果合并。

上面给出一个简略的例子,统计月份呈现的频次:

public class CollectOperator {public static void main(String[] args) {
        Map<String, Integer> wordCount = Stream.of("Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jun", "Jun", "Feb", "Jan")
                .collect(TreeMap::new,
                        (map, word) ->
                                map.merge(word, 1, Integer::sum),
                        Map::putAll);
        System.out.println(wordCount);
    }
}

输入后果:

{Apr=1, Aug=1, Dec=1, Feb=2, Jan=2, Jul=1, Jun=3, Mar=1, May=1, Nov=1, Oct=1, Sep=1}

reduce

reduce 的办法如下:

T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);

reduce 是通过重复的对一个输出序列的元素进行某种组合操作(求和、最大值或将所有元素都放入一个列表),最终将其组合为一个繁多的概要信息。

public class ReduceOperator {public static void main(String[] args) {
         String concat = Stream.of("Jan", "Feb", "Mar", "Apr", "May",
                 "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
                 .filter(s -> "J".equals(s.substring(0, 1)))
                 .reduce("", (a, b) -> {if (!"".equals(a)) {return a + "," + b;}
                     return b;
                 });
        System.out.println(concat);
    }
}

输入后果:

Jan,Jun,Jul

Stream中对于 minmaxcount 的源码如下:

Optional<T> min(Comparator<? super T> comparator);
Optional<T> max(Comparator<? super T> comparator);
long count();

count 的作用是求流中元素个数。

min 的作用是依据传入的 Comparator 来求 “ 最小 ” 的元素。

max 的作用是依据传入的 Comparator 来求 “ 最小 ” 的元素。

它们的底层都是依赖 reduce 实现的。

当初看一下例子代码:

public class InfoReduceOperator {public static void main(String[] args) {String[] months = {"Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        long count = Stream.of(months)
                .filter(s -> "J".equals(s.substring(0, 1)))
                .count();
        System.out.printf("计算首字母是 `J` 的字符串的个数是:%d 个",count);
        System.out.println();
        String min = Stream.of(months).min(Comparator.comparing(Function.identity())).get();
        System.out.printf("字符串数组中最小的字符串是:%s", min);
        System.out.println();
        String max = Stream.of(months).max(String::compareTo).get();
        System.out.printf("字符串数组中最大的字符串是:%s", max);
    }
}

输入后果:

计算首字母是 `J` 的字符串的个数是:3 个
字符串数组中最小的字符串是:Apr
字符串数组中最大的字符串是:Sep

match

match 是匹配操作,它总共有三类办法:anyMatchallMatchnoneMatch

anyMatch 用于查看所有元素中至多有一个是匹配的。它的办法如下:

boolean anyMatch(Predicate<? super T> predicate);

allMatch 用于查看所有元素是否都匹配。办法如下:

boolean allMatch(Predicate<? super T> predicate);

noneMatch 用于查看所有元素都没有匹配的。办法如下:

boolean noneMatch(Predicate<? super T> predicate);

上面看一下对于下面办法的应用例子:

public class MatchOperator {public static void main(String[] args) {String[] months = {"Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        boolean isAnyMatch = Stream.of(months).anyMatch(s -> "A".equals(s.substring(0, 1)));
        System.out.printf("判断 months 数组中是否存在首字母为 `A`:%s",isAnyMatch);
        System.out.println();
        boolean isAllMath = Stream.of(months).allMatch(s -> "A".equals(s.substring(0, 1)));
        System.out.printf("判断 months 数组中是否所有元素的首字母都为 `A`:%s", isAllMath);
        System.out.println();
        boolean isNoneMath = Stream.of(months).noneMatch(s -> "A".equals(s.substring(0, 1)));
        System.out.printf("判断 months 数组中的首字母都不为 `A`:%s", isNoneMath);
    }
}

输入后果:

判断 months 数组中是否存在首字母为 `A`:true
判断 months 数组中是否所有元素的首字母都为 `A`:false
判断 months 数组中的首字母都不为 `A`:false

find

find 是查找操作,它总共有三类办法:findFirstfindAny

findFirst 的作用是返回以后流中的第一个元素。办法如下:

Optional<T> findFirst();

findAny 的作用是返回以后流中的任意元素。办法如下:

Optional<T> findAny();

咱们来一下它们的应用办法:

public class FindOperator {public static void main(String[] args) {String[] months = {"Jan", "Feb", "Mar", "Apr", "May",
                "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        String first = Stream.of(months).parallel()
                .filter(s -> "J".equals(s.substring(0, 1)))
                .findFirst().get();
        System.out.printf("获取流中第一个元素:%s", first);
        System.out.println();
        String any = Stream.of(months).parallel()
                .filter(s -> "M".equals(s.substring(0, 1)))
                .findAny().get();
        System.out.printf("获取流中任意一个元素:%s", any);
    }
}

输入后果:

获取流中第一个元素:Jan
获取流中任意一个元素:Mar

findFirst() 无论流是否是并行化,总是抉择流中的第一个元素。

对于非并行流,findAny() 会抉择流中的第一个元素。但当咱们应用 parallel() 来并行化后,findAny()才实现了抉择任意元素。

如果必须抉择流中最初一个元素,那就应用 reduce()

String last = Stream.of(months).parallel()
                .filter(s -> "J".equals(s.substring(0, 1)))
                .reduce((v1, v2) -> v2).get();

reduce() 的参数只是用最初一个元素替换了最初两个元素,最终只生成最初一个元素。

小结

应用 Stream 流对于 Java 是一个极大的晋升,应用它编写代码看起来较为简洁优雅,并且 Stream 流的并行化极大的施展了当初多核时代的劣势。

更多内容请关注公众号「海人的博客」,回复「资源」即可取得收费学习资源!

正文完
 0