一. Lambda定义(λ):
-- 匿名,它不像普通方法那样有一个明确的名称;
-- 函数,它不像普通方法那样属于某个特定的类,但和方法一样,Lambda有参数列表、函数主体、返回类型或抛出异常列表:
-- 传递,Lambda可以作为参数传递给方法或存储在变量中:
-- 简洁。
二. Lambda表达式结构:
1. 参数列表;
2. 箭头:箭头->把参数列表与Lambda主体分隔开;
3. Lambda主体:表达式就是Lambda表达式的例子。
三.Lambda基本语法:
(parameters) -> expression
或
(parameters) -> { statements; }
使用显式返回语句时需要使用花括号“{}”。
eg: Lambda示例:
使用案例 | Lambda示例 |
无参数,返回void | () -> {} |
无参数,返回String | () -> "Raoul" |
无参数,返回String(利用显式返回语句) | () -> { return "Result";} |
布尔表达式 | (List<String> list) -> list.isEmpty() |
创建对象 | () -> new Apple(10); |
消费一个对象 | (Apple apple) -> { System.out.println(a.getWeight()); } |
从一个对象中选择/抽取 | (String s) -> s.length() |
组合两个值 | (int a, int b) -> a * b |
比较两个对象 | (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()) |
四. 在哪里使用Lambda
可以在函数式接口上使用Lambda表达式。
函数式接口:只定义一个抽象方法的接口。
@FunctionalInterface:表明为函数式接口,但它不是必须的。
(T, U) -> R表达式展示了应当如何思考函数描述符。左侧代表了参数类型。这里它代表一个函数,具有两个参数,分别为泛型T和U,返回类型为R。
Java的泛型只能绑定到引用类型,当需要引用原始类型时因为自动装箱和拆箱机制时,装箱后的值需要更多的内存,需要付出性能代价,为了避免装箱操作对Predicate<T>和Function<T, R>等通用函数式接口的原始类型特化:IntPredicate、IntToLongFunction等。
Java8中的常用函数式接口:
函数式接口 | 函数描述符 | 原始类型特化 |
Predicate<T> | T -> boolean | IntPredicate, LongPredicate, DoublePredicate |
Consumer<T> | T -> void | IntConsumer, LongConsumer, DoubleConsumer |
Function<T, R> | T -> R | IntFunction<R>, IntToDoubleFunction, IntToLongFunction, LongFunction<R>, LongToDoubleFunction, LongToIntFunction, DoubleFunction<R>, ToIntFunction<T>, ToDoubleFunction<T>, ToLongFunction<T> |
Supplier<T> | () -> T | BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier |
UnaryOperator<T> | T -> T | IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator |
BinaryOperator<T> | (T, T) -> T | IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator |
BiPredicate<L, R> | (L, R) -> boolean | |
BiConsumer<T, U> | (T, U) -> void | ObjectIntConsumer<T>, ObjectLongConsumer<T>, ObjectDoubleConsumer<T> |
BiFunction<T, U, R> | (T, U) -> R | ToIntBiFunction<T, U>, ToLongBiFunction<T, U>, ToDoubleBiFunction<T, U> |
Runnable | () -> void |
当需要Lambda表达式抛出异常时,有两种方式:
-- 自己编写新的函数式接口,并声明受检异常(任何函数式接口都不允许抛出受检异常);
-- 将Lambda包在一个try/catch块中。
@FunctionalInterface public interface BufferedReaderProcessor { String process(BufferedReader b) throws IOException; }
Function<BufferedReader, String> f = (BufferedReader b) -> { try { return b.readLine(); } catch (IOException e) { throw new RuntimeException(e); } };
当Lambda表达式抛出一个异常时,throws语句也必须与Lambda所指类型相匹配。
如果Lambda的主体是一个语句表达式,它就和一个返回void的函数描述符兼容(当然需要参数列表也兼容)。
eg:尽管List的add方法返回的是boolean,但以下两行都是合法的:
//Predicate返回了一个boolean Predicate<String> p = s -> list.add(s); //Consumer返回了一个void Consumer<String> b = s -> list.add(s);
Lambda类型推断:Java编译器会从上下文(目标类型)中推断出用什么函数式接口来配合Lambda表达式,所以也能推断出适合Lambda的签名,因为函数描述符可以通过目标类型来得到。这样就可以在Lambda中省去标注参数类型,当参数只有一个时还可以省去参数的括号。
Lambda使用局部变量的限制:Lambda表达式对值封闭,而不是对变量封闭。Lambda表达式引用的局部变量必须是final的,只能有一次赋值。
这是因为实例变量是储存在堆中,而局部变量是储存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问他的副本而不是访问原始变量。
Lambda方法引用:
方法引用就是Lambda的快捷写法。目标引用放在分隔符::前面,方法的名称放在后面。
eg:
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 |
如何构建方法引用:
主要有三类:
-- 指向静态方法的放方法引用;
-- 指向任意类型实例方法的方法引用;
-- 指向现有对象的实例方法的方法引用。
Lambda构造函数引用:
对于一个现有构造函数,可以利用它的名称和关键字new来创建它的一个引用:ClassName::new。它的功能与静态方法的引用类似。
Lambda表达式 | 等价构造方法引用Lambda表达式 |
Supplier<Apple> c1 = Apple::new; Apple a1 = c1.get(); | Supplier<Apple> c1 = () -> new Apple(); Apple a1 = c1.get(); |
Function<Integer, Apple> c2 = Apple::new; Apple a2 = c2.apply(110); | Function<Integer, Apple> c2 = (weight) -> new Apple(weight); Apple a2 = c2.apply(110); |
BiFunction<String, Integer, Apple> c2 = Apple::new; Apple a2 = c2.apply("green", 110); | BiFunction<String, Integer, Apple> c2 = (color, weight) -> new Apple(color, weight); Apple a2 = c2.apply("green", 110); |
复合Lambda表达式的有用方法:
复合类型 | 方法 | 说明 | 举例 |
比较器复合 | reversed() | 逆序 | //按重量递减排序 inventory.sort(comparing(Apple::getWeight)) .reversed(); |
thenComparing | 比较器链(接受一个函数作为参数,如果两个对象用第一个Comparator比较之后是一样的,就提供第二个Comparator | //两个苹果一样重时按国家排序 inventory.sort(comparing(Apple::getWeight)) .reversed(). thenComparing(Apple::getCountry); | |
谓词复合 (and和or方法的优先级是按照在表达式链中的位置,从左向右确定的) | negate | 非 | //产生现有对象redApple的非 Predicate<Apple> notRedApple = redApple.negate(); |
and | 将两个Lambda用and组合起来 | //一个苹果既是红色又比较重(链接两个谓词来生成一个Predicate对象) Predicate<Apple> RedAndHeavyApple = redApple.and(a -> a.getWeight() > 150); | |
or | 要么 | //要么重(150g以上)的红苹果,要么是绿苹果 Predicate<Apple> RedAndHeavyApple = redApple.and(a -> a.getWeight() > 150) .or(a -> "green".equals(a.getColor())); | |
函数复合 | andThen | 先对输入应用一个给定函数,再对输出应用另一个函数 | //等同于数学上的g(f(x)),返回4 Function<Integer, Integer> f = x -> x + 1; Function<Integer, Integer> g = x -> x * 2; Function<Integer, Integer> h = f.andThen(g); int result = h.apply(1); |
compose | 先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果 | //等同于数学上的f(g(x)),返回3 Function<Integer, Integer> f = x -> x + 1; Function<Integer, Integer> g = x -> x * 2; Function<Integer, Integer> h = f.compose(g); int result = h.apply(1); |