Java8中部分新特性详述

Java8中的新特性

一、Lambda表达式

Lambda表达式可以理解为一种可传递的匿名函数:它没有名称,但又参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

匿名:和匿名类类似的,它没有明确的名字

函数:Lambda函数不属于某个特定的类,但和方法一样都具有参数列表、函数主体、返回类型,还可以有抛出的异常列表

传递:Lambda表达式可以作为参数传递给方法或者存储在变量中。

Lambda表达式的基本形式: (parameters)-> expression 或(parameters)-> {statements;}

怎么使用Lambda表达式?

哪里可以使用Lambda表达式?你可以在函数式接口上使用Lambda表达式。

函数式接口也就是只定义了一个抽象方法的接口。Lambda表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。

    上面既然提到了Lambda表达式是函数式接口的抽象方法的具体实现,那这两者之间的函数描述符应该是一致的,才可以编译成功。

    如

    public void process(Runnable r){

         r.run();

}
process(()->System.out.println(“this is awesome”));

    Runnable接口的函数描述符为()->void,而其对应的Lambda表达式实现也为()->void。签名一致的情况下才能通过类型检查。

函数式接口:

    在Java8以前,常用的函数式接口Comparable、Runnable、Callable等。而在Java8中又引入了一系列的函数式接口。

    常用的函数式接口

函数式接口

函数描述符

原始类型特化

Predicate<T>

T->boolean

由于Java中存在将原始类型自动转化为对应包装类型的机制以及对应的逆过程(装箱和拆箱),但进行这一操作会影响性能(装箱后的值是将基本类型包裹起来,并保存在堆中,这样一来就需要更多的内存,并需要额外的内存搜索来获取被包裹的值。故而Java8为函数式接口提供了原始类型特化的版本,以便在输入和输出都是原始类型时避免自动装箱,针对转梦的输入参数类型的函数式接口的名称都要加上对应的基本类型前缀。如DoublePredicate,LongPredicate,IntPredicate)

Consumer<T>

T->void

Function<T>

T->R

Supplier<T>

()->T

UnaryOperator<T>

T->T

BinaryOperator<T>

(T,T)->T

BiPredicate<L,R>

(L,R)->boolean

BiConsumer<T,U>

(T,U)->void

BiFunction<T,U,R>

(T,U)->R

类型检查:

    Lambda的类型是从使用Lambda的上下文(比如接受它传递的方法的参数或者接受它的值的局部变量)中推断出来的,而我们且将上下文要求Lambda表达式需要的类型称为目标类型。当目标类型的抽象方法的函数描述符和Lambda表达式的函数描述符一致时,即类型检查无误。这样我们就可以将一个Lambda表达式应用到不同的函数式接口中,只要二者的函数描述符一致即可。 

    注意点:特出的void兼容规则

    如果一个Lambda的主体是一个语句表达式,它就和一个返回void的函数描述符兼容(参数列表兼容的前提下)

类型推断:Java编译器可以从上下文推断出用什么函数式接口来配合Lambda表达式,也能推断出使用Lambda的前面,这样就可以在Lambbda语法中省去标注参数类型。

Lambda与闭包:

  闭包:闭包是一个函数的实例,可以无限制地范围那个函数的非本地变量。如闭包可以作为参数传递给另一个函数,也可以访问和修改器作用域之外的变量。

而Java8中Lambda表达式和匿名类可以做类似于闭包的事情,可以作为参数传递给方法,并且可以访问其作用域之外的变量,但存在一个限制,它们不能修改定义Lambda的方法的局部变量的内容,这些变量都必须是隐式最终的。而匿名类则为显式最终的(可以理解为是对值封闭,而不是对变量封闭)

二、方法引用:

    方法引用可以被看做仅仅调用特定方法情形下的Lambda的一种快捷写法。它表示,如果一个Lambda表达式代表的只是直接调用这个方法,那最好还是用名称来调用它,而不是去描述如何调用它。

示例:

(Apple a)->a.getWeight()  等价于方法引用的 Apple::getWeight

(str,i)->str.substring(i) 等价于string::substring

如何构建方法引用:

指向静态方法的方法引用:

指向任意类型的实例方法的方法引用

指向现有对象的实例方法的方法引用 

构造函数引用

    对于一个现有构造函数,可以利用它的名称和关键字new来创建它的一个引用ClassName::new

Supplier<Apple> c1= Apple::new;

Apple a1=c1.get();

Function<Integer,Apple> c2=Apple::new;

Apple  a2=c2.apply(110);

BiFunction<String,Integer,Apple> c3=Apple::new;

Apple a3=c3.apply(“green”,110);

三、Stream

1.Stream的概念简析

流(Stream)是Java API的新成员,允许你以声明性方法处理数据集合(在一定程度上有点类似于SQL语句),它表示的是要做什么,而不是怎么做。此外,它还可以透明的并行处理。

    下面先就展现一个流的示例写法:

List<String> threeHighCaloricDishNames = menu.stream()
        //筛选
        .filter(dish -> dish.getCalories()>500)
        //排序,降序
        .sorted(comparing(Dish::getCalories).reversed())
        //转换
        .map(Dish::getName)
        //限制输出为3个
        .limit(3)
        //将流转换为集合
        .collect(Collectors.toList());
System.out.println(threeHighCaloricDishNames);

上述代码完成了下述操作:

获取一个数据源(source)→ 数据转换→执行操作获取想要的结果

这里我们将数据转换中的操作称为中间操作,最后的执行操作获得想要的结果称作终端操作。

2.Stream的基本操作

以下是一些常见的中间操作和终端操作

中间操作

操作

返回类型

操作参数

函数描述符

filter

Stream<T>

Predicate<T>

T->boolean

map

Stream<R>

Function<T,R>

T->R

faltmap

Stream<R>

Function<T,R>

T->Stream<R>

limit

Stream<T>

sorted

Stream<T>

Comparator<T>

(T,T)->int

distinct

Stream<T>

终端操作

操作

返回类型

目的

forEach

void

消费流中的每个元素并对其应用Lambda。

count

long

返回流中的元素个数

collect

根据目的决定

把流规约成一个集合,如List、Map、Set甚至是Integer。

anyMatch

boolean

检查流中是否有所有元素能匹配给定的谓词

anyMatch

boolean

检查流中是否有一个元素能匹配给定的谓词

findAny

Optional<T>

查找到任一个匹配给定谓词的元素

findFirst

Optional<T>

查找到第一个匹配给定谓词的元素

reduce

Optional<T>

将流中所有元素组合起来,完成指定的操作,如求和、求积、求最大值、最小值等

注意点:

每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道

在对一个Stream进行多次中间操作时,由于转换操作具有延迟特性(多个转换操作只会在终端操作的时候融合起来,然后一次循环完成)。我们可以简单的这样理解,在Stream中有个操作函数的集合,每次转换操作的时候都会把转换函数放到这个集合中,在终端操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。

此外,有些中间操作和终端操作具有短路的特性。

即指对于一个中间操作而言,如果它接收的是一个无限大的流,但返回的是有限的新流。(如limt)

对于一个终端操作,如果它接受的是一个无限大的流,但能在有限的时间内计算出结果(如findFirst、findAny等)

3.Stream的使用

Stream的使用涉及到三点:

Stream的构建、Stream的转换、Stream的归约

1)Stream的构建:

最常用的创建Stream有两种途径:

a)    通过Stream接口的静态工厂方法(注意:Java8里接口可以带静态方法);

b)    通过Collection接口的默认方法--stream(),把一个Collection对象转换成Stream

Stream接口的静态工程方法

  1. of方法
  2. generate方法
  3. iterate方法

Collection接口的默认方法:

stream()

2)Stream的转换:

Stream中转换需要了解的就是刚才前面提到的Stream的中间操作。

3)Stream的归约:

Stream中的归约的最基本的方法即前面提到的Stream的终端操作。

这边需要重点强调的就是collect方法。它可以接受各种做法作为参数,将流中的元素累积成一个汇总结构。具体的做法是通过定义一个新的Collector接口来定义的。

Collector类中具有很多的工厂方法,如前面使用过的toList方法,或是toSet、toCollection、counting、summingInt等。

根据不同的工厂方法,可以实现不同的归约操作。此外你还可以实现自定义收集器。

猜你喜欢

转载自www.linuxidc.com/Linux/2017-06/144695.htm