lambda表达式
-
理解(行为参数化)
lambda表达式是一个函数式接口的实现,可以将函数作为方法参数,或者将代码作为变量看待。
-
函数式接口
函数式接口就是只定义一个抽象方法的接口(但可以有零个或多个default,static方法)。通过注解
@FunctionalInterface
来声明这是一个函数式接口,这个注解不是必需的,但对于为此设计的接口而言,使用它是比较好的做法。 -
语法
(parameters) -> expression
或(注意语句的花括号)(parameters) -> { statements; }
-
示例
/** 定义一个函数式接口 */ @FunctionalInterface public interface MyLambdaInterface<T> { void doSomething(T t); } public class Test { static <T> void func(T t, MyLambdaInterface lambda) { lambda.doSomething(t); } public static void main(String[] args) { /** 这里可以理解lambda表达式是一个函数式接口的实例 */ MyLambdaInterface lambda = s -> System.out.println(s); func("Hello World", lambda); /** 直接作为参数传递 */ func(10.0, s -> System.out.println(s)); } }
-
更多函数式接口
除了常见的只有一个抽象方法的接口(如 Comparable,Runnable,Callable等),Java 8 在java.util.function 包中引入了几个新的函数式接口。
-
Predicate<T>
@FunctionalInterface public interface Predicate<T> { /** 接受泛型 T 的对象,返回 boolean 类型 * 函数描述符:T -> boolean */ boolean test(T t); /** 其他 default,static 方法 */ }
-
Consumer<T>
@FunctionalInterface public interface Consumer<T> { /** 接受泛型 T 的对象,返回 void 类型 * 函数描述符:T -> void */ void accept(T t); /** 其他 default,static 方法 */ }
-
Function<T, R>
接口定义了一个叫作apply的方法,接受一个泛型T的对象,返回一个泛型R的对象。函数描述符:T -> R
-
原始类型特化
Java 8 为前面描述的函数式接口带来专门的版本,以便在输入和输出都是基础类型使避免自动装箱的操作。
public interface IntPredicate { boolean test(int t); } IntPredicate evenNumbers = (int i) -> i % 2 == 0; evenNumbers.test(1000); // 无装箱 Predicate<Integer> oddNumbers = (Integer i) -> i % 2 == 1; oddNumbers.test(1000); // 有装箱
这样,DoublePredicate、IntConsumer、IntFunction、ToIntFunction<T>、IntToDoubleFunction等都不难理解了
-
-
类型检查、类型推断及限制
-
lambda表达式本身并不包含它在实现哪个函数式接口的信息。所以同一个lambda表达式可以与不同的函数式接口联系起来,只要它们的抽象方法签名能够兼容。
/** 比如 Callable 和 PrivilegedAction 的唯一抽象方法的签名都是 () -> T * 所以下面两个赋值都是有效的 */ Callable<Integer> c = () -> 42; PrivilegedAction<Integer> p = () -> 42;
-
Java编译器会推断出lambda表达式的参数类型
// 没有类型推断 Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight); // 类型推断 Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight);
-
使用局部变量
lambda表达式允许使用自由变量(不是参数,而是在外层作用域中定义的变量),就像匿名类一样。 它们被称作lambda捕获。
lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量。但局部变量必须显式声明为final,或事实上是final。换句话说,Lambda表达式只能捕获指派给它们的局部变量一次(注:捕获实例变量可以被看作捕获最终局部变量this)。
-
-
方法引用
方法引用就是让你根据已有的方法实现来创建lambda表达式,可以把方法引用看作仅仅涉及单一方法的lambda语法糖。
方法引用主要有4类:
指向静态方法的方法引用
指向任意类型实例方法的方法引用
指向现有对象的实例方法的方法引用
-
构造函数的引用
/** 指向静态方法的方法引用 */ Function<String, Integer> f1 = Integer::parseInt; int res = f1.apply("123"); System.out.println(res); /** 指向任意类型实例方法的方法引用 */ BiFunction<String, Integer, Character> f2 = String::charAt; char c1 = f2.apply("hello", 2); System.out.println(c1); /** 指向现有对象的实例方法的方法引用 */ String s = new String("hello"); Function<Integer, Character> f3 = s::charAt; char c2 = f3.apply(4); System.out.println(c2); /** 构造函数的引用 */ Function<String, String> stringFunction = String::new; String string = stringFunction.apply("hello"); System.out.println(string);
相关资料
1.《Java 8 实战》