java SE 8新特性lambda表达式

目录

一、lambda表达式的简介

二、lambda表达式的语法

三、函数式接口

四、lambda表达式作用及运用

五、方法引用

六、构造器引用

七、变量的作用域


一、lambda表达式的简介

1、什么是lambda表达式?

lambda表达式是一个可以传递的代码块,可以在将来执行一次或多次。使用lambda表达式的重点就是延迟执行。

2、什么时候可以用到lambda表达式?

当你需要将一个代码块作为参数传递,并希望这个代码块在将来某个时间调用时就可以用到lambda表达式。

3、为什么要引入lambda表达式?

在java SE 8(jdk1.8)之前,在java中不能直接传递代码块。因为java是一种面向对象语言,如果想传递代码块,就必须构造一个对象,并且这个对象的类需要有一个方法能够包含所需要的代码。在其他语言中可以直接处理代码块,为了让java也能够处理代码块,java设计者们在java SE 8中提出用lambda表达式来处理代码块。

二、lambda表达式的语法

1、基本形式小括号、参数、箭头(->)以及一个表达式,如下。

(String first,String second)->first.length()-second.length()

2、如果箭头(->)后面的代码要完成的计算无法放在一个表达式中,就要像写方法一样,把这些代码放在{}中,并包含显示的return语句。如下:

(String first,String second)->
{
    if(first.length()<second.length())return -1;
    else if(first.length()>second.length())return 1;
    else return 0;
}

3、即使lambda表达式没有参数,仍然要提供空括号,就像无参方法一样。

()->System.out.println("hello world")

4、如果可以推导出lambda表达式的参数类型,则可以忽略参数的类型(通常都可以忽略)。如下

//Compartor是一个函数式接口,lambda表达式可以赋值给函数式接口变量,后面会具体介绍
Compartor<String> comp = (first,second)->first.length()-second.length();

 5、如果方法只有一个参数,而且这个参数的类型可以推导得出,那么可以省略小括号。如下

//ActionListener 也是一个函数式接口
ActionListener listener = event->System.out.println("hello world");

注意:最好将lambda表达式看成一个函数来理解。从上面的例子可以看出箭头(->)前面的参数就如同方法的参数,箭头后面的语句就如同方法的方法体。

综上lambda表单式的常见形式如下:

(参数)->单条语句

(参数)->{多条语句}

三、函数式接口

函数式接口:只有一个抽象方法的接口,被称为函数式接口。需要这种接口的对象时,就可以提供一个lambda表达式。

注:在java中,lambda表达式不能独立存在,总是会将其转换成函数式接口的实例来运用。

Java API 的java.util.function包中定义了很多非常通用的函数式接口。例如包中有一个特别有用的函数式接口Predicate:

public interface Predicate<T>{
    boolean test(T t);
}

ArrayList类有一个removeIf方法,它的参数就是一个Predicate接口。这个接口专门用来传递lambda表达式。例如,下面语句将一个数组列表删除所有的null值:

list.removeOf(e->e==null);

注:如果设计自己的函数式接口,其中只有一个抽象方法,可以用@FunctionalInterface注解来标记这个接口。这样当你无意中增加了另外的抽象方法时,编译器会报错。

以下是Java API 的java.util.function包中一些常用的函数式接口:

表1 常用函数式接口
函数式接口参数类型

返回

类型

抽象方法名描述其他方法
Runnablevoidrun作无参数或返回值的动作运行 
Supplier<T>Tget提供一个T类型的值 
Consumer<T>Tvoidaccept处理一个T类型的值andThen
BiConsumer<T,U>T,Uvoid accept处理T和U类型的值andThen
Function<T,R>TRapply有一个T类型参数的函数compose,andThen,identity
BiFunction<T,U,R>T,URapply有T和U类型参数的函数andThen
Predicate<T>Tbooleantest布尔值函数and,or,negate,isEqual
BiPredicate<T,U>T,Ubooleantest有两个参数的布尔值函数and,or,negate

 

表2 基本类型的函数式接口(  注:p,q为int,long,double;P,Q为Int,Long,Double
函数式接口参数类型返回类型抽象方法名
BooleanSuppliernonebooleangetAsBoolean
PSuppliernonepgetAsP
PConsumerpvoidaccept
ObjPConsumer<T>T,pvoidaccept
PFunction<T>pTapply
PtoQFunctionpqapplyAsQ
ToPFunction<T>TpapplyAsP
ToPBiFunction<T,U>T,UpapplyAsP
PPredicatepbooleantest

四、lambda表达式作用及运用

最直观的作用就是使代码变得更加简洁。

我们可以对比一下Lambda表达式和传统的java对同一个接口的实现:

代码-1

 

这两种方式本质上完全相同,但java8中的写法更简洁。由于lambda表达式可以赋值给一个函数式接口变量,所以我们可以将lambda表达式作为参数传递给函数,而传统的java必须要有明确的接口的定义初始化才行:

代码-2

 

下面我们举个例子来说明:

假设Student类的定义和List<Student>的的值都给定。

代码-3

现在需要你打印出stuList中年龄大于17岁的所有学生的信息。

Lambda写法:定义两个函数式接口,定义一个静态方法,调用静态函数式并给参数赋值。

代码-4

在第三部分我们已经提到在Java API的java.util.function包中定义了很多非常通用的函数式接口,所以很多时候我们不必自己定义函数接口。这个案例中我们可以不定义Checker和Executor这两个接口,直接用java.util.function包中的Predicate<T>和Consumer<T>接口就可以了。你可以看出这两个接口和Checker、Executor其实是一样的。

代码-5

 我们将静态方法checkAndExcute改用函数式接口包中的接口,代码-4中的内容就只需要以下几步就可完成:

代码-6

 提问:假如现在需要你打印出所有姓张的同学该如何做?需要重新定义方法吗?

回答当然是否,我们只需在调用checkAndExcute方法时第二个参数传另一个lambda表达式即可,如下:

代码-6

小结:在设计方法时配合函数式接口、lambda表达式可以让我们的方法变得更简洁、更通用。又如我们要编写一个排序方法,可以将排序的比较条件设计成函数式接口中的方法,这样我们在调用这个排序方法时传入不同的lambda表达式就可以得到不同的排序结果。

五、方法引用

有时,可能已经有现成的方法可以完成你想传递到其他代码的某个动作。列如第四部分我们的案例中(如代码-6所示)我们调用checkAndExcute方法时第三个参数传的一个lambda表达式stu -> System.out.println(stu.toString()) ,此处我们可以改为传一个方法引用的表达式System.out::println。表达式System.out::println等价于stu -> System.out.println(stu)。(注:输出语句输出一个对象时,系统会自动调用该对象的toString方法)。

方法引用的基本法则:::操作符分割方法名与对象或类名。

1、方法引用主要有三种情况:

  • object :: instanceMethod                   对象名::实例方法名
  • Class :: staticMethod                         类名::静态方法名
  • Class :: instanceMenthod                  类名::实例方法名

前两种情况,方法引用等价于提供方法参数的lambda表达式。如System.out::println等价于s -> System.out.println(s)。类似的,Math::pow等价于Math.pow(x,y)

第三种情况,第1个方法参数会成为方法目标。例如:String::compareToIgnoreCase等价于(x,y) -> x.compareToIgnoreCase(y)

2、方法引用的其他情况:

  • 在方法引用中可以使用this参数。例如,this::equals等同于x -> this.equals(x)
  • 在方法引用中使用super也是合法的。super :: instanceMethod ,使用 super做为目标会调用给的方法的超类版本。        

六、构造器引用

1、构造器引用和方法引用很相似,只是把方法名改成了new,即Class::new例如Person::new是Person构造器的一个引用。系统会根据上下文选择具体调用哪一个构造器。

2、可以用数组类型建立构造器引用。例如int[] ::new是一个构造器引用,它有一个参数:即数组长度,等价于x->new int[x]

七、变量的作用域

在lambda表达式中可以访问外围方法或类中的变量。如下面这个例子:

调用方法 :repeatMessage("Hello",1000);//每1000毫秒打印一次Hello

可以看出text变量不是lambda表达式中定义的变量,而是repeatMessage方法的一个参数变量。这里有一个问题,lambda表达式的代码可能会在repeatMessage方法调用返回很久后才会运行,而那个时候参数变量已经不存在了。哪究竟是如何保留text变量呢?

首先看lambda表达式的3个部分:

  • 一个代码块;
  • 参数;
  • 自由变量的值(这个是指非参数而且不在代码中定义的变量)

在我们的例子中,这个lambda表达式有1一个自由变量text。表示lambda表达式的数据结构必须存储自由变量的值,在这里是“Hello”。我们说它被lambda表达式捕获(当把一个lambda表达式转换成其对应的函数式接口对象时,自由变量的值会复制到这个对象的实例变量中)。

(注:关于代码块以及自由变量值有一个术语:闭包在java中lambda表达式就是闭包。)

lambda表达式定义和使用变量的注意事项:

  • lambda表达式中捕获的变量必须实际上是最终变量(即这个变量初始化之后就不会在为它赋新值)。
  • 在lambda表达式中声明一个与局部变量同名的参数或局部变量是不合法的。
  • 在lambda表达式中使用this关键字时,是指创建这个lambda表达式的方法的this参数。(在lambda表达式中使用this,和在其他地方使用this没有任何差别)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值