Lambda表达式 学习-----java8实战

1.lambda的语法

2.在哪里如何使用lambda

3.环绕执行模式.

4.函数式接口,类型推断

5.方法引用

6.Lambda复合

 

Lambda表达式 介绍

可以简单理解简洁的表示可传递的匿名函数的一种表现:它没有名称,有参数列表函数主体,

  返回类型,还有可能有异常类列表.

匿名: 没有像方法那样有名称:写的少想得多.

函数: 因为Lambda表达式不像方法那样属于某个特定的类,但和方法一样,有参数列表,函数主体,

  返回类型.还有可能有异常列表

传递: Lambda表达式作为参数传递给方法或者存储在变量中.

简介: 无需向匿名类一样写很多模板代码

 

image.png

先前不同比较代码:

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->booleanIntPredicate,LongPredicate, DoublePredicate
Consumer<T>T->voidIntConsumer,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表达式的类型检查过程

image.png

首先找出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。

image.png

构造函数引用

对于一个现有的构造函数,你可以利用它的名称和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));

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值