关于java:5万字长文Stream和Lambda表达式最佳实践附PDF下载

8次阅读

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

1. Streams 简介

明天要讲的 Stream 指的是 java.util.stream 包中的诸多类。Stream 能够不便的将之前的联合类以转换为 Stream 并以流式形式进行解决,大大的简化了咱们的编程,Stream 包中,最外围的就是 interface Stream<T>

从下面的图中咱们能够看到 Stream 继承自 BaseStream。Stream 中定义了很多十分实用的办法,比方 filter,map,flatmap,forEach,reduce,collect 等等。接下来咱们将会逐个解说。

1.1 创立 Stream

Stream 的创立有很多形式,java 引入 Stream 之后所有的汇合类都增加了一个 stream() 办法,通过这个办法能够间接失去其对应的 Stream。也能够通过 Stream.of 办法来创立:

//Stream Creation
        String[] arr = new String[]{"a", "b", "c"};
        Stream<String> stream = Arrays.stream(arr);
        stream = Stream.of("a", "b", "c");

1.2 Streams 多线程

如果咱们想应用多线程来解决汇合类的数据,Stream 提供了十分不便的多线程办法 parallelStream():

//Multi-threading
        List<String> list =new ArrayList();
        list.add("aaa");
        list.add("bbb");
        list.add("abc");
        list.add("ccc");
        list.add("ddd");
        list.parallelStream().forEach(element -> doPrint(element));

1.3 Stream 的基本操作

Stream 的操作能够分为两类,一类是两头操作,两头操作返回 Stream<T>,因而能够级联调用。另一类是终止操作,这类操作会返回 Stream 定义的类型。

//Operations
        long count = list.stream().distinct().count();

下面的例子中,distinct() 返回一个 Stream,所以能够级联操作,最初的 count() 是一个终止操作,返回最初的值。

Matching

Stream 提供了 anyMatch(), allMatch(), noneMatch() 这三种 match 形式,咱们看下怎么应用:

//Matching
        boolean isValid = list.stream().anyMatch(element -> element.contains("h"));
        boolean isValidOne = list.stream().allMatch(element -> element.contains("h"));
        boolean isValidTwo = list.stream().noneMatch(element -> element.contains("h"));  

Filtering

filter() 办法容许咱们对 Stream 中的数据进行过滤,从而失去咱们须要的:

Stream<String> filterStream = list.stream().filter(element -> element.contains("d"));

下面的例子中咱们从 list 中选出了蕴含“d”字母的 String。

Mapping

map 就是对 Stream 中的值进行再加工,而后将加工过后的值作为新的 Stream 返回。

//Mapping
        Stream<String> mappingStream = list.stream().map(element -> convertElement(element));

    private static String convertElement(String element) {return "element"+"abc";}

上的例子中咱们把 list 中的每个值都加上了“abc”而后返回一个新的 Stream。

FlatMap

flatMap 和 Map 很相似,然而他们两个又有不同,看名字咱们能够看到 flatMap 意思是打平之后再做 Map。

怎么了解呢?

如果咱们有一个 CustBook 类:

@Data
public class CustBook {List<String> bookName;}

CustBook 定义了一个 bookName 字段。

先看一下 Map 返回的后果:

List<CustBook> users = new ArrayList<>();
        users.add(new CustBook());
Stream<Stream<String>> userStreamMap
                = users.stream().map(user -> user.getBookName().stream());

在下面的代码中,map 将每一个 user 都转换成了 stream,所以最初的后果是返回 Stream 的 Stream。

如果咱们只想返回 String,则能够应用 FlatMap:

List<CustBook> users = new ArrayList<>();
        users.add(new CustBook());
        Stream<String> userStream
                = users.stream().map(user -> user.getBookName().stream());

简略点讲 FlatMap 就是将层级关系铺平重来。

Reduction

应用 reduce() 办法能够不便的对汇合的数据进行运算,reduce() 接管两个参数,第一个是开始值,前面是一个函数示意累计。

//Reduction
        List<Integer> integers = Arrays.asList(1, 1, 1);
        Integer reduced = integers.stream().reduce(100, (a, b) -> a + b);

下面的例子咱们定义了 3 个 1 的 list,而后调用 reduce(100, (a, b) -> a + b) 办法,最初的后果是 103.

Collecting

collect() 办法能够不便的将 Stream 再次转换为汇合类,不便解决和展现:

List<String> resultList
                = list.stream().map(element -> element.toUpperCase()).collect(Collectors.toList());

2. functional interface 的分类和应用

java 8 引入了 lambda 表达式,lambda 表达式实际上示意的就是一个匿名的 function。

在 java 8 之前,如果须要应用到匿名 function 须要 new 一个类的实现,然而有了 lambda 表达式之后,所有都变的十分简介。

咱们看一个之前讲线程池的时候的一个例子:

//ExecutorService using class
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.submit(new Runnable() {
            @Override
            public void run() {log.info("new runnable");
            }
        });

executorService.submit 须要接管一个 Runnable 类,下面的例子中咱们 new 了一个 Runnable 类,并实现了它的 run()办法。

下面的例子如果用 lambda 表达式来重写,则如下所示:

//ExecutorService using lambda
        executorService.submit(()->log.info("new runnable"));

看起是不是很简略,应用 lambda 表达式就能够省略匿名类的结构,并且可读性更强。

那么是不是所有的匿名类都能够用 lambda 表达式来重构呢?也不是。

咱们看下 Runnable 类有什么特点:

@FunctionalInterface
public interface Runnable 

Runnable 类下面有一个 @FunctionalInterface 注解。这个注解就是咱们明天要讲到的 Functional Interface。

2.1 Functional Interface

Functional Interface 是指带有 @FunctionalInterface 注解的 interface。它的特点是其中只有一个子类必须要实现的 abstract 办法。如果 abstract 办法后面带有 default 关键字,则不做计算。

其实这个也很好了解,因为 Functional Interface 改写成为 lambda 表达式之后,并没有指定实现的哪个办法,如果有多个办法须要实现的话,就会有问题。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

Functional Interface 个别都在 java.util.function 包中。

依据要实现的办法参数和返回值的不同,Functional Interface 能够分为很多种,上面咱们别离来介绍。

2.2 Function:一个参数一个返回值

Function 接口定义了一个办法,接管一个参数,返回一个参数。

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

个别咱们在对汇合类进行解决的时候,会用到 Function。

Map<String, Integer> nameMap = new HashMap<>();
        Integer value = nameMap.computeIfAbsent("name", s -> s.length());

下面的例子中咱们调用了 map 的 computeIfAbsent 办法,传入一个 Function。

下面的例子还能够改写成更短的:

Integer value1 = nameMap.computeIfAbsent("name", String::length);

Function 没有指明参数和返回值的类型,如果须要传入特定的参数,则能够应用 IntFunction, LongFunction, DoubleFunction:

@FunctionalInterface
public interface IntFunction<R> {

    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    R apply(int value);
}

如果须要返回特定的参数,则能够应用 ToIntFunction, ToLongFunction, ToDoubleFunction:

@FunctionalInterface
public interface ToDoubleFunction<T> {

    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    double applyAsDouble(T value);
}

如果要同时指定参数和返回值,则能够应用 DoubleToIntFunction, DoubleToLongFunction, IntToDoubleFunction, IntToLongFunction, LongToIntFunction, LongToDoubleFunction:

@FunctionalInterface
public interface LongToIntFunction {

    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    int applyAsInt(long value);
}

2.3 BiFunction:接管两个参数,一个返回值

如果须要承受两个参数,一个返回值的话,能够应用 BiFunction:BiFunction, ToDoubleBiFunction, ToIntBiFunction, ToLongBiFunction 等。

@FunctionalInterface
public interface BiFunction<T, U, R> {

    /**
     * Applies this function to the given arguments.
     *
     * @param t the first function argument
     * @param u the second function argument
     * @return the function result
     */
    R apply(T t, U u);

咱们看一个 BiFunction 的例子:

//BiFunction
        Map<String, Integer> salaries = new HashMap<>();
        salaries.put("alice", 100);
        salaries.put("jack", 200);
        salaries.put("mark", 300);

        salaries.replaceAll((name, oldValue) ->
                name.equals("alice") ? oldValue : oldValue + 200);

2.4 Supplier:无参的 Function

如果什么参数都不须要,则能够应用 Supplier:

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();}

2.5 Consumer:接管一个参数,不返回值

Consumer 接管一个参数,然而不返回任何值,咱们看下 Consumer 的定义:

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

看一个 Consumer 的具体利用:

//Consumer
        nameMap.forEach((name, age) -> System.out.println(name + "is" + age + "years old"));

2.6 Predicate:接管一个参数,返回 boolean

Predicate 接管一个参数,返回 boolean 值:

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);

如果用在汇合类的过滤下面那是极好的:

//Predicate
        List<String> names = Arrays.asList("A", "B", "C", "D", "E");
        List<String> namesWithA = names.stream()
                .filter(name -> name.startsWith("A"))
                .collect(Collectors.toList());

2.7 Operator:接管和返回同样的类型

Operator 接管和返回同样的类型,有很多种 Operator:UnaryOperator BinaryOperator,DoubleUnaryOperator, IntUnaryOperator, LongUnaryOperator, DoubleBinaryOperator, IntBinaryOperator, LongBinaryOperator 等。

@FunctionalInterface
public interface IntUnaryOperator {

    /**
     * Applies this operator to the given operand.
     *
     * @param operand the operand
     * @return the operator result
     */
    int applyAsInt(int operand);

咱们看一个 BinaryOperator 的例子:

 //Operator
        List<Integer> values = Arrays.asList(1, 2, 3, 4, 5);
        int sum = values.stream()
                .reduce(0, (i1, i2) -> i1 + i2);

3. Lambda 表达式最佳实际

Lambda 表达式 java 8 引入的函数式编程框架。之前的文章中咱们也讲过 Lambda 表达式的根本用法。

本文将会在之前的文章根底上更加具体的解说 Lambda 表达式在理论利用中的最佳实践经验。

3.1 优先应用规范 Functional 接口

之前的文章咱们讲到了,java 在 java.util.function 包中定义了很多 Function 接口。基本上涵盖了咱们可能想到的各种类型。

如果咱们自定义了上面的 Functional interface:

@FunctionalInterface
public interface Usage {String method(String string);
}

而后咱们须要在一个 test 办法中传入该 interface:

public String test(String string, Usage usage) {return usage.method(string);
}

下面咱们定义的函数接口须要实现 method 办法,接管一个 String,返回一个 String。这样咱们齐全能够应用 Function 来代替:

public String test(String string, Function<String, String> fn) {return fn.apply(string);
}

应用标准接口的益处就是,不要反复造轮子。

3.2 应用 @FunctionalInterface 注解

尽管 @FunctionalInterface 不是必须的,不应用 @FunctionalInterface 也能够定义一个 Functional Interface。

然而应用 @FunctionalInterface 能够在违反 Functional Interface 定义的时候报警。

如果是在保护一个大型项目中,加上 @FunctionalInterface 注解能够分明的让其他人理解这个类的作用。

从而使代码更加标准和更加可用。

所以咱们须要这样定义:

@FunctionalInterface
public interface Usage {String method(String string);
}

而不是:

public interface Usage {String method(String string);
}

3.3 在 Functional Interfaces 中不要滥用 Default Methods

Functional Interface 是指只有一个未实现的形象办法的接口。

如果该 Interface 中有多个办法,则能够应用 default 关键字为其提供一个默认的实现。

然而咱们晓得 Interface 是能够多继承的,一个 class 能够实现多个 Interface。如果多个 Interface 中定义了雷同的 default 办法,则会报错。

通常来说 default 关键字个别用在降级我的项目中,防止代码报错。

3.4 应用 Lambda 表达式来实例化 Functional Interface

还是下面的例子:

@FunctionalInterface
public interface Usage {String method(String string);
}

要实例化 Usage,咱们能够应用 new 关键词:

Usage usage = new Usage() {
    @Override
    public String method(String string) {return string;}
};

然而最好的方法就是用 lambda 表达式:

Usage usage = parameter -> parameter;

3.5 不要重写 Functional Interface 作为参数的办法

怎么了解呢?咱们看上面两个办法:

public class ProcessorImpl implements Processor {
    @Override
    public String process(Callable<String> c) throws Exception {// implementation details}
 
    @Override
    public String process(Supplier<String> s) {// implementation details}
}

两个办法的办法名是一样的,只有传入的参数不同。然而两个参数都是 Functional Interface,都能够用同样的 lambda 表达式来示意。

在调用的时候:

String result = processor.process(() -> "test");

因为区别不了到底调用的哪个办法,则会报错。

最好的方法就是将两个办法的名字批改为不同的。

3.6 Lambda 表达式和外部类是不同的

尽管咱们之前讲到应用 lambda 表达式能够替换外部类。然而两者的作用域范畴是不同的。

在内部类中,会创立一个新的作用域范畴,在这个作用域范畴之内,你能够定义新的变量,并且能够用 this 援用它。

然而在 Lambda 表达式中,并没有定义新的作用域范畴,如果在 Lambda 表达式中应用 this,则指向的是外部类。

咱们举个例子:

private String value = "Outer scope value";

public String scopeExperiment() {Usage usage = new Usage() {
        String value = "Inner class value";
 
        @Override
        public String method(String string) {return this.value;}
    };
    String result = usage.method("");
 
    Usage usageLambda = parameter -> {
        String value = "Lambda value";
        return this.value;
    };
    String resultLambda = usageLambda.method("");
 
    return "Results: result =" + result + 
      ", resultLambda =" + resultLambda;
}

下面的例子将会输入“Results: result = Inner class value, resultLambda = Outer scope value”

3.7 Lambda Expression 尽可能简洁

通常来说一行代码即可。如果你有十分多的逻辑,能够将这些逻辑封装成一个办法,在 lambda 表达式中调用该办法即可。

因为 lambda 表达式说到底还是一个表达式,表达式当然越短越好。

java 通过类型推断来判断传入的参数类型,所以咱们在 lambda 表达式的参数中尽量不传参数类型,像上面这样:

(a, b) -> a.toLowerCase() + b.toLowerCase();

而不是:

(String a, String b) -> a.toLowerCase() + b.toLowerCase();

如果只有一个参数的时候,不须要带括号:

a -> a.toLowerCase();

而不是:

(a) -> a.toLowerCase();

返回值不须要带 return:

a -> a.toLowerCase();

而不是:

a -> {return a.toLowerCase()};

3.8 应用办法援用

为了让 lambda 表达式更加简洁,在能够应用办法援用的时候,咱们能够应用办法援用:

a -> a.toLowerCase();

能够被替换为:

String::toLowerCase;

3.9 Effectively Final 变量

如果在 lambda 表达式中援用了 non-final 变量,则会报错。

effectively final 是什么意思呢?这个是一个近似 final 的意思。只有一个变量只被赋值一次,那么编译器将会把这个变量看作是 effectively final 的。

    String localVariable = "Local";
    Usage usage = parameter -> {
         localVariable = parameter;
        return localVariable;
    };

下面的例子中 localVariable 被赋值了两次,从而不是一个 Effectively Final 变量,会编译报错。

为什么要这样设置呢?因为 lambda 表达式通常会用在并行计算中,当有多个线程同时拜访变量的时候 Effectively Final 变量能够避免不能够意料的批改。

4. stream 表达式中实现 if/else 逻辑

在 Stream 解决中,咱们通常会遇到 if/else 的判断状况,对于这样的问题咱们怎么解决呢?

还记得咱们在上一篇文章 lambda 最佳实际中提到,lambda 表达式应该越简洁越好,不要在其中写臃肿的业务逻辑。

接下来咱们看一个具体的例子。

4.1 传统写法

如果咱们有一个 1 to 10 的 list,咱们想要别离挑选出奇数和偶数进去,传统的写法,咱们会这样应用:

    public void inForEach(){List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        ints.stream()
                .forEach(i -> {if (i.intValue() % 2 == 0) {System.out.println("i is even");
                    } else {System.out.println("i is old");
                    }
                });
    }

下面的例子中,咱们把 if/else 的逻辑放到了 forEach 中,尽管没有任何问题,然而代码显得十分臃肿。

接下来看看怎么对其进行改写。

4.2 应用 filter

咱们能够把 if/else 的逻辑改写为两个 filter:

List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        Stream<Integer> evenIntegers = ints.stream()
                .filter(i -> i.intValue() % 2 == 0);
        Stream<Integer> oddIntegers = ints.stream()
                .filter(i -> i.intValue() % 2 != 0);

有了这两个 filter,再在 filter 过后的 stream 中应用 for each:

        evenIntegers.forEach(i -> System.out.println("i is even"));
        oddIntegers.forEach(i -> System.out.println("i is old"));

怎么样,代码是不是十分简洁明了。

5. 在 map 中应用 stream

Map 是 java 中十分罕用的一个汇合类型,咱们通常也须要去遍历 Map 去获取某些值,java 8 引入了 Stream 的概念,那么咱们怎么在 Map 中应用 Stream 呢?

5.1 基本概念

Map 有 key,value 还有示意 key,value 整体的 Entry。

创立一个 Map:

Map<String, String> someMap = new HashMap<>();

获取 Map 的 entrySet:

Set<Map.Entry<String, String>> entries = someMap.entrySet();

获取 map 的 key:

Set<String> keySet = someMap.keySet();

获取 map 的 value:

Collection<String> values = someMap.values();

下面咱们能够看到有这样几个汇合:Map,Set,Collection。

除了 Map 没有 stream,其余两个都有 stream 办法:

Stream<Map.Entry<String, String>> entriesStream = entries.stream();
        Stream<String> valuesStream = values.stream();
        Stream<String> keysStream = keySet.stream();

咱们能够通过其余几个 stream 来遍历 map。

5.2 应用 Stream 获取 map 的 key

咱们先给 map 增加几个值:

someMap.put("jack","20");
someMap.put("bill","35");

下面咱们增加了 name 和 age 字段。

如果咱们想查找 age=20 的 key,则能够这样做:

Optional<String> optionalName = someMap.entrySet().stream()
                .filter(e -> "20".equals(e.getValue()))
                .map(Map.Entry::getKey)
                .findFirst();

        log.info(optionalName.get());

因为返回的是 Optional, 如果值不存在的状况下,咱们也能够解决:

optionalName = someMap.entrySet().stream()
                .filter(e -> "Non ages".equals(e.getValue()))
                .map(Map.Entry::getKey).findFirst();

        log.info("{}",optionalName.isPresent());

下面的例子咱们通过调用 isPresent 来判断 age 是否存在。

如果有多个值,咱们能够这样写:

someMap.put("alice","20");
        List<String> listnames = someMap.entrySet().stream()
                .filter(e -> e.getValue().equals("20"))
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());

        log.info("{}",listnames);

下面咱们调用了 collect(Collectors.toList()) 将值转成了 List。

5.3 应用 stream 获取 map 的 value

下面咱们获取的 map 的 key,同样的咱们也能够获取 map 的 value:

List<String> listAges = someMap.entrySet().stream()
                .filter(e -> e.getKey().equals("alice"))
                .map(Map.Entry::getValue)
                .collect(Collectors.toList());

        log.info("{}",listAges);

下面咱们匹配了 key 值是 alice 的 value。

6. Stream 中的操作类型和 peek 的应用

java 8 stream 作为流式操作有两种操作类型,两头操作和终止操作。这两种有什么区别呢?

咱们看一个 peek 的例子:

Stream<String> stream = Stream.of("one", "two", "three","four");
        stream.peek(System.out::println);

下面的例子中,咱们的本意是打印出 Stream 的值,但实际上没有任何输入。

为什么呢?

6.1 两头操作和终止操作

一个 java 8 的 stream 是由三局部组成的。数据源,零个或一个或多个两头操作,一个或零个终止操作。

两头操作是对数据的加工,留神,两头操作是 lazy 操作,并不会立马启动,须要期待终止操作才会执行。

终止操作是 stream 的启动操作,只有加上终止操作,stream 才会真正的开始执行。

所以,问题解决了,peek 是一个两头操作,所以下面的例子没有任何输入。

6.2 peek

咱们看下 peek 的文档阐明:peek 次要被用在 debug 用处。

咱们看下 debug 用处的应用:

Stream.of("one", "two", "three","four").filter(e -> e.length() > 3)
                .peek(e -> System.out.println("Filtered value:" + e))
                .map(String::toUpperCase)
                .peek(e -> System.out.println("Mapped value:" + e))
                .collect(Collectors.toList());

下面的例子输入:

Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR

下面的例子咱们输入了 stream 的两头值,不便咱们的调试。

为什么只作为 debug 应用呢?咱们再看一个例子:

Stream.of("one", "two", "three","four").peek(u -> u.toUpperCase())
                .forEach(System.out::println);

下面的例子咱们应用 peek 将 element 转换成为 upper case。而后输入:

one
two
three
four

能够看到 stream 中的元素并没有被转换成大写格局。

再看一个 map 的比照:

Stream.of("one", "two", "three","four").map(u -> u.toUpperCase())
                .forEach(System.out::println);

输入:

ONE
TWO
THREE
FOUR

能够看到 map 是真正的对元素进行了转换。

当然 peek 也有例外,如果咱们 Stream 外面是一个对象会怎么样?

    @Data
    @AllArgsConstructor
    static class User{private String name;}
        List<User> userList=Stream.of(new User("a"),new User("b"),new User("c")).peek(u->u.setName("kkk")).collect(Collectors.toList());
        log.info("{}",userList);

输入后果:

10:25:59.784 [main] INFO com.flydean.PeekUsage - [PeekUsage.User(name=kkk), PeekUsage.User(name=kkk), PeekUsage.User(name=kkk)]

咱们看到如果是对象的话,理论的后果会被扭转。

为什么 peek 和 map 有这样的区别呢?

咱们看下 peek 和 map 的定义:

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

peek 接管一个 Consumer,而 map 接管一个 Function。

Consumer 是没有返回值的,它只是对 Stream 中的元素进行某些操作,然而操作之后的数据并不返回到 Stream 中,所以 Stream 中的元素还是原来的元素。

而 Function 是有返回值的,这意味着对于 Stream 的元素的所有操作都会作为新的后果返回到 Stream 中。

这就是为什么 peek String 不会发生变化而 peek Object 会发送变动的起因。

7. lambda 表达式中的异样解决

java 8 中引入了 lambda 表达式,lambda 表达式能够让咱们的代码更加简介,业务逻辑更加清晰,然而在 lambda 表达式中应用的 Functional Interface 并没有很好的解决异样,因为 JDK 提供的这些 Functional Interface 通常都是没有抛出异样的,这意味着须要咱们本人手动来解决异样。

因为异样分为 Unchecked Exception 和 checked Exception, 咱们别离来探讨。

7.1 解决 Unchecked Exception

Unchecked exception 也叫做 RuntimeException,呈现 RuntimeException 通常是因为咱们的代码有问题。RuntimeException 是不须要被捕捉的。也就是说如果有 RuntimeException,没有捕捉也能够通过编译。

咱们看一个例子:

List<Integer> integers = Arrays.asList(1,2,3,4,5);
        integers.forEach(i -> System.out.println(1 / i));

这个例子是能够编译胜利的,然而下面有一个问题,如果 list 中有一个 0 的话,就会抛出 ArithmeticException。

尽管这个是一个 Unchecked Exception,然而咱们还是想解决一下:

        integers.forEach(i -> {
            try {System.out.println(1 / i);
            } catch (ArithmeticException e) {
                System.err.println("Arithmetic Exception occured :" + e.getMessage());
            }
        });

下面的例子咱们应用了 try,catch 来解决异样,简略然而毁坏了 lambda 表达式的最佳实际。代码变得臃肿。

咱们将 try,catch 移到一个 wrapper 办法中:

    static Consumer<Integer> lambdaWrapper(Consumer<Integer> consumer) {
        return i -> {
            try {consumer.accept(i);
            } catch (ArithmeticException e) {
                System.err.println("Arithmetic Exception occured :" + e.getMessage());
            }
        };
    }

则原来的调用变成这样:

integers.forEach(lambdaWrapper(i -> System.out.println(1 / i)));

然而下面的 wrapper 固定了捕捉 ArithmeticException,咱们再将其改编成一个更通用的类:

    static <T, E extends Exception> Consumer<T>
    consumerWrapperWithExceptionClass(Consumer<T> consumer, Class<E> clazz) {

        return i -> {
            try {consumer.accept(i);
            } catch (Exception ex) {
                try {E exCast = clazz.cast(ex);
                    System.err.println("Exception occured :" + exCast.getMessage());
                } catch (ClassCastException ccEx) {throw ex;}
            }
        };
    }

下面的类传入一个 class,并将其 cast 到异样,如果能 cast,则解决,否则抛出异样。

这样解决之后,咱们这样调用:

integers.forEach(
                consumerWrapperWithExceptionClass(i -> System.out.println(1 / i),
                        ArithmeticException.class));

7.2 解决 checked Exception

checked Exception 是必须要解决的异样,咱们还是看个例子:

    static void throwIOException(Integer integer) throws IOException {}
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        integers.forEach(i -> throwIOException(i));

下面咱们定义了一个办法抛出 IOException,这是一个 checked Exception,须要被解决,所以在上面的 forEach 中,程序会编译失败,因为没有解决相应的异样。

最简略的方法就是 try,catch 住,如下所示:

        integers.forEach(i -> {
            try {throwIOException(i);
            } catch (IOException e) {throw new RuntimeException(e);
            }
        });

当然,这样的做法的害处咱们在下面曾经讲过了,同样的,咱们能够定义一个新的 wrapper 办法:

    static <T> Consumer<T> consumerWrapper(ThrowingConsumer<T, Exception> throwingConsumer) {

        return i -> {
            try {throwingConsumer.accept(i);
            } catch (Exception ex) {throw new RuntimeException(ex);
            }
        };
    }

咱们这样调用:

integers.forEach(consumerWrapper(i -> throwIOException(i)));

咱们也能够封装一下异样:

static <T, E extends Exception> Consumer<T> consumerWrapperWithExceptionClass(ThrowingConsumer<T, E> throwingConsumer, Class<E> exceptionClass) {

        return i -> {
            try {throwingConsumer.accept(i);
            } catch (Exception ex) {
                try {E exCast = exceptionClass.cast(ex);
                    System.err.println("Exception occured :" + exCast.getMessage());
                } catch (ClassCastException ccEx) {throw new RuntimeException(ex);
                }
            }
        };
    }

而后这样调用:

integers.forEach(consumerWrapperWithExceptionClass(i -> throwIOException(i), IOException.class));

8. stream 中 throw Exception

之前的文章咱们讲到,在 stream 中解决异样,须要将 checked exception 转换为 unchecked exception 来解决。

咱们是这样做的:

    static <T> Consumer<T> consumerWrapper(ThrowingConsumer<T, Exception> throwingConsumer) {

        return i -> {
            try {throwingConsumer.accept(i);
            } catch (Exception ex) {throw new RuntimeException(ex);
            }
        };
    }

将异样捕捉,而后封装成为 RuntimeException。

封装成 RuntimeException 感觉总是有那么一点点问题,那么有没有什么更好的方法?

8.1 throw 小窍门

java 的类型推断大家应该都晓得,如果是 <T extends Throwable> 这样的模式,那么 T 将会被认为是 RuntimeException!

咱们看下例子:

public class RethrowException {public static <T extends Exception, R> R throwException(Exception t) throws T {throw (T) t; // just throw it, convert checked exception to unchecked exception
    }

}

下面的类中,咱们定义了一个 throwException 办法,接管一个 Exception 参数,将其转换为 T,这里的 T 就是 unchecked exception。

接下来看下具体的应用:

@Slf4j
public class RethrowUsage {public static void main(String[] args) {
        try {throwIOException();
        } catch (IOException e) {log.error(e.getMessage(),e);
            RethrowException.throwException(e);
        }
    }

    static void throwIOException() throws IOException{throw new IOException("io exception");
    }
}

下面的例子中,咱们将一个 IOException 转换成了一个 unchecked exception。

9. stream 中 Collectors 的用法

在 java stream 中,咱们通常须要将解决后的 stream 转换成汇合类,这个时候就须要用到 stream.collect 办法。collect 办法须要传入一个 Collector 类型,要实现 Collector 还是很麻烦的,须要实现好几个接口。

于是 java 提供了更简略的 Collectors 工具类来不便咱们构建 Collector。

上面咱们将会具体解说 Collectors 的用法。

如果咱们有这样两个 list:

List<String> list = Arrays.asList("jack", "bob", "alice", "mark");
List<String> duplicateList = Arrays.asList("jack", "jack", "alice", "mark");

下面一个是无反复的 list,一个是带反复数据的 list。接下来的例子咱们会用下面的两个 list 来解说 Collectors 的用法。

9.1 Collectors.toList()

List<String> listResult = list.stream().collect(Collectors.toList());
        log.info("{}",listResult);

将 stream 转换为 list。这里转换的 list 是 ArrayList,如果想要转换成特定的 list,须要应用 toCollection 办法。

9.2 Collectors.toSet()

Set<String> setResult = list.stream().collect(Collectors.toSet());
        log.info("{}",setResult);

toSet 将 Stream 转换成为 set。这里转换的是 HashSet。如果须要特地指定 set,那么须要应用 toCollection 办法。

因为 set 中是没有反复的元素,如果咱们应用 duplicateList 来转换的话,会发现最终后果中只有一个 jack。

Set<String> duplicateSetResult = duplicateList.stream().collect(Collectors.toSet());
        log.info("{}",duplicateSetResult);

9.3 Collectors.toCollection()

下面的 toMap,toSet 转换进去的都是特定的类型,如果咱们须要自定义,则能够应用 toCollection()

List<String> custListResult = list.stream().collect(Collectors.toCollection(LinkedList::new));
        log.info("{}",custListResult);

下面的例子,咱们转换成了 LinkedList。

9.4 Collectors.toMap()

toMap 接管两个参数,第一个参数是 keyMapper,第二个参数是 valueMapper:

Map<String, Integer> mapResult = list.stream()
                .collect(Collectors.toMap(Function.identity(), String::length));
        log.info("{}",mapResult);

如果 stream 中有反复的值,则转换会报 IllegalStateException 异样:

Map<String, Integer> duplicateMapResult = duplicateList.stream()
                .collect(Collectors.toMap(Function.identity(), String::length));

怎么解决这个问题呢?咱们能够这样:

Map<String, Integer> duplicateMapResult2 = duplicateList.stream()
                .collect(Collectors.toMap(Function.identity(), String::length, (item, identicalItem) -> item));
        log.info("{}",duplicateMapResult2);

在 toMap 中增加第三个参数 mergeFunction,来解决抵触的问题。

9.5 Collectors.collectingAndThen()

collectingAndThen 容许咱们对生成的汇合再做一次操作。

List<String> collectAndThenResult = list.stream()
                .collect(Collectors.collectingAndThen(Collectors.toList(), l -> {return new ArrayList<>(l);}));
        log.info("{}",collectAndThenResult);

9.6 Collectors.joining()

Joining 用来连贯 stream 中的元素:

String joinResult = list.stream().collect(Collectors.joining());
        log.info("{}",joinResult);
        String joinResult1 = list.stream().collect(Collectors.joining(" "));
        log.info("{}",joinResult1);
        String joinResult2 = list.stream().collect(Collectors.joining("","prefix","suffix"));
        log.info("{}",joinResult2);

能够不带参数,也能够带一个参数,也能够带三个参数,依据咱们的须要进行抉择。

9.7 Collectors.counting()

counting 次要用来统计 stream 中元素的个数:

Long countResult = list.stream().collect(Collectors.counting());
        log.info("{}",countResult);

9.8 Collectors.summarizingDouble/Long/Int()

SummarizingDouble/Long/Int 为 stream 中的元素生成了统计信息,返回的后果是一个统计类:

IntSummaryStatistics intResult = list.stream()
                .collect(Collectors.summarizingInt(String::length));
        log.info("{}",intResult);

输入后果:

22:22:35.238 [main] INFO com.flydean.CollectorUsage - IntSummaryStatistics{count=4, sum=16, min=3, average=4.000000, max=5}

9.9 Collectors.averagingDouble/Long/Int()

averagingDouble/Long/Int() 对 stream 中的元素做均匀:

Double averageResult = list.stream().collect(Collectors.averagingInt(String::length));
        log.info("{}",averageResult);

9.10 Collectors.summingDouble/Long/Int()

summingDouble/Long/Int() 对 stream 中的元素做 sum 操作:

Double summingResult = list.stream().collect(Collectors.summingDouble(String::length));
        log.info("{}",summingResult);

9.11 Collectors.maxBy()/minBy()

maxBy()/minBy() 依据提供的 Comparator,返回 stream 中的最大或者最小值:

Optional<String> maxByResult = list.stream().collect(Collectors.maxBy(Comparator.naturalOrder()));
        log.info("{}",maxByResult);

9.12 Collectors.groupingBy()

GroupingBy 依据某些属性进行分组,并返回一个 Map:

Map<Integer, Set<String>> groupByResult = list.stream()
                .collect(Collectors.groupingBy(String::length, Collectors.toSet()));
        log.info("{}",groupByResult);

9.13 Collectors.partitioningBy()

PartitioningBy 是一个特地的 groupingBy,PartitioningBy 返回一个 Map,这个 Map 是以 boolean 值为 key,从而将 stream 分成两局部,一部分是匹配 PartitioningBy 条件的,一部分是不满足条件的:

 Map<Boolean, List<String>> partitionResult = list.stream()
                .collect(Collectors.partitioningBy(s -> s.length() > 3));
        log.info("{}",partitionResult);

看下运行后果:

22:39:37.082 [main] INFO com.flydean.CollectorUsage - {false=[bob], true=[jack, alice, mark]}

后果被分成了两局部。

10. 创立一个自定义的 collector

在之前的 java collectors 文章外面,咱们讲到了 stream 的 collect 办法能够调用 Collectors 外面的 toList() 或者 toMap() 办法,将后果转换为特定的汇合类。

明天咱们介绍一下怎么自定义一个 Collector。

10.1 Collector 介绍

咱们先看一下 Collector 的定义:

Collector 接口须要实现 supplier(),accumulator(),combiner(),finisher(),characteristics() 这 5 个接口。

同时 Collector 也提供了两个动态 of 办法来不便咱们创立一个 Collector 实例。

咱们能够看到两个办法的参数跟 Collector 接口须要实现的接口是一一对应的。

上面别离解释一下这几个参数:

  • supplier

Supplier 是一个函数,用来创立一个新的可变的汇合。换句话说 Supplier 用来创立一个初始的汇合。

  • accumulator

accumulator 定义了累加器,用来将原始元素增加到汇合中。

  • combiner

combiner 用来将两个汇合合并成一个。

  • finisher

finisher 将汇合转换为最终的汇合类型。

  • characteristics

characteristics 示意该汇合的特色。这个不是必须的参数。

Collector 定义了三个参数类型,T 是输出元素的类型,A 是 reduction operation 的累加类型也就是 Supplier 的初始类型,R 是最终的返回类型。咱们画个图来看一下这些类型之间的转换关系:

有了这几个参数,咱们接下来看看怎么应用这些参数来结构一个自定义 Collector。

10.2 自定义 Collector

咱们利用 Collector 的 of 办法来创立一个不变的 Set:

    public static <T> Collector<T, Set<T>, Set<T>> toImmutableSet() {
        return Collector.of(HashSet::new, Set::add,
                (left, right) -> {left.addAll(right);
                    return left;
                }, Collections::unmodifiableSet);
    }

下面的例子中,咱们 HashSet::new 作为 supplier,Set::add 作为 accumulator,自定义了一个办法作为 combiner,最初应用 Collections::unmodifiableSet 将汇合转换成不可变汇合。

下面咱们固定应用 HashSet::new 作为初始汇合的生成办法,实际上,下面的办法能够更加通用:

    public static <T, A extends Set<T>> Collector<T, A, Set<T>> toImmutableSet(Supplier<A> supplier) {

        return Collector.of(
                supplier,
                Set::add, (left, right) -> {left.addAll(right);
                    return left;
                }, Collections::unmodifiableSet);
    }

下面的办法,咱们将 supplier 提出来作为一个参数,由内部来定义。

看下下面两个办法的测试:

    @Test
    public void toImmutableSetUsage(){Set<String> stringSet1=Stream.of("a","b","c","d")
                .collect(ImmutableSetCollector.toImmutableSet());
        log.info("{}",stringSet1);

        Set<String> stringSet2=Stream.of("a","b","c","d")
                .collect(ImmutableSetCollector.toImmutableSet(LinkedHashSet::new));
        log.info("{}",stringSet2);
    }

输入:

INFO com.flydean.ImmutableSetCollector - [a, b, c, d]
INFO com.flydean.ImmutableSetCollector - [a, b, c, d]

11. stream reduce 详解和误区

Stream API 提供了一些预约义的 reduce 操作,比方 count(), max(), min(), sum() 等。如果咱们须要本人写 reduce 的逻辑,则能够应用 reduce 办法。

本文将会详细分析一下 reduce 办法的应用,并给出具体的例子。

11.1 reduce 详解

Stream 类中有三种 reduce,别离承受 1 个参数,2 个参数,和 3 个参数,首先来看一个参数的状况:

Optional<T> reduce(BinaryOperator<T> accumulator);

该办法承受一个 BinaryOperator 参数,BinaryOperator 是一个 @FunctionalInterface, 须要实现办法:

R apply(T t, U u);

accumulator 通知 reduce 办法怎么去累计 stream 中的数据。

举个例子:

List<Integer> intList = Arrays.asList(1,2,3);
        Optional<Integer> result1=intList.stream().reduce(Integer::sum);
        log.info("{}",result1);

下面的例子输入后果:

com.flydean.ReduceUsage - Optional[6]

一个参数的例子很简略。这里不再多说。

接下来咱们再看一下两个参数的例子:

T reduce(T identity, BinaryOperator<T> accumulator);

这个办法接管两个参数:identity 和 accumulator。多出了一个参数 identity。

兴许在有些文章外面有人通知你 identity 是 reduce 的初始化值,能够轻易指定,如下所示:

Integer result2=intList.stream().reduce(100, Integer::sum);
        log.info("{}",result2);

下面的例子,咱们计算的值是 106。

如果咱们将 stream 改成 parallelStream:

Integer result3=intList.parallelStream().reduce(100, Integer::sum);
        log.info("{}",result3);

得出的后果就是 306。

为什么是 306 呢?因为在并行计算的时候,每个线程的初始累加值都是 100,最初 3 个线程加进去的后果就是 306。

并行计算和非并行计算的后果竟然不一样,这必定不是 JDK 的问题,咱们再看一下 JDK 中对 identity 的阐明:

identity 必须是 accumulator 函数的一个 identity,也就是说必须满足:对于所有的 t, 都必须满足 accumulator.apply(identity, t) == t

所以这里咱们传入 100 是不对的,因为 sum(100+1)!= 1。

这里 sum 办法的 identity 只能是 0。

如果咱们用 0 作为 identity, 则 stream 和 parallelStream 计算出的后果是一样的。这就是 identity 的真正用意。

上面再看一下三个参数的办法:

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

和后面的办法不同的是,多了一个 combiner,这个 combiner 用来合并多线程计算的后果。

同样的,identity 须要满足 combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)

大家可能留神到了为什么 accumulator 的类型是 BiFunction 而 combiner 的类型是 BinaryOperator?

public interface BinaryOperator<T> extends BiFunction<T,T,T>

BinaryOperator 是 BiFunction 的子接口。BiFunction 中定义了要实现的 apply 办法。

其实 reduce 底层办法的实现只用到了 apply 办法,并没有用到接口中其余的办法,所以我猜想这里的不同只是为了简略的辨别。

尽管 reduce 是一个很罕用的办法,然而大家肯定要遵循 identity 的标准,并不是所有的 identity 都是适合的。

12. stream 中的 Spliterator

Spliterator 是在 java 8 引入的一个接口,它通常和 stream 一起应用,用来遍历和宰割序列。

只有用到 stream 的中央都须要 Spliterator,比方 List,Collection,IO channel 等等。

咱们先看一下 Collection 中 stream 办法的定义:

default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);
    }
default Stream<E> parallelStream() {return StreamSupport.stream(spliterator(), true);
    }

咱们能够看到,不论是并行 stream 还是非并行 stream,都是通过 StreamSupport 来结构的,并且都须要传入一个 spliterator 的参数。

好了,咱们晓得了 spliterator 是做什么的之后,看一下它的具体构造:

spliterator 有四个必须实现的办法,咱们接下来进行具体的解说。

12.1 tryAdvance

tryAdvance 就是对 stream 中的元素进行解决的办法,如果元素存在,则对他进行解决,并返回 true,否则返回 false。

如果咱们不想解决 stream 后续的元素,则在 tryAdvance 中返回 false 即可,利用这个特色,咱们能够中断 stream 的解决。这个例子我将会在前面的文章中讲到。

12.2 trySplit

trySplit 尝试对现有的 stream 进行分拆,个别用在 parallelStream 的状况,因为在并发 stream 下,咱们须要用多线程去解决 stream 的不同元素,trySplit 就是对 stream 中元素进行分拆解决的办法。

现实状况下 trySplit 应该将 stream 拆分成数目雷同的两局部能力最大晋升性能。

12.3 estimateSize

estimateSize 示意 Spliterator 中待处理的元素,在 trySplit 之前和之后个别是不同的,前面咱们会在具体的例子中阐明。

12.4 characteristics

characteristics 示意这个 Spliterator 的特色,Spliterator 有 8 大特色:

public static final int ORDERED    = 0x00000010;// 示意元素是有序的(每一次遍历后果雷同)public static final int DISTINCT   = 0x00000001;// 示意元素不反复
public static final int SORTED     = 0x00000004;// 示意元素是按肯定法则进行排列(有指定比拟器)public static final int SIZED      = 0x00000040;//
示意大小是固定的
public static final int NONNULL    = 0x00000100;// 示意没有 null 元素
public static final int IMMUTABLE  = 0x00000400;// 示意元素不可变
public static final int CONCURRENT = 0x00001000;// 示意迭代器能够多线程操作
public static final int SUBSIZED   = 0x00004000;// 示意子 Spliterators 都具备 SIZED 个性 

一个 Spliterator 能够有多个特色,多个特色进行 or 运算,最初失去最终的 characteristics。

12.5 举个例子

下面咱们探讨了 Spliterator 一些要害办法,当初咱们举一个具体的例子:

@AllArgsConstructor
@Data
public class CustBook {private String name;}

先定义一个 CustBook 类,外面放一个 name 变量。

定义一个办法,来生成一个 CustBook 的 list:

    public static List<CustBook> generateElements() {return Stream.generate(() -> new CustBook("cust book"))
                .limit(1000)
                .collect(Collectors.toList());
    }

咱们定义一个 call 办法,在 call 办法中调用了 tryAdvance 办法,传入了咱们自定义的解决办法。这里咱们批改 book 的 name, 并附加额定的信息。

    public String call(Spliterator<CustBook> spliterator) {
        int current = 0;
        while (spliterator.tryAdvance(a -> a.setName("test name"
                .concat("- add new name")))) {current++;}

        return Thread.currentThread().getName() + ":" + current;
    }

最初,写一下测试方法:

    @Test
    public void useTrySplit(){Spliterator<CustBook> split1 = SpliteratorUsage.generateElements().spliterator();
        Spliterator<CustBook> split2 = split1.trySplit();

        log.info("before tryAdvance: {}",split1.estimateSize());
        log.info("Characteristics {}",split1.characteristics());
        log.info(call(split1));
        log.info(call(split2));
        log.info("after tryAdvance {}",split1.estimateSize());
    }

运行的后果如下:

23:10:08.852 [main] INFO com.flydean.SpliteratorUsage - before tryAdvance: 500
23:10:08.857 [main] INFO com.flydean.SpliteratorUsage - Characteristics 16464
23:10:08.858 [main] INFO com.flydean.SpliteratorUsage - main:500
23:10:08.858 [main] INFO com.flydean.SpliteratorUsage - main:500
23:10:08.858 [main] INFO com.flydean.SpliteratorUsage - after tryAdvance 0

List 总共有 1000 条数据,调用一次 trySplit 之后,将 List 分成了两局部,每局部 500 条数据。

留神,在 tryAdvance 调用之后,estimateSize 变为 0,示意所有的元素都曾经被处理完毕。

再看一下这个 Characteristics=16464,转换为 16 进制:Ox4050 = ORDERED or SIZED or SUBSIZED 这三个的或运算。

这也是 ArrayList 的基本特征。

13. break stream 的 foreach

咱们通常须要在 java stream 中遍历解决外面的数据,其中 foreach 是最最罕用的办法。

然而有时候咱们并不想解决完所有的数据,或者有时候 Stream 可能十分的长,或者根本就是有限的。

一种办法是先 filter 出咱们须要解决的数据,而后再 foreach 遍历。

那么咱们如何间接 break 这个 stream 呢?明天本文重点解说一下这个问题。

13.1 应用 Spliterator

上篇文章咱们在讲 Spliterator 的时候提到了,在 tryAdvance 办法中,如果返回 false,则 Spliterator 将会进行解决后续的元素。

通过这个思路,咱们能够创立自定义 Spliterator。

如果咱们有这样一个 stream:

Stream<Integer> ints = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

咱们想定义一个操作,当 x > 5 的时候就进行。

咱们定义一个通用的 Spliterator:

public class CustomSpliterator<T> extends Spliterators.AbstractSpliterator<T>  {

    private Spliterator<T> splitr;
    private Predicate<T> predicate;
    private volatile boolean isMatched = true;

    public CustomSpliterator(Spliterator<T> splitr, Predicate<T> predicate) {super(splitr.estimateSize(), 0);
        this.splitr = splitr;
        this.predicate = predicate;
    }

    @Override
    public synchronized boolean tryAdvance(Consumer<? super T> consumer) {
        boolean hadNext = splitr.tryAdvance(elem -> {if (predicate.test(elem) && isMatched) {consumer.accept(elem);
            } else {isMatched = false;}
        });
        return hadNext && isMatched;
    }
}

在下面的类中,predicate 是咱们将要传入的判断条件,咱们重写了 tryAdvance,通过将 predicate.test(elem) 退出判断条件,从而当条件不满足的时候返回 false.

看下怎么应用:

@Slf4j
public class CustomSpliteratorUsage {public static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<T> predicate) {CustomSpliterator<T> customSpliterator = new CustomSpliterator<>(stream.spliterator(), predicate);
        return StreamSupport.stream(customSpliterator, false);
    }

    public static void main(String[] args) {Stream<Integer> ints = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        List<Integer> result =
          takeWhile(ints, x -> x < 5)
                        .collect(Collectors.toList());
        log.info(result.toString());
    }
}

咱们定义了一个 takeWhile 办法,接管 Stream 和 predicate 条件。

只有当 predicate 条件满足的时候才会持续,咱们看下输入的后果:

[main] INFO com.flydean.CustomSpliteratorUsage - [1, 2, 3, 4]

13.2 自定义 forEach 办法

除了应用 Spliterator,咱们还能够自定义 forEach 办法来应用本人的遍历逻辑:

public class CustomForEach {

    public static class Breaker {
        private volatile boolean shouldBreak = false;

        public void stop() {shouldBreak = true;}

        boolean get() {return shouldBreak;}
    }

    public static <T> void forEach(Stream<T> stream, BiConsumer<T, Breaker> consumer) {Spliterator<T> spliterator = stream.spliterator();
        boolean hadNext = true;
        Breaker breaker = new Breaker();

        while (hadNext && !breaker.get()) {
            hadNext = spliterator.tryAdvance(elem -> {consumer.accept(elem, breaker);
            });
        }
    }
}

下面的例子中,咱们在 forEach 中引入了一个内部变量,通过判断这个内部变量来决定是否进入 spliterator.tryAdvance 办法。

看下怎么应用:

@Slf4j
public class CustomForEachUsage {public static void main(String[] args) {Stream<Integer> ints = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        List<Integer> result = new ArrayList<>();
        CustomForEach.forEach(ints, (elem, breaker) -> {if (elem >= 5) {breaker.stop();
            } else {result.add(elem);
            }
        });
        log.info(result.toString());
    }
}

下面咱们用新的 forEach 办法,并通过判断条件来重置判断 flag,从而达到 break stream 的目标。

14. predicate chain 的应用

Predicate 是一个 FunctionalInterface,代表的办法须要输出一个参数,返回 boolean 类型。通常用在 stream 的 filter 中,示意是否满足过滤条件。

    boolean test(T t);

14.1 根本应用

咱们先看下在 stream 的 filter 中怎么应用 Predicate:

    @Test
    public void basicUsage(){List<String> stringList=Stream.of("a","b","c","d").filter(s -> s.startsWith("a")).collect(Collectors.toList());
        log.info("{}",stringList);
    }

下面的例子很根底了,这里就不多讲了。

14.2 应用多个 Filter

如果咱们有多个 Predicate 条件,则能够应用多个 filter 来进行过滤:

    public void multipleFilters(){List<String> stringList=Stream.of("a","ab","aac","ad").filter(s -> s.startsWith("a"))
                .filter(s -> s.length()>1)
                .collect(Collectors.toList());
        log.info("{}",stringList);
    }

下面的例子中,咱们又增加了一个 filter,在 filter 又增加了一个 Predicate。

14.3 应用复合 Predicate

Predicate 的定义是输出一个参数,返回 boolean 值,那么如果有多个测试条件,咱们能够将其合并成一个 test 办法:

    @Test
    public void complexPredicate(){List<String> stringList=Stream.of("a","ab","aac","ad")
                .filter(s -> s.startsWith("a") &&  s.length()>1)
                .collect(Collectors.toList());
        log.info("{}",stringList);
    }

下面的例子中,咱们把 s.startsWith(“a”) && s.length()>1 作为 test 的实现。

14.4 组合 Predicate

Predicate 尽管是一个 interface,然而它有几个默认的办法能够用来实现 Predicate 之间的组合操作。

比方:Predicate.and(), Predicate.or(), 和 Predicate.negate()。

上面看下他们的例子:

@Test
    public void combiningPredicate(){Predicate<String> predicate1 = s -> s.startsWith("a");
        Predicate<String> predicate2 =  s -> s.length() > 1;
        List<String> stringList1 = Stream.of("a","ab","aac","ad")
                .filter(predicate1.and(predicate2))
                .collect(Collectors.toList());
        log.info("{}",stringList1);

        List<String> stringList2 = Stream.of("a","ab","aac","ad")
                .filter(predicate1.or(predicate2))
                .collect(Collectors.toList());
        log.info("{}",stringList2);

        List<String> stringList3 = Stream.of("a","ab","aac","ad")
                .filter(predicate1.or(predicate2.negate()))
                .collect(Collectors.toList());
        log.info("{}",stringList3);

    }

实际上,咱们并不需要显示的 assign 一个 predicate,只有是满足
predicate 接口的 lambda 表达式都能够看做是一个 predicate。同样能够调用 and,or 和 negate 操作:

List<String> stringList4 = Stream.of("a","ab","aac","ad")
                .filter(((Predicate<String>)a -> a.startsWith("a"))
                        .and(a -> a.length() > 1))
                .collect(Collectors.toList());
        log.info("{}",stringList4);

14.5 Predicate 的汇合操作

如果咱们有一个 Predicate 汇合,咱们能够应用 reduce 办法来对其进行合并运算:

@Test
    public void combiningPredicateCollection(){List<Predicate<String>> allPredicates = new ArrayList<>();
        allPredicates.add(a -> a.startsWith("a"));
        allPredicates.add(a -> a.length() > 1);

        List<String> stringList = Stream.of("a","ab","aac","ad")
                .filter(allPredicates.stream().reduce(x->true, Predicate::and))
                .collect(Collectors.toList());
        log.info("{}",stringList);
    }

下面的例子中,咱们调用 reduce 办法,对汇合中的 Predicate 进行了 and 操作。

15. 中构建有限的 stream

在 java 中,咱们能够将特定的汇合转换成为 stream,那么在有些状况下,比方测试环境中,咱们须要结构肯定数量元素的 stream,须要怎么解决呢?

这里咱们能够构建一个有限的 stream,而后调用 limit 办法来限定返回的数目。

15.1 根本应用

先看一个应用 Stream.iterate 来创立有限 Stream 的例子:

    @Test
    public void infiniteStream(){Stream<Integer> infiniteStream = Stream.iterate(0, i -> i + 1);
        List<Integer> collect = infiniteStream
                .limit(10)
                .collect(Collectors.toList());
        log.info("{}",collect);
    }

下面的例子中,咱们通过调用 Stream.iterate 办法,创立了一个 0,1,2,3,4…. 的有限 stream。

而后调用 limit(10) 来获取其中的前 10 个。最初调用 collect 办法将其转换成为一个汇合。

看下输入后果:

INFO com.flydean.InfiniteStreamUsage - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

15.2 自定义类型

如果咱们想输入自定义类型的汇合,该怎么解决呢?

首先,咱们定义一个自定义类型:

@Data
@AllArgsConstructor
public class IntegerWrapper {private Integer integer;}

而后利用 Stream.generate 的生成器来创立这个自定义类型:

    public static IntegerWrapper generateCustType(){return new IntegerWrapper(new Random().nextInt(100));
    }

    @Test
    public void infiniteCustType(){
        Supplier<IntegerWrapper> randomCustTypeSupplier = InfiniteStreamUsage::generateCustType;
        Stream<IntegerWrapper> infiniteStreamOfCustType = Stream.generate(randomCustTypeSupplier);

        List<IntegerWrapper> collect = infiniteStreamOfCustType
                .skip(10)
                .limit(10)
                .collect(Collectors.toList());
        log.info("{}",collect);
    }

看下输入后果:

INFO com.flydean.InfiniteStreamUsage - [IntegerWrapper(integer=46), IntegerWrapper(integer=42), IntegerWrapper(integer=67), IntegerWrapper(integer=11), IntegerWrapper(integer=14), IntegerWrapper(integer=80), IntegerWrapper(integer=15), IntegerWrapper(integer=19), IntegerWrapper(integer=72), IntegerWrapper(integer=41)]

16. 自定义 parallelStream 的 thread pool

之前咱们讲到 parallelStream 的底层应用到了 ForkJoinPool 来提交工作的,默认状况下 ForkJoinPool 为每一个处理器创立一个线程,parallelStream 如果没有特地指明的状况下,都会应用这个共享线程池来提交工作。

那么在特定的状况下,咱们想应用自定义的 ForkJoinPool 该怎么解决呢?

16.1 通常操作

如果咱们想做一个从 1 到 1000 的加法,咱们能够用并行 stream 这样做:

List<Integer> integerList= IntStream.range(1,1000).boxed().collect(Collectors.toList());
        ForkJoinPool customThreadPool = new ForkJoinPool(4);

        Integer total= integerList.parallelStream().reduce(0, Integer::sum);
        log.info("{}",total);

输入后果:

INFO com.flydean.CustThreadPool - 499500

16.2 应用自定义 ForkJoinPool

下面的例子应用的共享的 thread pool。咱们看下怎么应用自定义的 thread pool 来提交并行 stream:

List<Integer> integerList= IntStream.range(1,1000).boxed().collect(Collectors.toList());

ForkJoinPool customThreadPool = new ForkJoinPool(4);
        Integer actualTotal = customThreadPool.submit(() -> integerList.parallelStream().reduce(0, Integer::sum)).get();
        log.info("{}",actualTotal);

下面的例子中,咱们定义了一个 4 个线程的 ForkJoinPool,并应用它来提交了这个 parallelStream。

输入后果:

INFO com.flydean.CustThreadPool - 499500

如果不想应用公共的线程池,则能够应用自定义的 ForkJoinPool 来提交。

17. 总结

本文对立介绍了 Stream 和 lambda 表达式的应用,涵盖了 Stream 和 lambda 表达式的各个小的细节,心愿大家可能喜爱。

本文的代码 https://github.com/ddean2009/learn-java-streams/

本文的 PDFjava-stream-lambda-all-in-one.pdf

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

正文完
 0