代码改变世界

一小时上手Java 8新特性

2019-03-31 19:53  yx1989  阅读(174)  评论(0编辑  收藏  举报

一小时上手Java 8新特性

本文摘译自 https://www.journaldev.com/2389/java-8-features-with-examples,并做了适当增补。

Iterable 接口里的 forEach() 方法

Java 8 在 java.lang.Iterable 接口里引入了forEach方法,所以我们可以只关注业务逻辑(不需要关注迭代细节)。

public class ForEach {
    public static void main(String[] args) {
        ArrayList<Integer> myList = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            myList.add(i);
        }

        myList.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(".." + integer);
            }
        });
    }
}

class MyConsumer implements Consumer<Integer> {
    @Override
    public void accept(Integer integer) {

    }
}

接口中的default和static方法

如果仔细查看forEach例子中的细节的话,会注意到forEach方法是定义在Iterable接口内的;但是我们知道接口并不能拥有方法体。

Java 8增强了接口的功能,可以使用default关键字来创建拥有方法实现的接口。Iterable接口的forEach方法实现如下:

    /**
     * Performs the given action for each element of the {@code Iterable}
     * until all elements have been processed or the action throws an
     * exception.  Unless otherwise specified by the implementing class,
     * actions are performed in the order of iteration (if an iteration order
     * is specified).  Exceptions thrown by the action are relayed to the
     * caller.
     *
     * @implSpec
     * <p>The default implementation behaves as if:
     * <pre>{@code
     *     for (T t : this)
     *         action.accept(t);
     * }</pre>
     *
     * @param action The action to be performed for each element
     * @throws NullPointerException if the specified action is null
     * @since 1.8
     */
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

我们知道Java是不支持类的多继承的,但是由于现在接口和抽象类更像了,我们可以使用接口来实现。如果一个类实现的多个接口里,有相同的default方法,那么它必须要实现自己的该方法。

接口里的static method (静态方法),可以通过接口名来调用,但是不能被重写。

Java 8在集合API里重度使用了default和static方法。

函数性接口和Lambda表达式

函数性接口是Java 8中引入的新概念:一个只有一个抽象方法的接口,被称为函数性接口。我们并不需要显示使用@FunctionalInterface 注解来声明一个接口成为函数性接口。@FunctionalInterface注解和@Override注解类似,用来帮助发现错误的操作。@FunctionalInterface注解用来避免意外向接口里添加额外方法。

Runnable接口内只有一个run方法,是函数性接口的一个很好的例子。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

使用函数性接口的一个重要好处是我们可以使用Lamba表达式来实例化一个接口。

  • 使用匿名类实例化一个接口,但是代码看起来很笨重:

    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("My Runnable");
        }
    };
    
  • 使用Lamba表达式,来提供方法参数和业务逻辑:

    Runnable r = () -> {
        System.out.println("My Runnable");
    };
    
  • 如果实现里只有一行语句的话,我们甚至可以省掉花括号:

    Runnable r = () -> System.out.println("My Runnable");
    

所以Lambda表达式意味着可以更简单的创建函数性接口的匿名类,但是Lambda表达式在运行时并没有任何额外的好处。如果不介意多写一点代码的,建议谨慎使用Lamba表达式。

Lamba表达式是个大话题,此处不展开细说。

批量操作集合数据的 Java Stream API

Java.util.stream 被添加到Java 8中,用来在集合上执行filter/map/reduce等操作。Stream API支持线性和并行执行。

filter

filter用来使用指定条件过滤集合。

示例代码:过滤出 > 90 的元素。

import java.util.ArrayList;
import java.util.stream.Stream;

public class StreamAPI {
    public static void main(String[] args) {
        ArrayList<Integer> myList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            myList.add(i);
        }

        //parallel stream
        Stream<Integer> parallelStream = myList.parallelStream();

        //sequential stream
        Stream<Integer> sequentialStream = myList.stream();

        Stream<Integer> highNums = parallelStream.filter(p -> p > 90);
        highNums.forEach(p -> System.out.println("High Nums parallel=" + p));

        Stream<Integer> sequentialHighNums = sequentialStream.filter(p -> p > 90);
        sequentialHighNums.forEach(p -> System.out.println("High Nums sequential=" + p));
    }
}

结果

High Nums parallel=93
High Nums parallel=94
High Nums parallel=95
High Nums parallel=98
High Nums parallel=91
High Nums parallel=96
High Nums parallel=92
High Nums parallel=99
High Nums parallel=97
High Nums sequential=91
High Nums sequential=92
High Nums sequential=93
High Nums sequential=94
High Nums sequential=95
High Nums sequential=96
High Nums sequential=97
High Nums sequential=98
High Nums sequential=99

可以看到filter Lambda表达式p -> p > 90,过滤出了大于90的元素。并且可以看到parallelStream()输出的结果是乱序的(由并发执行引起),而stream()是按照原顺序输出的。

map

map则是在每个元素上应用给定的函数做操作。

示例代码:如果元素大有5,则增加100。

import java.util.ArrayList;
import java.util.stream.Stream;

public class StreamAPI {
    public static void main(String[] args) {
        ArrayList<Integer> myList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            myList.add(i);
        }

        //parallel stream
        Stream<Integer> parallelStream = myList.parallelStream();

        //sequential stream
        Stream<Integer> sequentialStream = myList.stream();

        Stream<Integer> mapNums = parallelStream.map((p) -> {
            if (p > 5) {
                return p + 100;
            } else {
                return p;
            }
        });
        mapNums.forEach(p -> System.out.println("High Nums parallel=" + p));

        Stream<Integer> sequentialMapNums = sequentialStream.map((p) -> {
            if (p > 5) {
                return p + 100;
            } else {
                return p;
            }
        });
        sequentialMapNums.forEach(p -> System.out.println("High Nums sequential=" + p));
    }
}

结果

High Nums parallel=106
High Nums parallel=5
High Nums parallel=1
High Nums parallel=0
High Nums parallel=107
High Nums parallel=108
High Nums parallel=4
High Nums parallel=109
High Nums parallel=3
High Nums parallel=2
High Nums sequential=0
High Nums sequential=1
High Nums sequential=2
High Nums sequential=3
High Nums sequential=4
High Nums sequential=5
High Nums sequential=106
High Nums sequential=107
High Nums sequential=108
High Nums sequential=109

reduce

reduce是相对复杂的一个函数,他用来对集合元素做一些聚合操作,如求和、计数、最大值、最小值等等。

package com.alibaba.toolkit.demo.java8;

import java.util.ArrayList;
import java.util.Optional;
import java.util.stream.Stream;

public class StreamAPI {
    public static void main(String[] args) {
        ArrayList<Integer> myList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            myList.add(i);
        }

        //parallel stream
        Stream<Integer> parallelStream = myList.parallelStream();

        //sequential stream
        Stream<Integer> sequentialStream = myList.stream();

        Optional<Integer> sumResult = parallelStream.reduce((sum, i) -> {
            return sum + i;
        });
        System.out.println("sumResult: " + sumResult.get());

        Optional<Integer> sequentialSumResult = sequentialStream.reduce((sum, i) -> {
            return sum + i;
        });
        System.out.println("sequentialSumResult: " + sequentialSumResult.get());
    }
}

结果

sumResult: 45
sequentialSumResult: 45

reduce方法的第一个参数是当前的中间结果,第二个参数是当前遍历到的元素。

如果写完整的话,代码为

Optional<Integer> sequentialSumResult = stream.reduce(new BinaryOperator<Integer>() {
    @Override
    public Integer apply(Integer sum, Integer i) {
        return sum + i;
    }
});

Java Time API

之前版本的Java,使用Date, Time, 和Time Zones都很繁琐,Joda-Time等库变得很流行。Java 8在java.time包中提供了对时间流式操作的新API。Java 8的Time API很大程度上也参考了Joda-Time。

import java.time.LocalDate;
import java.time.Month;

public class TimeAPI {
    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        System.out.println("Current Date=" + today);

        today = LocalDate.of(2019, Month.MARCH, 31);
        System.out.println("today=" + today);

        LocalDate tomorrow = today.plusDays(1);
        System.out.println("tomorrow=" + tomorrow);

        LocalDate nextMonth = today.plusMonths(1);
        System.out.println("nextMonth=" + nextMonth);
    }
}

结果

Current Date=2019-03-31
today=2019-03-31
tomorrow=2019-04-01
nextMonth=2019-04-30

其他

Java 8 还增强了Collection API,Concurrency API,Java IO,以及众多的微小改进。篇幅所限,不一一展开了。