应该在什么地方使用Lambda表达式
你可以在函数式的接口上使用Lambda表达式。
什么是函数式接口?
接口只定义了一个抽象方法,那么该接口就是函数式接口(Java8中接口还可以拥有默认方法,哪怕有多个默认方法,但是只要只定义了一个抽象方法,那么它就是一个函数式接口)。
Lambda表达式的构成
Lambda表达式分三个部分组成,Lambda表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并且把整个表达式作为函数式接口的实例,表达式构成如下:
- 参数列表
- 箭头(->)
- Lambda主体(类似匿名函数的实现)
如:
//函数式接口
public interface Predicate<T> {
boolean test(T t);
}
//Lambda例子
Predicate<Apple> s = (Apple a) -> a.getWeight() > 100;
Apple a = new Apple("red", 150);
System.out.println(s.test(a));
//实体
public class Apple {
private String coler;
private int weight;
//省略构造函数和getter、setter方法
}
值得注意的是(Lambda表达式必须和函数式接口的唯一抽象方法的签名一致):
- Lambda表达式的参数列表必须和对应的函数式接口的抽象方法的参数列表一致
- Lambda表达式的主体的返回值必须和对应的函数式接口的抽象方法的返回值一致
@FunctionInterface注解
此注解用于表示该接口会被设计为一个函数式接口,如果使用此注解但是接口中超过一个抽象方法,编译器将会返回一个提示原因的错误。
Java8一些内建的函数式接口
- java.util.function.Predicate<T>
只有一个 boolean test<T t>方法,接收参数类型T,返回布尔值。用法如下:
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;
}
public static void main(String[] args) {
List<String> listOfStrings = new ArrayList<>();
listOfStrings.add("ssss");
listOfStrings.add("zjc");
listOfStrings.add("");
//Predicate的lambda主体用于判断字符串是否为空
List<String> noEmpty = filter(listOfStrings, s -> !s.isEmpty());
System.out.println(noEmpty.toString());
}
2.java.util.function.Consumer<T>
它接受泛型T的对象,没有返回(void)。
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));
3.java.util.function.Function<T, R>
定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象
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<Integer> l = map(Arrays.asList("lambdas","in","action"),(String s) -> s.length());
4.另外还有一些原始类型特化DoublePredicate、IntConsumer、LongBinaryOperator、IntFunction等
Lambda的类型检查、类型推断和限制
类型检查
Lambda的类型是从使用Lambda的上下文推断出来的,上下文(如接受它传递的方法的参数或者接受它的值的局部变量)中Lambda的表达式需要的类型称为目标类型。
类型检查的过程大致分解:
- 首先,你要找出filter方法的声明。
- 第二,要求它是Predicate<Apple>(目标类型)对象的第二个正式参数。
- 第三,Predicate<Apple>是一个函数式接口,定义了一个叫作test的抽象方法。
- 第四,test方法描述了一个函数描述符,它可以接受一个Apple,并返回一个boolean。
- 最后,filter的任何实际参数都必须匹配这个要求。
如果Lambda表达式抛出一个异常,那么抽象方法所声明的throws语句必须与之匹配。
如果一个Lambda的主体是一个语句表达式, 它就和一个返回void的函数描述符兼容(当
然需要参数列表也兼容。
如下图:
类型推断
Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来得到。
如下图:
对局部变量的限制
局部变量必须显式声明为final或事实上是final
方法引用
格式:目标引用::方法的名称
方法引用的三种类型:
- 指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)
- 指向任意类型实例方法的方法引用( 例如String 的length 方法, 写作String::length)
- 指向现有对象的实例方法的方法引用(假设你有一个局部变量expensiveTransaction用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)
</br>
例子:
Lambda | 等效的方法引用 |
---|---|
(Apple a)-> a.getWeight() | Apple::getWeight |
( ) -> Thread.currentThread( ).dumpStack( ) | Thread.currentThread( )::dumpStack( ) |
(str,i)-> str.substring(i) | String::substring |
</br>例子图示:
Lambda和方法引用最佳实践
1.传递代码
public static void main(String[] args){
List<Apple> inventory = new ArrayList<>();
//...inventory.add(new Apple(...))
inventory.sort(new AppleComparator());
}
public static class AppleComparator implements Comparator<Apple>{
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
}
2.使用匿名类
inventory.sort(new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
});
3.使用Lambda表达式
inventory.sort((o1, o2) -> o1.getWeight().compareTo(o2.getWeight()));
//或者使用比较器的静态方法进一步简化:
inventory.sort(Comparator.comparing((a) -> a.getWeight()));
4.使用方法引用
inventory.sort(Comparator.comparing(Apple::getWeight));
复合Lambda表达式
以下使用的都是接口的默认方法或者说是非抽象方法
1.比较器复合
2.谓词复合
谓词接口包括三个方法:negate、and和or,让你可以重用已有的Predicate来创建更复杂的谓词。
3.函数复合
把Function接口所代表的Lambda表达式复合起来。Function接口为此配了andThen和compose两个默认方法,它们都会返回Function的一个实例。
参考资料: 《Java 8 in action》
End on 2017-5-13 11:16.
Help yourselves!
我是throwable,在广州奋斗,白天上班,晚上和双休不定时加班,晚上有空坚持写下博客。
希望我的文章能够给你带来收获,共勉。