Lambda表达式
Lambda表达式可以理解为简洁地表示可传递的匿名函数的一种方式: 它没有名称,但它
有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
举个例子:
// Java8以前
Comparator byWeight = new Comparator() {
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
}
// Java8,使用了Lambda表达式
Comparator byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
如何使用Lambda表达式?
注意: 可以不需要写参数类型,单条语句不需要写return,多条语句需要使用{ }括号括起来。
在哪里使用Lambda表达式?
在函数式接口上使用Lambda表达式。
函数式接口
函数式接口就是只定义一个抽象方法的接口。
// 下面哪些是函数式接口?
public interface Adder {
int add(int a, int b);
}
public interface SmartAdder extends Adder{
int add(double a, double b);
}
public interface Nothing{
}
答案: 只有Adder是函数式接口。SmartAdder不是函数式接口,因为它继承了一个Adder又自己定义了一个。Nothing则没有声明抽象方法。
函数式接口能干什么?
Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。 具体来说就是函数式接口一个具体实现的实例。
其实,使用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后直接内联将它实例化。比如下面的Runnable的run方法。
// 使用匿名内部类的方式
Runnable r = new Runnabler(){
@Override
public void run(){
System.out.println("Hello World!");
}
}
// 使用Lambda
Runnable r = () -> System.out.println("Hello World!");
使用函数式接口
Predicate
java.util.function.Predicate接口定义了一个名叫test的抽象方法,他接受泛型T对象,并返回一个boolean。在你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。从下面Predicate的源码中也可以看到其他的默认实现方法,这些将会在后面进行解答。
// 底层源码
@FunctionalInterface
public interface Predicate<T>{
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);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
先看一个栗子:
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> results = new ArrayList<>();
for(T s: list){
if(p.test(s)){
results.add(s);
}
}
return results;
}
// 利用Lambda实现一个判断字符串是否为空的方法
Predicate nonEmptyStringPredicate = (String s) -> !s.isEmpty();
// 调用该过滤器
List nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
上述的做法将使程序变得非常灵活,比如之后要判断List中是否包含长度大于5的等等,则只需要利用Predicate进行相应的实现即可。
// 扩展
Predicate nonEmptyStringPredicate1 = (String s) -> s.length() > 5;
Consumer
java.util.function.Consumer 定义了一个名叫 accept 的抽象方法,它接受泛型 T的对象,没有返回,如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。
比如创建一个forEach方法,接受一个Integers的列表,并对其中每个元素执行操作。
// 底层源码
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
public static <T> void forEach(List<T> list, Consumer<T> c) {
for (T i : list) {
c.accept(i);
}
}
forEach(Arrays.asList(1,2,3,4,5),
(Integer i) -> System.out.println(i));
Function
java.util.function.Function
// 底层源码
@FunctionalInterface
public interface Function<T, R> {
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));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
例如: 创建一个map方法,以将一个String列表映射到包含每个String长度的Integer列表。
public static <T,R> List<R> map(List<T> list, Function<T,R> f) {
List<R> result = new ArrayList<>();
for (T s: list) {
result.add(f.apply(s));
}
return result;
}
// [7, 2, 6]
List l = map(Arrays.asList("lambdas","in","action"),
(String s) -> s.length());
注意:任何函数式接口都不允许抛出受检异常,如果你需要在Lambda表达式来抛出异常,有两种办法:定义一个自己的函数式接口,并声明受检异常,或者把Lambda包在一个try/catch块中。
方法引用
方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。
先看一个例子:
// 之前
inventory.sort((Apple a1, Apple a2)
-> a1.getWeight().compareTo(a2.getWeight()));
// 现在(使用方法引用和 java.util.Comparator.comparing)
inventory.sort(comparing(Apple::getWeight));
基本思想:如果一个Lambda表达的只是"直接调用这个方法", 最好还是用名称来调用它,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。但是,显式地指明方法的名称,你的代码的可读性会更好。
// 方法引用就是Lambda的快捷写法
Apple::getWeight
| | 等价于
(Apple a) -> a.getWeight()
Lambda | 等效的方法引用 |
---|---|
(Apple a) -> a.getWeight() | Apple::getWeight |
() -> Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
(str, i) -> str.substring(i) | String::substring |
(String s) -> System.out.println(s) | System.out::println |
如何创建方法引用
方法引用主要有三类
流
流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个 实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。
public static void main(String[] args) {
Apple a1 = new Apple("red", 500);
Apple a2 = new Apple("black", 300);
Apple a3 = new Apple("green", 420);
// Java7
List<Apple> appleList = Arrays.asList(a1, a2, a3);
List<Apple> list = new ArrayList<>();
for (Apple a : appleList) {
if (a.getWeight() > 400) {
list.add(a);
}
}
Collections.sort(list, new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return Integer.compare(o1.getWeight(), o2.getWeight());
}
});
List<String> colorList = new ArrayList<>();
for(Apple a: list){
colorList.add(a.getColor());
}
System.out.println(colorList);
// Java8
List<String> colorList2 =
appleList.stream()
.filter(a -> a.getWeight() > 400)
.sorted(Comparator.comparing(Apple::getWeight).reversed())
.map(Apple::getColor)
.collect(Collectors.toList());
System.out.println(colorList2);
// 多核架构并行处理
List<String> colorList3 =
appleList.parallelStream()
.filter(a -> a.getWeight() > 400)
.sorted(Comparator.comparing(Apple::getWeight).reversed())
.map(Apple::getColor)
.collect(Collectors.toList());
System.out.println(colorList3);
}
Java8中的Stream API可以让你写出这样的代码: 声明性:更简洁,更易读。可复合:更灵活。可并行:性能更好。
流到底是什么?
简单来说就是支持数据处理操作的源生成的元素序列。
使用流
中间操作
操作 | 类型 | 返回类型 | 操作参数 | 函数描述符 |
---|---|---|---|---|
filter | 中间 | Stream | Predicate | T -> boolean |
map | 中间 | Stream | Function |
T -> R |
sorted | 中间 | Stream | Comparator | (T, T) -> int |
distinct | 中间 | Stream | Predicate | T -> boolean |
终端操作
操作 | 类型 | 目的 |
---|---|---|
forEach | 终端 | 消费流中的每个元素,并对其应用Lambda。这一操作返回void |
count | 终端 | 返回流中的个数。这一操作返回long |
collect | 终端 | 把流归约成一个集合,比如List,Map甚至是Integer |
小结
* 流是“从支持数据处理操作的源生成的一些列元素”。
* 流利用内部迭代:通过filter,map,sorted等操作被抽象掉了。
* 流操作有两类:中间操作和终端操作。
* filter和map等中间操作会返回一个流,并可以连接在一起。可以用他们来设置一条流水线,但不会生成任何结果。
* forEach和count等终端操作会返回一个非流的值,并处理流水线以返回结果。
* 流中的元素是按需计算的。
使用谓词筛选
Stream接口支持filter方法,该操作会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包含所有符合谓词的元素的流。
筛选各异的元素
流还支持一个叫做distinct的方法, 会返回一个元素各异(根据流所生成元素hashCode和equals方法实现)的流。
e.g. 下面代码会筛选出所有偶数,并确保没有重复。
List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
截短流
流还支持limit(n)方法,该方法会返回一个不超过给定长度的流。所需的长度作为参数传递给limit。如果流是有序的,则最多会返回前n个元素。
List dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());
跳过元素
流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中能够元素不足n个,则返回一个空流。注意:limit(n)和skip(n)是互补的!
List dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());
对流中每一个元素应用函数
流还支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素。(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”,而不是去“修改”)。
e.g. 下面的代码把方法引用Dish::getName传给了map方法,来提取流中菜肴的名称。
List dishNames = menu.stream()
.map(Dish::getName)
.collect(toList());
如果你要找出每道菜的名称并判断名称有多长,怎么做?
List dishNameLengths = menu.stream()
.map(Dish::getName)
.mdistinctap(String::length)
.collect(toList);
流的扁平化
对于一张单词表,如何返回一张列表,列出里面各不相同的字符呢?e.g. 给定单词列表[“Hello”,“World”],你想要返回列表[“H”,“e”,“l”, “o”,“W”,“r”,“d”]。
你可能想到的第一个版本是这样的
words.stream()
.map(word -> word.split(""))
.distinct()
.collect(toList());
这个方法的问题在于:传递给map方法的Lambda为每个单词返回了一个String[]。因此,map返回的流实际上是Stream类型的。
检查谓词是否至少匹配一个元素
anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。
e.g. 检查菜单中是否有素食
if (menu.stream().anyMatch(Dish::isVegetarian)) {
System.out.println("The menu is (somewhat) vegetarian frinedly!!!")
}
anyMatch方法返回一个boolean,因此是一个终端操作。
检查谓词是否匹配所有元素
allMatch方法的工作原理和anyMatch类似,它会看看流中的元素是否都能匹配给定的谓词。
e.g. 查看菜品是否有利健康(即所有菜的热量都低于1000卡路里)
boolean isHealthy = menu.stream()
.allMatch(d -> d.getCalories() < 1000);
与之对应的是noneMatch,他可以确保流中没有任何元素与给定的谓词匹配。
boolean isHealthy = menu.stream()
.noneMatch(d -> d.getCalories() >= 1000);
anyMatch,allMatch和noneMatch这三个操作都用到了所谓的短路,这就是Java中&&和||运算符短路在流中的版本。
查找元素
findAny方法将返回当前流中的任意元素。可以与其他流操作结合使用。
e.g. 想找一道素食菜肴,可以结合使用filter和findAny来查询。
Optional dish =
menu.stream()
.filter(Dish::isVegetarian)
.findAny();
Optional
Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在。在上面的例子中,findAny可能什么元素都没找到,而Optional的引入可以避免出现null的问题。
Optional中有几种可以迫使你显式地检查值是否存在或处理值不存在的方法。
isPresent():将在Optional包含值的时候返回true,否则返回false。
ifPresent(Consumer block)会在值存在的时候执行给定的代码块。
T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。
T orElse(T other)会在值存在时返回值,否则返回一个默认值。
menu.stream()
.filter(Dish::isVegetarian)
.findAny()
.ifPresent(d -> System.out.println(d.getName());
查找第一个元素
List someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional firstSquareDivisibleByThree =
someNumbers.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findFirst(); // 9
何时使用findFirst和findAny
答案是并行。找到第一个元素在并行上限制更多。如果你不关心返回的元素是哪个,请使用findAny,因为它在使用并行流时限制较少。
归约
如何把一个流中的元素组合起来,使用reduce操作来表达更复杂的查询,比如“计算菜单中的总卡路里”或“菜单中卡路里最高的菜是哪一个”。此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个Integer。这样的查询可以被归类为归约操作(将流归约成一个值)。