Lambda表达式
出现背景-如果我中了五百万
- Java8之前的方法与类是强耦合的,一个函数必须与类进行关联。而lambda表达式出现改变了一切。让Java中的方法与类耦合度降低。看如下例子
/**
* lambda表达式出现的背景
*/
public class WhenIHave500W {
//女朋友
private Long myGirls = 0L;
private Long friends = 100L;
public static void main(String[] args) {
//拉斯维加斯走一遭
CostMoney gamble = new CostMoney() {
@Override
public long cost(long money) {
return money/2;
}
};
//投资实业
CostMoney investStock = new CostMoney() {
@Override
public long cost(long money) {
return money+1;
}
};
//买一套房
CostMoney investHouse = new CostMoney() {
@Override
public long cost(long money) {
return money*2;
}
};
RelationChange girlRelationChange = new RelationChange() {
@Override
public int relationChange(int relation) {
return relation+1;
}
};
RelationChange friendChange = new RelationChange() {
@Override
public int relationChange(int relation) {
return relation*2;
}
};
//现金500W元
WhenIHave500W dayliDream = new WhenIHave500W();
//投资房产
System.out.println("拉斯维加斯赌博资产:"+dayliDream.costMoney(500*10000,gamble));
System.out.println("投资实业资产:"+dayliDream.costMoney(500*10000,investStock));
System.out.println("炒房资产:"+dayliDream.costMoney(500*10000,investHouse));
System.out.println("女朋友+1:" + dayliDream.relationChange(dayliDream.myGirls, girlRelationChange));
System.out.println("朋友:" + dayliDream.relationChange(dayliDream.friends, friendChange));
System.out.println("别做梦了,还是996吧。。。");
}
long costMoney(long money,CostMoney costMoney){
return costMoney.cost(money);
}
long relationChange(Long relation,RelationChange relationChange){
return relationChange.relationChange(relation);
}
}
interface CostMoney {
long cost(long money);
}
interface RelationChange {
Long relationChange(Long relation);
}
运行结果显然买房才是正途。进一步如果你买房挣了五百万,你已经是千万富翁。所以你的人际关系会发生变化。亲戚朋友会乘以2,女朋友会+1。当然最后还是黄粱一梦。
但是这里有一个重点及时CostMoney接口实现investStock 与GirlRelationChange,investHouse 与friendsChange其实二者的算法是一样的,但是我们却要实现两个接口。这就是方法与类的耦合。他们实际使用的函数都是f(x) = x+1 与f(x) = x*2.
因此lambda函数在Java8中,将方法与类进行抽离。方法实现的类这一步有Java底层为我们实现。所以上述例子就可以改为
/**
* lambda表达式出现的背景
*/
public class WhenIHave500W {
//女朋友
private Long myGirls = 0L;
private Long friends = 100L;
public static void main(String[] args) {
//现金500W元
WhenIHave500W dayliDream = new WhenIHave500W();
//投资房产
System.out.println("拉斯维加斯赌博资产:"+dayliDream.costMoney(500*10000,x->x/2));
System.out.println("投资实业资产:"+dayliDream.costMoney(500*10000,x->x*2));
System.out.println("炒房资产:"+dayliDream.costMoney(500*10000,x->x+1));
System.out.println("女朋友+1:" + dayliDream.relationChange(dayliDream.myGirls, x->x+1));
System.out.println("朋友:" + dayliDream.relationChange(dayliDream.friends, x->x*2));
System.out.println("别做梦了,还是996吧。。。");
}
long costMoney(long money,CostMoney costMoney){
return costMoney.cost(money);
}
long relationChange(Long relation,RelationChange relationChange){
return relationChange.relationChange(relation);
}
}
interface CostMoney {
Long cost(Long money);
}
interface RelationChange {
Long relationChange(Long relation);
}
1. 表达式的语法
- (parameters) -> exression
- (parameters) -> {statement}
2. lambda使用地方
- 行为参数化
- 函数中将某个特定的操作行为进行参数化
- 使用函数是接口来传递行为
- 执行一个任务
- 传递lambda
- 函数描述符
- 函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。这种抽象方法叫做函数的描述符
- 比如runnable接口我们可以用函数描述符()->{}来表达,比如比较大小Comparator接口我们可以通过(int,int)->int表示比较器接口
- 在哪里可以使用lambda
- 当函数式接口的抽象方法与lambda表达式表达的函数签名相同的时候我们可以使用
runnable r = ()->{} //ok
runnable r = ()->{return 1;} //compile wrong
4.@FunctionnalInterface
- 在新的API中都会有一个@FunctionalInterface的注解,这个注解表达的就是该接口是一个函数式接口,如果你有超过两个抽象方法则会报错
3.Lambda使用时间
- 通过使用lambda表达式与行为参数化来使编程更简单
public class ProcessFile {
public static String processFile() throws IOException{
try(BufferedReader br = new BufferedReader(new FileReader("to.txt"))){
//1
return br.readLine();
}
}
public static String processFile2(FileProcessAble processAble) throws IOException{
try(BufferedReader br = new BufferedReader(new FileReader("to.txt"))){
//2
return processAble.processFile(br);
}
}
}
@FunctionalInterface
interface FileProcessAble {
//3
public String processFile(BufferedReader reader) throws IOException;
}
//4
public class MainApp {
public static void main(String[] args) {
try {
//只能够读第一行
System.out.println(ProcessFile.processFile());
//想读几行读几行
System.out.println(ProcessFile.processFile2((reader -> {
return reader.readLine() + ":" + reader.readLine();
})));
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用方法四步骤:
-
- 代码1处的br.readLine();需要被行为可自动化
-
- 代码2定义一个接口用来表示行为参数花
-
- 代码3重构为代码5处
-
- MainApp进行调用
预定义函数式接口
- jdk为我们预先定义好了常用的函数式接口。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
- 避免装箱的值类型操作
@FunctionalInterface
public interface IntPredicate {
/**
* Evaluates this predicate on the given argument.
*
* @param value the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(int value);
}
*/
@FunctionalInterface
public interface IntConsumer {
/**
* Performs this operation on the given argument.
*
* @param value the input argument
*/
void accept(int value);
}
函数描述符 | 函数式接口 |
---|---|
T->boolean | Predicate |
T->void | Consumer |
T->R | Function<T,R> |
()->T | Supplier |
T->T | UnaryOperator |
(T,T)->T | BinaryOperator |
(L,R)->boolean | BiPredicate<L,R> |
(T,U)->void | BiConsumer<T,U> |
(T,U)->R | BiFunction(T,U,R) |
labmda表达式如果抛出异常
- labmda表达式对应的函数式接口声明异常–找到签名
- labmda表达式中的statement抛出异常。–函数中直接抛出
再谈lambda表达式与函数式接口
- lambda表达式为函数式接口生成一个实现
- lambda表示式本身并不携带函数式接口中的信息,那么之间如何匹配的呢?通过以下代码例子来表达
package jdk8.three.five;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class HevierThanApp {
public List<Apple> filter(List<Apple> appleList, Predicate<Apple> filterAppOperator) {
ArrayList<Apple> filterList = new ArrayList<>();
appleList.forEach(apple -> {
if (filterAppOperator.test(apple)) {
filterList.add(apple);
}
});
return filterList;
}
public static void main(String[] args) {
HevierThanApp hevierThanApp = new HevierThanApp();
List<Apple> apples = new ArrayList<>();
List<Apple> filter = hevierThanApp.filter(apples, app -> app.getWeight() > 15);
}
}
class Apple {
private int weight;
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
- List filet = hevierThanApp.filet(apples, app -> app.getWeight() > 15);中labmda表达式的上下文是什么呢?是HevierThanApp中的filter方法
- 定位到filter(Listinventory,Predicate p),目标类型是Predicate
- 定位Predicate,再定位到public boolean test(Apple apple);
- 上一步骤中可获得函数描述符(Apple)->boolean,验证lambda表达式的函数表达式与目标大表示是否相同
public class SameSignatureProblem {
public void processString(String message, Consumer<String> consumer){
consumer.accept(message);
}
public void processString(String message, MyConsumer1 consumer1){
consumer1.consume(message);
}
public static void main(String[] args) {
SameSignatureProblem sameSignatureProblem = new SameSignatureProblem();
//编译报错
sameSignatureProblem.processString("hello",message -> System.out.println(message));
}
}
public interface MyConsumer1 {
public void consume(String message);
}
这种情况在注释处编译报错,但是定义方法处是能够重载的。
3. 一个lambda表达式可对应不同函数式接口
public static void main(String[] args) {
PrivilegedAction<Integer> privilegedAction = ()->3;
Callable<Integer> callable = ()->3;
}
需要强调的是void的特殊兼容规则,()-> expression,特殊规则:如果主体为一个语句表达式,则该表达式与void兼容。
ArrayList<String> list = new ArrayList<>();
Predicate<String> predicate = (msg)->list.add(msg);
Consumer<String> consumer = (msg)->list.add(msg);
以下代码不能够编译的原因,虽然lambda表达式是Object类型,但Object类型不是函数式接口无法相等
public void cantCompile(){
Object o = ()-> System.out.println("hello");
}
- lambda表示式是编译器通过目标类型推断出来的,那么lambda表达式的类型签名编译器便能够很好地推断出来,可以很好地省去标注参数类型。
Consumer<String> consumerNoParam = msg ->list.add(msg);`
- 变量捕获:lambda表达式可以捕获外部的变量,但是存在一定的限制。无限制捕获实例变量和静态标量(也就是在表达式所在的主体中),但局部变量必须显式地声明为final或事实上为final。所以如下代码不能够通过编译
public void localVarCompileFail(){
int name =0;
Runnable r = ()->{
try{
Thread.sleep(3000);
}catch(Exception e){
throw new RuntimeException(e);
}
System.out.println(name);
};
new Thread(r,"test1").start();
name=1;
}
lambda表达式对局部变量访问限制的思考:如上例中的代码,如果Runnable对象r交给test1线程执行,则name存在main线程栈上,很可能main线程栈已经被销毁了,所以r对象执行的时候访问的name为原name的副本。因此只有局部变量name不能够修改,才能够与副本保持一致。所以有了这个限制
6. 闭包思考:closure的含义就是一个函数实例可以无限制访问定义它的上下文的变量,java8lambda表达式也可以做类似的事情,但是lambda表达式是对值封闭,并不对变量封闭,造成这样地原因是堆区变量线程间共享,但线程上的栈变量仅与栈生命周期相关,如果允许捕获局部变量就会引起线程间不安全的新可能性。
方法引用与lambda表达式
- lambda表达式:表达的是行为参数化地载体,通过lambda表示式来向编译器描述具体行为。
- 方法引用:根据已有的方法来创建lambda表达式。
方法引用
- 特定的lambda表达式的语法糖,阅读简单。
- 特定lambda表达式指的是statement中仅有一个方法调用,而方法调用分为静态方法调用,实例方法调用,局部变量方法调用
- 重点解释局部变量方法调用:指的lambda表达式中方法调用的主体是外部的局部变量。接下来分别使用代码演示三类型方法引用
//静态方法
Function<String,Integer> function = number->Integer.parseInt(number);
Function<String,Integer> function2 = Integer::parseInt;
//实例引用
Function<String,Integer> function3 = msg ->msg.length();
Function<String,Integer> function4 = String::length;
//局部变量引用
String message ="test";
Supplier<Integer> supplier = ()->message.length();
Supplier<Integer> supplier2 = message::length;
- 构造函数-特殊的静态方法引用:调用形式为ClassName::new;如下为调用例子,例子中就会返回一个长度为空的字符串
Supplier<String> supplier3 = String::new;
- 方法引用思维导图
lambda表达式发展过程
//类实现
class AppleComparator implements Comparator<Apple>{
@Override
public int compare(Apple o1, Apple o2) {
return o1.weight.compareTo(o2.weight);
}
}
inventory.sort(new AppleComparator());
//匿名类实现
inventory.sort(new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return o1.weight.compareTo(o2.weight);
}
});
//labmda表达式
inventory.sort((Apple o1, Apple o2)->{return o1.weight.compareTo(o2.weight);});
//Comparator的静态方法
inventory.sort(Comparator.comparing(o1->o1.weight));
//方法引用
inventory.sort(Comparator.comparing(Apple::getWeight));
- 类实现与匿名内部类的实现缺点:冗长,策略变动修改麻烦,重复模板代码过多
- lambda表达式出现的好处:将重复模板代码交给编译器去根据上下文获得,并且程序员角度类传递行为改为从方法传递行为,抽象层级上升。
- 方法引用出现的好处:代码可读性进一步增加
Java中默认方法是什么
- 接口中允许给出该方法的默认实现,与类不同。接口实现的方法是无状态方法,没有成员变量。在方法名前面添加default关键字
- 接口中可以存在静态方法并且默认实现
Java中默认方法出现的背景
- 类库接口中增加提供某个方法,用户使用该接口不用额外实现方法,导致变动量过大,能够平滑升级
函数式接口中的默认方法
- 因为函数式接口是只包含一个抽象方法的接口,默认方法有实现可以放入函数式接口中。该接口仍然可以称为函数式接口。
默认方法的使用方式
- 可选方法,即某个类继承自父类,但是父类中某个特定抽象方法并不需要实现,但是根据规则子类仍然要继承抽象方法并实现空方法。此时默认方法就可以提供一个可选方法。方便子节点根据需要是否实现该方法。如Java 8中Iterator的remove方法
default void remove() {
throw new UnsupportedOperationException("remove");
}
- 行为的多继承:可以继承来自多个接口的默认实现方法。从而允许java的多继承方法
三点冲突规则
- 继承父接口的默认方法实现,如果多个接口、父类中有同样的方法,那么该子类中的该方法会使用哪个方法呢?
- 类的方法优先级最高,继承类实现的方法优先级高于任何默认方法(类可以看做不是默认方法)
- 接口中,谁最具体谁优先级更高。如果B接口继承了A接口,B中提供的实现方法优先级就比A高
- 无法判断需要自身实现,并显示说明。
- 三条规则本质上是谁具体就选择谁。如下用代码演示三条规则
- 预先定义以下接口与类
//接口1
package jdk8.nine;
public interface Interface1 {
default public void sayHello(){
System.out.println("Interface1");
}
}
package jdk8.nine;
//接口2
public interface Interface2 {
default public void sayHello(){
System.out.println("Interface2");
}
}
package jdk8.nine;
//接口3
public interface Interface3 extends Interface1 {
default public void sayHello(){
System.out.println("Interface3");
}
}
package jdk8.nine;
//类1
public class Class1 {
public void sayHello(){
System.out.println("Class1");
}
}
public class Class2 implements Interface1{
}
- 规则一:类的实现优先级最高
//打印结果为:Class1
public class ResolveConflictMultpExtend {
public static void main(String[] args) {
ClassMaxLevel classMaxLevel = new ClassMaxLevel();
classMaxLevel.sayHello();
}
}
class ClassMaxLevel extends Class1 implements Interface1,Interface2{
}
需要注意的是类必须要实现,如果类集成自父接口的默认方法,则规则一并不符合。如如下例子中
class ClassNotMaxLevel extends Class2 implements Interface2{
}
编译器输出结果为:Error:(14, 1) java: 类 jdk8.nine.ClassNotMaxLevel从类型 jdk8.nine.Interface2 和 jdk8.nine.Interface1 中继承了sayHello() 的不相关默认值。因为类优先级最高必须是类中声明的。
- 规则二:更具体的接口优先级更高。即同一个方法前面谁的深度更高则使用哪个接口,如例子所示:
class ClassInterfaceDeepMax implements Interface1,Interface3{
}
public class ResolveConflictMultpExtend {
public static void main(String[] args) {
//输出接口为Interface3
ClassInterfaceDeepMax classInterfaceDeepMax = new ClassInterfaceDeepMax();
classInterfaceDeepMax.sayHello();
}
}
需要注意的是如下接口会报错误:
class ClassInterfaceNotDeepMax implements Interface1,Interface2,Interface3{
}
Error:(24, 1) java: 类 jdk8.nine.ClassInterfaceNotDeepMax从类型
jdk8.nine.Interface2 和 jdk8.nine.Interface3 中继承了sayHello() 的不相关默认值
更具体指的是接口之间是具有继承关系,如果不具有继承关系,二者是无法区分的
- 显式指定,如果在上述的情况中编译错误我们需要指定使用某个特定的接口该如何做呢,显式指定
class ClassInterfaceNotDeepMax implements Interface1,Interface2,Interface3{
@Override
public void sayHello() {
Interface3.super.sayHello();
}
}
利用接口名.super()调用指定接口的方法。
复合lambda表达的原理
- 接口中利用默认方法,提供供更过的功能。这些功能主要任然是基于抽象方法的基础上实现。
Comparator中的复合方法
- 抽象方法
int compare(T o1, T o2);
- 逆序
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
- 比较器链
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
调用自身进行比较,如果等于0则用other的比较器进行比较
3. 调用
public static void main(String[] args) {
List<Apple> appleList = new ArrayList<Apple>();
appleList.sort(Comparator.comparing(Apple::getWeight).reversed().thenComparing(Apple::getPrice));
}
- 思考
- 比较器接口复合方法类似于创造一个新的lambda表达式,利用传入的参数构造一个新的比较器
谓词符合
- negate
default Predicate<T> negate() {
return (t) -> !test(t);
}
生成新的谓词对象,这个对象在原来的结果上取反
2. and
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
生成新的微词对象,这个微词对象在传入谓词两者都为成功
3. or
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
函数符合
- andThen函数,结果传入后面的参数。类似函数h(x)=g(f(x)).
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
- compose函数,先使用参数中的apply。类似函数h(x)=f(g(x)).
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
- 例子
public static void main(String[] args) {
//f(x) = x+2;
Function<Integer,Integer> f1 = (x)->x+2;
//g(x) = x*2;
Function<Integer,Integer> f2 = (x)->x*2;
//h(x) = g(f(x))
Function<Integer,Integer> f3 = f1.andThen(f2);
//h(x) = f(g(x))
Function<Integer,Integer> f4 = f1.compose(f2);
//结果为4
System.out.println(f1.apply(2));
//结果为4
System.out.println(f2.apply(2));
//结果为8
System.out.println(f3.apply(2));
//结果为6
System.out.println(f4.apply(2));
}