1.lambda的语法
2.在哪里如何使用lambda
3.环绕执行模式.
4.函数式接口,类型推断
5.方法引用
6.Lambda复合
Lambda表达式 介绍
可以简单理解简洁的表示可传递的匿名函数的一种表现:它没有名称,有参数列表函数主体,
返回类型,还有可能有异常类列表.
匿名: 没有像方法那样有名称:写的少想得多.
函数: 因为Lambda表达式不像方法那样属于某个特定的类,但和方法一样,有参数列表,函数主体,
返回类型.还有可能有异常列表
传递: Lambda表达式作为参数传递给方法或者存储在变量中.
简介: 无需向匿名类一样写很多模板代码
先前不同比较代码:
Collections.sort(appleList, new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
//根据重量升序
return o1.getHeavy().compareTo(o2.getHeavy());
}
});
参数列表: 这里采用的Comparator中的compare方法的参数,两个apple
箭头: 箭头->把参数列表和Lambda表达式主体隔开
Lambda主体: 比较两者的重量,表达式就是Lambda的返回值.
Lambda表达式的五种例子
//1.具有一个String类型的参数,返回一个int类型. 隐匿了return
(String s) -> s.length();
//2.直接返回一个int值
()->33
//3.Lambda表达式中有一个Apple 实体类类型.返回一个boolean值.苹果重量大于100
(Apple a) -> a.getHeavy()>100;
//4.Lambda表达式中有int类型,两个参数. 里面可以多行代码.void类型没有返回值
(int x,int y)->{
System.out.println(x);
System.out.println(x+y);
};
//5.Lambda表达式中有两个Apple类型. 根据两个类型的重量进行升序排序
(Apple a1, Apple a2) -> a1.getHeavy().compareTo(a2.getHeavy());
函数式接口
只定义了一个抽象方法的接口
public interface Predicate<T>{
boolean test(T t)
}
也可以在类上添加注解 @FunctionalInterface 注解,如果有多个抽象方法就会报“Multiple non-overriding
abstract methods found in interface Foo”
Lambda表达式如何检查类型
编译器如何检查Lambda在给定上下文中是否有效 ;
暂时知道:Lambda表达式可以赋给一个变量.或传递给一个接收函数式接口作为参数的方法(Lambda表达式的签名要和函数式抽象接口方法一样) ; 签名:表示的是函数式抽象接口的方法名称
java8函数式接口类增加了三个
Predicate.java
例:
@FunctionalInterface
public interface Predicate<T>{
boolean test(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;
}
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
详细请看javadoc文档 等下还是会继续晚上
Consumer.java
java.util.function.Consumer<T>定义了一个名叫accept()方抽象方法,它接收泛型T的对象.没有返回(void),你如果需要访问T型的对象,并对其进行一些操作可以使用该方法.
@FunctionalInterface
public interface Consumer<T>{
void accept(T t);
}
public static void foreach(List<T> list,Consumer<T> s){
for(T s:list){
s.accept((s);
}
}
//例如可以对其进行遍历 foreach
foreach(arrays.asList(1,2,3,4),(Integer i)->system.out.println(i));
Function.java
java.util.function.Function<T,R>定义了一个叫apply的方法,它接收一个泛型类型T的对象,返回一个泛型R的对象
@FunctionalInterface
public interface Function<T,R>{
R apply(T t);
}
//定义抽象方法实现
public static <T,R> List<R> map(List<T> list,Function<T,R> f){
List<R> rList=new ArrayList();
for(T t: list){
f.apply(t);
rList.add(t);
}
return rList;
}
//我们可以测试之前的例子,根据苹果的重量测试
map(Arrays.asList(new Apple("green",120),new Apple("blue",111),new Apple("green",15)),(Apple a)->a.getHeavy>100;
Java 8中的常用函数式接口
函数式接口
| 函数描述符
| 原始类型特化 |
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 | ObjIntConsumer<T>, ObjLongConsumer<T>,ObjDoubleConsumer<T> |
BiFunction<T,U,R>
| (T,U)->R | ToIntBiFunction<T,U>, ToLongBiFunction<T,U>, ToDoubleBiFunction<T,U> |
(T,U) -> R的表达方式展示了应当如何思考 一个函数描述符。表的左侧代表了参数类型。这里它代表一个函数,具有两个参数,分别为泛型 T和U,返回类型为R。
Lambdas及函数式接口的例子
使用案例 | Lambda表达式 | 对应的函数式接口 |
布尔表达式 | (List<String> list) -> list.isEmpty() | Predicate<List<String>> |
创建对象 | () -> new Apple(10) | Supplier<Apple>
|
消费一个对象 | (Apple a) -> System.out.println(a.getWeight()) | Consumer<Apple> |
从一个对象中 选择/提取 | (String s) -> s.length() | Function<String, Integer>或 ToIntFunction<String> |
合并两个值 | (int a, int b) -> a * b | IntBinaryOperator |
比较两个对象 | (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()) | Comparator<Apple>或 BiFunction<Apple, Apple, Integer> 或 ToIntBiFunction<Apple, Apple> |
Lambda表达式检查类型
目标类型
Lambda表达式的类型是从使用Lambda的上下文推断出来的,上下文(比如,接受它传递的参数,或接受它的值的局部变量),中lambda表达式需要的类型
解读Lambda表达式的类型检查过程
首先找出filterApple方法说明
第二 要求它是Predicate<Apple>(目标类型)对象的第二个正式参数
第三 Predicate<Apple>是一个函数式接口,定义了一个叫test()方法
第四步 test方法描述了一个函数描述符,它可以接受一个泛型T的参数,并返回一个boolean
第五步 filterApple方法的任何参数都必须匹配这个要求.
如果都符合,那么lambda表达式就成立
同样的Lambda ,不同的函数式接口
有了目标类型的概念,同一个Lambda表达式就可以和不同的函数式接口联系起来,只要他们的抽象方法签名能够兼容 比如Callable 和PrivilegeAction这两个接口表示什么也不接受而且一个泛型T的函数.
//目标类型是Callable<Integer>
Callable<Integer> c = ()->11
//目标类型是PrivilegeAction<Integer>
PrivilegeAction<Integer> p =()->22
特殊的Void兼容规则
如果Lambda表达式的主体是一个语句表达式,它就和一个返回void的函数式描述符兼容(当然需要参数列表也要兼容) 下面两个语句是正确的
// Predicate返回了一个boolean
Predicate<String> p = s -> list.add(s);
// Consumer返回了一个void
Consumer<String> b = s -> list.add(s);
例子:检查下面这个句子是否正常执行.
Object o = ()->system.out.println("Hello Lambda!");
答案: 目标类 Object不是函数式接口,所以不能正常执行.
类型推断
java编译器会通过上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式.这意味着它也可以推断出适合Lambda表达式的签名,因为函数式描述符可以通过目标类型来得到.这样做的好处,就是编译器可以了解Lambda表达式的参数类型.这样就可以在Lambda语法中省去标注参数.
//没有类型推断
List<Apple> greenApples = filter(inventory, a -> "green".equals(a.getColor()));
// 参数a 没有类型推断
Comparator<Apple> c =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
//有类型推断
Comparator<Apple> c =
(a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
局部变量
Lambda表达式允许使用自由变量(不是参数,而是在外层域中定义的变量)就像匿名类一样.它们被称作捕获Lambda
int num = 11
Runable r = ()->system.out.println(num);
Lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量.但局部变量必须显示声明为final,或者事实上的final.Lambda表达式中只允许局部变量指派一次, 局部变量如果被改变,那么Lambda就会报错.
int num = 11
Runable r = ()->system.out.println(num);
num=22;
//报错
对局部变量的限制
第一,实例变量和局部变量背后的实现有一
个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局
部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线
程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问它
的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了
这个限制。
第二,这一限制不鼓励你使用改变外部变量的典型命令式编程模式(我们会在以后的各章中
解释,这种模式会阻碍很容易做到的并行处理)。
方法引用
方法引用可以让你重复的使用现有的方法定义.并想Lambda一样传递它们.在一些情况下比起Lambda更容易让人理解 . 讲真.方法引用如果没有一定的使用相信你看过之后.会一脸蒙蔽.有些真难理解.
之前我们使用的排序
list.sort((Apple a1,Apple a2)->a1.getHeavy.compareTo(a1.getHeavy);
使用方法引用:
list.sort(comparing::getHeavy)
看了是不是一脸懵逼.comparing哪里来的呃.
----------------------
你为什么应该关心方法引用?方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷
写法。它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称
来调用它,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建
Lambda表达式。但是,显式地指明方法的名称,你的代码的可读性会更好。它是如何工作的呢?
当你需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面。例如,
Apple::getWeight就是引用了Apple类中定义的方法getWeight。请记住,不需要括号,因为
你没有实际调用这个方法。方法引用就是Lambda表达式(Apple a) -> a.getWeight()的快捷
写法。
(Apple a)->a.getHeavy ---------------------> Apple::getHeavy
()->Thread.currentThread.dumpStack() -------------------->Thread.currentThread()::dumpStack
(str,i)->str.substring(i) ----------->String::substring
(String s)->system.out.println(s)--------------->system.out::println
如何构建方法引用
静态方法的引用
例如 Integer的parseInt方法,写作Integer::parseInt
任意类型实例方法的方法引用
例如String的length方法,写作String::length
现有对象的实例方法的方法引用
例如: 假设你有个局部变量expension 存放Transaction对象. 现有方法getValue 那么写作 expension::getValue
Lambda表达式
()->expensiveTransaction.getValue()可以写作expensiveTransaction::getValue。
构造函数引用
对于一个现有的构造函数,你可以利用它的名称和new来构造函数引用.ClassName::new (类名称::new)构造新对象
它的功能与指向静态方法的方法引用类似,它适合Supplier的签名()->Apple
例子:
//默认构造
Supplier<Apple> a =Apple::new;
Apple a1 = a.get();
----
//相当于Lambda
Supplier<Apple> a2=()->new Apple();
Apple a3=a2.get();
------------------------------
//构造函数有一个参数的话 使用 Function<T,R>
Function<Integer,Apple> ap1= Apple::new;
Apple ap2=ap1.apply(1);
//构造函数有两个参数的话 使用BiFunction<T,T,R>
BiFunction<Integer,String,Apple> ap2=Apple::new
Apple ap3=ap2.apply(1,"a")
//如果三个参数,那么我们就必须自己创建函数式接口了
public interface TrcFunction<T,U,M,R>{
R apply(T t,U u,M m)
}
TrcFunction<Integer,String,Integer,Apple> ap3=Apple::new
Apple ap4=ap3.apply(1,"1",2)
Lambda 和方法引用实战
用不同的排序策略给Apple列表排序
实现最终目标 : list.sort(compring::getHeavy)
第一步 传递代码
public class AppleComparator implements Comparator<Apple>{
public int compare(Apple a1,Apple a2){
return a1.getHeavy.compareTo(a2.getHeavy);
}
list.sort(new AppleComparator())
}
第二步 使用匿名类
list.sort(new Comparator<Apple>{
public int compare(Apple a1,Apple a2){
return a1.getHeavy.compareTo(a2.getHeavy);
}
});
第三步 使用Lambda表达式
在需要函数式接口的地方可以使用Lambda表达式。我们
回顾一下:函数式接口就是仅仅定义一个抽象方法的接口。抽象方法的签名(称为函数描述符)
描述了Lambda表达式的签名。在这个例子里,Comparator代表了函数描述符(T, T) -> int。
list.sort((a1,a2)->a1.getHeavy.compareTo(a2.getHeavy));
Comparator具有一个叫作comparing的静态辅助方法,
它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象
list.sort(comparing((a)->a.getHeavy));
第四步 使用方法引用
方法引用就是替代那些转发参数的Lambda表达式的语法糖。
list.sort(comparing(Apple::getHeavy));