Lambda表达式简介
lambda表达式可以被理解为一个匿名函数的简明表示,它可以被传递,它没有名称,但是它有一个参数列表,一个主体,一个返回类型,也可能是可以抛出的异常列表。
- 匿名 因为它没有一个明确的名字,不像一个方法,必须有方法名。
- 函数 因为lambda不是属于某一个特定类的方法,但它和方法一样,lambda有一个参数列表,一个主体,一个返回类型,以及可能抛出的异常。
- 传递 一个lambda表达式可以作为参数传递给一个方法或存储在一个变量。
- 简洁 你不需要写很多冗长的代码,就是比写匿名内部类还简洁。
在使用Java 8以前,创建一个比较器,一般都会采用内部类方式:
Comparator<Apple> byWeight = new Comparator<Apple>() { public int compare(Apple a1, Apple a2) { return a1.getWeight().compareTo(a2.getWeight()); } };
使用lambda表达式之后的代码,只需一行代码。
Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
lambda表达式的语法结构
lambda表达式由参数、箭头、主体,这三部分构成。
- 参数 跟一个方法的参数类似。
- 箭头 由参数列表指向函数主体。
- 主体 函数的具体运算操作。
lambda表达式示例
- (parameters) -> expression 是否有返回值取决于后面expression的返回值。
- (parameters) -> { statements; } 多个语句需要用{}包含。
- (parameters) -> { statements;return 23 } 用{}包含的语句,如果有返回值,需要显示return指定。
正确的表达式
(String s) -> s.length() 这个表达式有一个String的参数s,返回int,lambda表达式隐藏的return返回语句;
(Apple a) -> a.getWeight() > 150 这个表达式有一个参数Apple,返回boolean
(int x,int y) -> {System.out.println(“result:”),System.out.println(x+y)} 这个表达式有两个int型参数,没有返回值(或返回void) ,lambda表达式可以包含多个语句,但必须用{}大括号包含在里面。
() -> 42 这个表达式没有参数,但返回int型
错误的表达式
(int i) -> return “result:” + i; 正确写法(int i) -> {return “result:” + i;}
(String s) -> {“zhang” + s;} 正确写法(String s) -> {return “zhang” + s;}或者(String s) -> “zhang” + s
精简写法
参数类型可以省略,例如:Comparator<apple> c = (a1,a2) -> a1.getWeight().compareTo(a2.getWeight());
当一个lambda表达式只有一个参数时,参数类型可以省略,括号也可以省略。
例如List<Apple> greenApples = filter(inventory,a - > "green".eqauls(a.getColor()));
函数式接口
所谓函数式接口,就是某个接口,它只有一个抽象方法。采用lambda表达式的作用,相当于该接口的唯一一个抽象方法的实现。单一抽象方法的接口,在JDK很常见,常用的有:
public interface Comparator<t> { int compare(T o1,T o2); } public interface Runnable { void run(); } public interface EventListener { void actionPerformed(ActionEvent e); }
@FunctionalInterface注解
Java 8 专门为函数式编程,新增了java.util.function包的一些接口类,如果查看这些接口类的源码,会注意到这些函数式接口都是带@FunctionalInterface注解,此注解用于指示该接口为函数式接口。如果你定义了一个接口加上了@FunctionalInterface注解,但是接口里有多个抽象方法,这时候编译器在编译的时候就会报错。
Java 8里的函数式接口
Java 8已经提供大量的函数式接口,在写lambda表达式时,经常会碰到,这表达式要怎么写,就要先了解对应的函数式接口里方法的参数和返回值。
Predicate
@FunctionalInterface public interface Predicate<T> { boolean test(T t); }
Predicate接口里的test方法,参数是T,返回boolean型,使用场景Stream的filter(Predicate<? super T> predicate)
Consumer
@FunctionalInterface public interface Consumer<T> { void accept(T t); }
Consumer接口的accept方法,参数是T,返回void,使用场景Iterable的forEach(Consumer<? super T> action)
Function
@FunctionalInterface public interface Function<T, R> { R apply(T t); }
Function接口的apply方法,参数是T,返回值R,使用场景Stream里的map(Function<? super T, ? extends R> mapper),flapMap(Function<? super T, ? extends R> mapper)
方法引用
前面lambda表达式简介里,介绍了lambda表达式是一个匿名的函数,可以被当做参数传递给调用方。如果传给调用方的这个参数不再是lambda表达式,而是源自于某个类的方法,这时就称之为方法引用。
例如用lambda表示比较器:
Arrays.sort(list,(Apple a,Apple b) -> a.getWeight().compareTo(b.getWeight()));
采用方法引用的写法:
Arrays.sort(list,Comparator.comparing(Apple::getWeight))
打开Comparator的comparing源码可以看到,这个方法里已经实现了的比较器的判断规则,只不过这个规则是根据传入的函数(或方法)的返回值,做大小比较。
public static <T, U extends Comparable<? super U>> Comparator<T> comparing( Function<? super T, ? extends U> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)); }
方法引用的三种方式
方法引用静态方法,例如,Integer的parseInt(),写成Integer::parseInt。
一个方法引用一个任意类型的实例方法,例如String的length((),写成String::length。
方法引用现有对象的实例方法,例如有个本地变量obj,它有个getValue()的方法,写成obj::getValue
上图中需要注意的是第二点,lambda的第一个参数是,是方法调用的实例对象,与之对应的方法引用,会排除第一个参数,也就是arg0会忽略掉,只有rest作为参数。
Function<String, Integer> stringToInteger =(String s) -> Integer.parseInt(s);,对应的方法引用是Integer::parseInt
BiPredicate<List<String>, String> contains = (list, element) -> list.contains(element);这个lambda表达式里,用第一个参数的实例对象去调用contains方法,属于方法引用的第二种方式,所以对应的方法引用是 List::contains。这里主要的混淆点是contains()方法的参数只有一个,但因为这个方法属于String的实例方法(而不是类的静态方法),隐含了第一个参数。
构造方法引用
类的构造方法也可以被引用,使用语法是ClassName::new。例如:
Supplier<Apple> c1 = App::new; App a1 = c1.get(()
这个语句引用的是无参构造方法,也可以引用有参构造方法:
BiFunction<String,Integer,Apple> c3 = Apple::new; App a1 = c3.apply("gree",110)
这里的构造方法Apple(String color,Integer weight)有两个参数。
参考资料
- 《Java 8 in Action》