Fork me on GitHub

Java 8 新特性

注意:所有文章除特别说明外,转载请注明出处.

Java 8 新特性

[TOC]

1. Lambda表达式

Lambda表达式允许将函数作为一个方法的参数(函数作为参数传递进方法中),使用Lambda表达式可以使程序变得紧凑。

// 1. 不需要参数,返回值为 5  
() -> 5  

// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  

// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  

// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  

// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

package cn.edu.xidian.B.Demo.Java8;

public class Java8Test {
    public static void main(String[] args) {
        Java8Test java8Test = new Java8Test();
        MathOperation addtion = (int a, int b) -> a + b;

        MathOperation substraction = (a, b) -> a - b;

        MathOperation multiplication = (int a, int b) -> {return a*b;};

        MathOperation division = (int a, int b) -> a/b;

        System.out.println("8+5=" + java8Test.oprate(8,5, addtion));
        System.out.println("8-5=" + java8Test.oprate(8,5, substraction));
        System.out.println("8*5=" + java8Test.oprate(8,5, multiplication));
        System.out.println("8/5=" + java8Test.oprate(8,5, division));

        GreetingService greetingService = message -> System.out.println("hello" + message);

        GreetingService greetingService1 = message -> System.out.println("hello" + message);

        greetingService.sayMessage("world");
        greetingService1.sayMessage("google");

    }

    interface MathOperation{
        int operation(int a, int b);
    }

    interface GreetingService{
        void sayMessage(String message);
    }

    private int oprate(int a, int b, MathOperation mathOperation){
        return mathOperation.operation(a, b);
    }
}

注意:1.Lambda 表达式主要用来定义行内执行的方法类型接口。2.Lambda表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

2. 变量作用域

lambda表达式只能引用标记final的外层局部变量,所以lambda内部修改定义在域外的局部变量。

3.Lambda表达式实现Runnable接口

1.使用匿名内部类

// 1.1使用匿名内部类  
new Thread(new Runnable() {  
    @Override  
    public void run() {  
        System.out.println("Hello world !");  
    }  
}).start();  

// 1.2使用 lambda expression  
new Thread(() -> System.out.println("Hello world !")).start();  

// 2.1使用匿名内部类  
Runnable race1 = new Runnable() {  
    @Override  
    public void run() {  
        System.out.println("Hello world !");  
    }  
};  

// 2.2使用 lambda expression  
Runnable race2 = () -> System.out.println("Hello world !");  

// 直接调用 run 方法(没开新线程哦!)  
race1.run();  
race2.run();  

4.方法的引用

提示:在Java8中方法的引用使用 :: 形式,比如在类Car中引用方法run()方法,直接使用形式 Car :: run 的形式。

5.函数式接口

Java 8为函数式接口引入了一个新注解@FunctionalInterface,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。

提醒:加不加 @FunctionalInterface 对于接口是不是函数式接口没有影响,该注解只是提醒编译器去检查该接口是否仅包含一个抽象方法。

1.函数式接口里允许定义默认方法

函数式接口里是可以包含默认方法,因为默认方法不是抽象方法,其有一个默认实现,所以是符合函数式接口的定义的。

2.函数式接口里允许定义静态方法
default void test();
3. 总结
1. 如果一个接口只要一个抽象方法,那么该接口就是一个函数式接口

2. 如果我们在某一个接口上声明了FunctionalInterface注解,那么编译器就会按照函数式接口的定义来要求该接口。

3. 如果某一个接口只有一个抽象方法,但我们并没有给该接口声明FunctionalInterface注解,那编译器会对该接口定义为函数式接口。

4. If an interface declares an abstract method overriding one of the public methods of {@code java.lang.Object}, that also does em>not</em> count toward the interface's abstract method count since any implementation of the interface will have an implementation from {@code java.lang.Object} or elsewhere.

5. 在将函数作为一等公民的语言中,Lambda表达式的类型是函数,但是在Java中,Lambda表达式是对象,它们必须依附于一类特别的对象类型——函数式接口。

6. 高阶函数,如果一个函数的参数是为一个函数,那么该函数就称为高阶函数。

7. 流是存在短路运算的,只要其中一个操作满足条件,后面的操作就将不会再去执行。
4. Function 接口

表示接收一个参数,并返回一个结果函数式接口。

Function<T, R> T 表示输入参数类型 R 表示结果类型

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

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    Objects.requireNonNull(before);
    return (V v) -> apply(before.apply(v));
}

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
}

/**
 * Returns a function that always returns its input argument.
 *
 * @param <T> the type of the input and output objects to the function
 * @return a function that always returns its input argument
 */
static <T> Function<T, T> identity() {
    return t -> t;
}
5. BiFunction 接口

BiFunction<T,U,R>描述了 参数类型为T和U,而返回类型为R的函数。

/**
 * Returns a composed function that first applies this function to
 * its input, and then applies the {@code after} function to the result.
 * If evaluation of either function throws an exception, it is relayed to
 * the caller of the composed function.
 *
 * @param <V> the type of output of the {@code after} function, and of the
 *           composed function
 * @param after the function to apply after this function is applied
 * @return a composed function that first applies this function and then
 * applies the {@code after} function
 * @throws NullPointerException if after is null
 */
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t, U u) -> after.apply(apply(t, u));
}

/**
 * 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);

总结:在之前我们定义函数功能的时候,我们在定义的时候已经固定好实现逻辑,然后现在的函数式编程只是定义了一个通用的方法,然后用户在具体使用的时候定义它的实现逻辑。

6. Predicate接口
/**
 * 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);

//逻辑与操作
default Predicate<T> and(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) && other.test(t);
}

//逻辑非操作
default Predicate<T> negate() {
    return (t) -> !test(t);
}

//逻辑或操作
default Predicate<T> or(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) || other.test(t);
}

...

在源码中查看具体的方法。

7. Supplier 接口

不接受参数,且返回结果。

/**
 * Gets a result.
 *
 * @return a result
 */
T get();
8. Consumer接口

表示接收一个单一的输入参数,不返回结果。

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

* @param after the operation to perform after this operation
 * @return a composed {@code Consumer} that performs in sequence this
 * operation followed by the {@code after} operation
 * @throws NullPointerException if {@code after} is null
 */
default Consumer<T> andThen(Consumer<? super T> after) {
    Objects.requireNonNull(after);
    return (T t) -> { accept(t); after.accept(t); };
}


//example
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(new Consumer<Integer>() {
    @Override
    public void accept(Integer integer) {
        System.out.println(integer);
    }
});

Optional

Optional 对象是一个T类型对象或者空对象的封装。Optional类型是要么指向对象要么为null的T类型引用的安全替代者。

8.7.1 使用Optional类型值

有效使用Optional的关键在于:使用一个要么如果值不存在,产生另外一个替代者。要么如果值存在,使用该值的方法。即不会报空指针异常。

第一种策略:当没有可匹配项时,我们可能会希望使用一个默认值,可能是一个空字符串。

    //封装的字符串,如果没有的话则为空字符串
    String result = optionalString.orElse("");

    //只有在被需要时才会被调用
    String result = optionalString.orElseGet(() -> System.getProperty("myapp.default"));

    //提供一个可以产生异常对象的方法
    String result = optionalString.orElseThrow(IllegalStateException::new);

8.7.2 如何不使用Optional类型值

8.7.3 创建Optional类型值

如果我们需要创建Optional对象的方法,有一些静态方法选择。Optional.of(result)和Optional.empty()。

public static Optional<Double> inverse(Double x){
    return x == 0 ? Optional.empty() : Optional.of(1 / x);
}

8.7.4 使用flatMap来组合可选值函数

8.7.5 将Optional转变成流

/**
     * If a value is present, apply the provided mapping function to it,
     * and if the result is non-null, return an {@code Optional} describing the
     * result.  Otherwise return an empty {@code Optional}.
     *
     * @apiNote This method supports post-processing on optional values, without
     * the need to explicitly check for a return status.  For example, the
     * following code traverses a stream of file names, selects one that has
     * not yet been processed, and then opens that file, returning an
     * {@code Optional<FileInputStream>}:
     *
     * <pre>{@code
     *     Optional<FileInputStream> fis =
     *         names.stream().filter(name -> !isProcessedYet(name))
     *                       .findFirst()
     *                       .map(name -> new FileInputStream(name));
     * }</pre>
     *
     * Here, {@code findFirst} returns an {@code Optional<String>}, and then
     * {@code map} returns an {@code Optional<FileInputStream>} for the desired
     * file if one exists.
     *
     * @param <U> The type of the result of the mapping function
     * @param mapper a mapping function to apply to the value, if present
     * @return an {@code Optional} describing the result of applying a mapping
     * function to the value of this {@code Optional}, if a value is present,
     * otherwise an empty {@code Optional}
     * @throws NullPointerException if the mapping function is null
     */
    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }

Spliterator

Spliterator是一个可分割迭代器(splitable iterator),可以和iterator顺序遍历迭代器一起看。jdk1.8发布后,对于并行处理的能力大大增强,Spliterator就是为了并行遍历元素而设计的一个迭代器,jdk1.8中的集合框架中的数据结构都默认实现了spliterator,后面我们也会结合ArrayList中的spliterator()一起解析。

//单个对元素执行给定的动作,如果有剩下元素未处理返回true,否则返回false
boolean tryAdvance(Consumer<? super T> action);

//对每个剩余元素执行给定的动作,依次处理,直到所有元素已被处理或被异常终止。默认方法调用tryAdvance方法
default void forEachRemaining(Consumer<? super T> action) {
   do { } while (tryAdvance(action));
}

//对任务分割,返回一个新的Spliterator迭代器
Spliterator<T> trySplit();

//用于估算还剩下多少个元素需要遍历
long estimateSize();

//当迭代器拥有SIZED特征时,返回剩余元素个数;否则返回-1
default long getExactSizeIfKnown() {
   return (characteristics() & SIZED) == 0 ? -1L : estimateSize();
}

 //返回当前对象有哪些特征值
int characteristics();

//是否具有当前特征值
default boolean hasCharacteristics(int characteristics) {
   return (characteristics() & characteristics) == characteristics;
}
//如果Spliterator的list是通过Comparator排序的,则返回Comparator
//如果Spliterator的list是自然排序的 ,则返回null
//其他情况下抛错
default Comparator<? super T> getComparator() {
    throw new IllegalStateException();
}

提示:特征值其实就是为表示该Spliterator有哪些特性,用于可以更好控制和优化Spliterator的使用。

6.Java8 默认方法

简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。我们只需在方法名前面加个 default 关键字即可实现默认方法。

默认方法很大程度上解决向后兼容的问题。避免其破坏类。

7.Java 8 Stream

此抽象可以让编程人员以一种声明的方式处理数据。这种风格将要处理的元素集合看作是一种流,流在管道中传输,并且可以在管道的结点上进行处理。

1.Stream流

Stream流是数据渠道,用于操作数据源(集合、数组)所生成的元素序列,其可以多个操作链接起来运行,内部迭代。

流的组成:

1. 源

2. 零个或多个中间操作

3. 终止操作

注意:stream流不会改变原有的对象,会返回一个新的stream。

提示:Stream操作还有两个基础特征。1.Pipelining:中间操作都会返回流对象本身。2.内部迭代:通过访问者模式(Visitor)实现。

流操作的分类:

1. 惰性求值

2. 及早求值

提示:只有在遇到上面的操作之后,中间操作才会执行。如果没有及早求值或终止操作,中间操作不会执行。

2.创建Stream
流的典型工作流程:

    1.创建Stream
    2.指定将初始流转换成其它流的中间操作,可能需要多步操作
    3.应用终止操作产生结果

使用Collection接口的Stream()方法可以将任何集合转换为Stream。如果是数组的话可以使用静态方法 Stream.of将它转化为Stream。

Stream<String> words = Stream.of(contents.split("\\PL+");

8. filter | map | flatMap 方法

流的转换产生一个新流,该流的元素来源于其它流。filter转换生成一个匹配一定条件的新流。

List<String> words = ...;
Stream<String> longWords = words.stream().filter(w -> w.length() > 12);

filter的参数是一个Predicate对象,即从T到boolean的函数。

考虑将一个流中的值进行某种形式的转换,可以考虑使用map()方法,并且传递给它一个执行转换的函数。

Stream<String> lowercaseWords = words.stream().map(String::toLowerCase);
Stream<String> firstLetters = words.stream().map(s -> s.substring(0, 1));

使用flatMap()方法将字符串转换成只包含字符串的流。

List<String> words = ...;
Stream<String> flatResult = words.stream().flatMap(w -> codePoints(w));
2.流与集合之间的比较
1.在集合中,其会包含当前数据结构中所有的值,可以随时对其操作,在集合里面的元素值已经提前计算好。而流则是按需就散,按照当前使用的需要计算数据。
创建stream的过程就是将一个数据源(集合或者数组等)转换为一个流。这里有三种方式创建stream流。

2.迭代的差异,集合进行外部迭代,一遍遍的的去进行迭代操作。而流由流水线直接进行内部迭代。


//1 通过Collection系列提供的stream()(串行) 或parallelStream()(并行)获取
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();//串行流
Stream<String> stream2 = list.parallelStream();//并行流

//2 通过Arrays中的静态方法stream() 获取数据流
User[] u = new User[2];
Stream<User> stream3 = Arrays.stream(u);

//3 通过Stream类中的静态方法of()
Stream<String> stream4 = Stream.of("11","2");
3.中间操作
<!--这里表示对数据源进行一系列的操作处理,链接起来作为一条流水线处理各种操作。-->
4.方法
1.filter(predicate)-接收lambda,从流中排除某些元素。
2.limit(n)-截断流,使其元素不超过给定数量。
3.skip(n)-跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流,与limit(n)互补。
4.distinct-筛选,通过流所生成元素的hashcode()和equals()去重复元素。
5.forEach 迭代流中的每个数据
6.map 映射每个元素对应的结果,类似类型转换
8.flatMap() 将流中的每一个元素映射为一个流,再把每一个流连接成为一个新流

7.collect 作为终端操作,接收的是一个Collector接口参数,能对数据进行一些搜集归总操作。
    7.1 toList
    7.2 toSet
    7.3 toCollection
    7.4 toMap

9.reduce 用于组合流中的元素 如:求和|求积|求最大值等

    //计算年龄总和
    int sum = list.stream().map(Person::getAge).reduce(0, (a,b) -> a + b);

    //或者是这样
    int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum);

//例
//打印年龄大于18的前4位用户,并且跳过第1个用户
public void test1(){
    list.stream().filter((x)->x.getAge()>18).distinct().limit(4)
    .skip(1).forEach(System.out::println);
 }

8.6 简单归约

归约就是如何从流数据中获得答案,归约是终止操作。如简单的归约函数:count() | max() | min() 方法,这些方法都会返回一个Optional类型的值。

Optional<String> largest = words.max(String::compareToIngnoreCase);
System.out.println("largest:" + largest.orElse(""));

方法引用(method reference)

方法引用实际上是个lambda表达式的一种语法糖。

我们可以将方法引用看做是一个函数指针,function pointer。

方法引用分为四类:

1. 类名::静态方法名,方法引用与方法的静态引用(方法调用)没有任何关系。

2. 引用名(对象名)::实例方法名

3. 类名::实例方法名,这里的调用者是第一个参数,后续的所有参数都是作为参数传入进去的。

4. 构造方法引用:类名::new

提示:实现类的优先级比接口的优先级要高一些。

在有现成的方法的时候,可以使用方法引用来代替lambda表达式。

流不能重复使用。

9.映射

1.map 接收lambda,并将元素转换成其它形式或提取信息,会将元素映射成新的元素。

2.flatMap 接收函数作为参数,并将流中的每个值转换成流,将流链接成另外一个流。

10.构建流

1.Stream.of("Aaron","Brian","Carol"); //这里构建一个字符串流
2.Stream.empty();//这里生成空流

11.总结

1.counting 用于计算总和

    long l = list.stream().collect(counting());

    //或者这样

    long l = list.stream().count();

2.summingInt | summingLong | summingDouble

    int sum = list.stream().collect(summingInt(Person::getAge));//计算年龄总和

    //或者是这样
    int sum = list.stream().mapToInt(Person::getAge).sum();

    //或者这样
    int sum = list.stream().map(Person::getAge).reduce(Interger::sum).get();

提示:函数式编程通常提供多种方式完成同一操作。

3.averagingInt | averagingLong | averagingDouble

    Double average = list.stream().collect(averagingInt(Person::getAge));

    //或者是这样
    OptionalDouble average = list.stream().mapToInt(Person::getAge).average();

4.joining 连接字符串

    此方法是对流里面的字符串进行连接 底层实现是使用用于字符串连接的StringBuilder

    String s = list.stream().map(Person::getName).collect(joining());

    String s = list.stream().map(Person::getName).collect(joining(","));//这里是在获得名字中间用,区分开

5.取最值 maxBy() | minBy() 

    此方法需要Comparator接口作为参数

        Optional<Person> optional = list.stream().collect(maxBy(comparing(Person::getAge)));

        //或者是这样
        Optional<Person> optional = list.stream().max(comparing(Person::getAge));

6.summarizingInt | summarizingLong | summarizingDouble

    这三种方法比较特殊,比如 summarizingInt 会返回 IntSummaryStatistics 类型

        //IntSummaryStatistics 包含了计算出来的平均值,总数,总和,最值,可以通过下面这些方法获得相应的数据
        IntSummaryStatistics l = list.stream().collect(summarizingInt(Person::getAge));

数据的分区与分组

Student student1 = new Student("zhangsan", 86, 18);
Student student2 = new Student("lisi", 89, 20);
Student student3 = new Student("wangwu", 93, 23);
Student student4 = new Student("lisi", 79, 24);

List<Student> students = Arrays.asList(student1, student2, student3, student4);

//group
Map<String, List<Student>> map = students.stream().collect(Collectors.groupingBy(Student::getName));
System.out.println(map);

System.out.println("--------------------------------");

Map<Integer, List<Student>> map2 = students.stream().collect(Collectors.groupingBy(Student::getScore));
System.out.println(map2);

System.out.println("--------------------------------");

Map<String, Long> map3 = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.counting()));
System.out.println(map3);

System.out.println("--------------------------------");

Map<String, Double> map4 = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.averagingDouble(Student::getScore)));
System.out.println(map4);

System.out.println("--------------------------------");

//partion
Map<Boolean, List<Student>> map5 = students.stream().collect(Collectors.partitioningBy(student -> student.getScore() >= 90));
System.out.println(map5);

并行

我们通过list.stream()将List类型转换为流类型,还可以通过list.parallelStream()转换成并行流。所以通常可以通过使用parallelStream代替Stream()方法。

注意:并行流是将内容分成多个数据块,使用不同的线程分别处理每个数据块的Luis。


底层(bottom)

分组与分区

分区是分组的一种特例,分区的话就只有false和true两种类型。

Collector 收集器

  1. Collector作为collect()方法的参数。

  2. Collector是一个接口,它是一个可变的汇聚操作,将输入元素累积到一个可变的结果容器中。

  3. 它会在所有元素都处理完毕之后,将累积的结果转换成一个最终的表示(这是一个可选操作)。它支持串行和并行两种操作。

  4. Collectors本身提供了关于Collector的常见汇聚实现,Collectors本身实际上是一个工厂。

  5. 为了确保串行与并行操作结果的等价性,Collector函数需要满足两个条件:identity(同一性)与associativity(结合性)。

  6. a == combiner.apply(a, supplier.get());

    (List list1, List list2) -> {list1.addAll(list2); return list1}

  7. 函数式编程最大的特点:表示做什么,而不是如何做。

combiner()函数,假设有四个线程同时去执行,就会生成四个不同的结果,combiner函数就是将四个不同的结果合并成一个结果。该函数是在多线程并行环境下起作用的。

自定义 Collector

Collectors 底层

对于Collectors静态工厂类来说,其实现一共分为两种情况:

1. 通过CollectorImpl来实现。

2. 通过reducing方法来实现,reducing方法本身又是通过CollectorImpl来实现。

Comparator 比较器

如果存在特化的方法和通用的方法,那么一定调用特化的方法,这样的效率要高一些。

####

本文标题:Java 8 新特性

文章作者:Bangjin-Hu

发布时间:2018年12月05日 - 09:22:26

最后更新:2020年03月30日 - 08:09:20

原始链接:http://bangjinhu.github.io/undefined/Java 8 新特性/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Bangjin-Hu wechat
欢迎扫码关注微信公众号,订阅我的微信公众号.
坚持原创技术分享,您的支持是我创作的动力.