Java8中的新特性
一、Lambda表达式
Lambda表达式可以理解为一种可传递的匿名函数:它没有名称,但又参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
匿名:和匿名类类似的,它没有明确的名字
函数:Lambda函数不属于某个特定的类,但和方法一样都具有参数列表、函数主体、返回类型,还可以有抛出的异常列表
传递:Lambda表达式可以作为参数传递给方法或者存储在变量中。
Lambda表达式的基本形式: (parameters)-> expression 或(parameters)-> {statements;}
怎么使用Lambda表达式?
哪里可以使用Lambda表达式?你可以在函数式接口上使用Lambda表达式。
函数式接口也就是只定义了一个抽象方法的接口。Lambda表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。
上面既然提到了Lambda表达式是函数式接口的抽象方法的具体实现,那这两者之间的函数描述符应该是一致的,才可以编译成功。
如
Runnable接口的函数描述符为()->void,而其对应的Lambda表达式实现也为()->void。签名一致的情况下才能通过类型检查。
函数式接口:
在Java8以前,常用的函数式接口Comparable、Runnable、Callable等。而在Java8中又引入了一系列的函数式接口。
常用的函数式接口
类型检查:
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
三、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的基本操作
以下是一些常见的中间操作和终端操作
中间操作
终端操作
注意点:
每次转换原有 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接口的静态工程方法
Collection接口的默认方法:
stream()