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类:

@Datapublic 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类有什么特点:

@FunctionalInterfacepublic 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接口定义了一个办法,接管一个参数,返回一个参数。

@FunctionalInterfacepublic 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:

@FunctionalInterfacepublic 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:

@FunctionalInterfacepublic 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:

@FunctionalInterfacepublic 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等。

@FunctionalInterfacepublic 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:

@FunctionalInterfacepublic interface Supplier<T> {    /**     * Gets a result.     *     * @return a result     */    T get();}

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

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

@FunctionalInterfacepublic 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值:

@FunctionalInterfacepublic 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等。

@FunctionalInterfacepublic 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:

@FunctionalInterfacepublic 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注解能够分明的让其他人理解这个类的作用。

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

所以咱们须要这样定义:

@FunctionalInterfacepublic 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

还是下面的例子:

@FunctionalInterfacepublic 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: threeMapped value: THREEFiltered value: fourMapped value: FOUR

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

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

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

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

onetwothreefour

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

再看一个map的比照:

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

输入:

ONETWOTHREEFOUR

能够看到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。

接下来看下具体的应用:

@Slf4jpublic 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@Datapublic 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: 50023:10:08.857 [main] INFO com.flydean.SpliteratorUsage - Characteristics 1646423:10:08.858 [main] INFO com.flydean.SpliteratorUsage - main:50023:10:08.858 [main] INFO com.flydean.SpliteratorUsage - main:50023: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.

看下怎么应用:

@Slf4jpublic 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办法。

看下怎么应用:

@Slf4jpublic 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@AllArgsConstructorpublic 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

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

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