lambda表达式

为何要引入Lambda表达式


  对于一个Java变量,我们可以对其赋值,但如果我们想对一个代码块赋值应该怎么做?
在这里插入图片描述

  在Java 8 之前,这是无法做到的,Lambda表达式的出现,使代码块可以赋值。但上面的写法并不是最简洁的,实际使用中,可以移除一些无用的声明。
在这里插入图片描述

  这样,就把一段代码赋值给一个变量。由于java是强类型语言,这里还有一个问题,这个变量的类型是什么?==在java中,所有Lambda表达式类型都是一个接口,而Lambda表达式本身,就是那个接口的实现。==上面的Lambda表达式加上类型后如下所示:
在这里插入图片描述

  这种只有一个函数需要实现的接口类型(只有一个抽象函数,仍可包含静态函数和默认函数),成为“函数式接口”。为了避免别人在接口中增加函数,可以在上面加上一个声明@FunctionalInterface。
在这里插入图片描述
  这样,就得到如下完整的Lambda表达式声明:

MyLambdaInterface aBlockOfCode = (s) -> System.out.println(s);

java 8在java.util.function包中预定义了大量函数式接口,典型的包含如下4类接口:

  • XxxFunction:这类接口中通常包含一个apply()抽象方法,该方法对参数进行处理、转换,然后返回一个新的值。通常用于对指定数据进行转换处理。
  • XxxConsumer:这类接口中通常包含一个accept()抽象方法,与apply()方法基本相似,也负责对参数进行处理,不过不返回处理结果。
  • XxxPredicate:这类接口通常包含一个test()抽象方法,通常用来对参数进行判断,然后返回一个boolean值。该接口通常用于判断参数是否满足指定条件,经常用于进行筛选过滤数据。
  • XxxSupplier:这类接口通常包含一个getAsXxx()的抽象方法,该方法不需要输入参数,会按某种逻辑算法返回一个数据。

如何使用Lambda表达式


  使用Lambda表达式的重点是延迟执行。如果想要立即执行代码,完全可以直接执行,无需将它包装在一个Lambda表达式中。之所以希望以后再执行,可能有以下原因:

  • 在一个单独的线程中执行代码;
  • 多次运行代码;
  • 在算法的适当位置运行代码(如 排序的比较操作);
  • 发生某种情况时执行代码(如 点击按钮等);
  • 只在必要时才运行代码。

  lambda是一个可传递的代码块,可以在以后执行一次或多次。我们先观察Java中哪些地方可能用过这种代码块。

如果想按指定时间间隔完成工作,可以把工作放在ActionListener的ActionPerformed方法中:

class Worker implements ActionListener{
    public void actionPerformed(ActionEvent event){
        ...
        do something
    }
}

想要反复执行上述代码,可以构造Worker的实例。然后把该实例对象提交到一个Timer对象。重点是actionPerformed方法中包含希望以后执行的代码。

或者考虑定义比较器进行排序。如果想按长度而不是默认的字典顺序对字符串排序,可以向sort方法中传入Comparator对象:

class LengthComparator implements Comparator<String>{
    public int compare(String first, String second){
        return first.length() - second.length();
    }
}
...
Arrays.sort(stringArray,new LengthComparator());

上述两个例子都有一些共同点,都是将一个代码块传递到某个对象(一个定时器/sort方法)。这个代码块将在某个时间调用。Java是面向对象语言,在java 8以前,传递这样的代码块并不容易,必须构造一个对象,该对象所属的类需要有一个方法能包含所需的代码。

有了Lambda表达式,可直接给出方法的实现,不需要新建对象繁琐的操作。不过lambda表达式要求目标类型必须是函数式接口,只能声明一个抽象方法。为保证lambda表达式的目标类型是一个明确的函数式接口,可以有如下三种常见方式:

  • 将Lambda表达式赋值给函数式接口的变量
  • 将lambda表达式作为函数式接口类型的参数传递给某个方法
  • 使用函数式接口对lambda表达式进行强制类型转换(这样才可赋值给Object对象,否则只能赋值给函数式接口变量)

方法引用与构造器引用


  • 方法引用

可能使用现有的方法可以完成传递到代码中的动作。如果lambda表达式的代码块只有一条语句,程序可以省略代码块的花括号。不仅如此,如果只有一条代码,还可以在代码块中使用方法引用和构造器引用。

种类示例说明对应的lambda表达式
引用类方法类名::类方法接口中被实现方法的全部参数传给该类方法作为参数(a,b…)->类名.类方法(a,b…)
引用特定对象的实例方法特定对象::实例方法接口中被实现方法的全部参数传给该类方法作为参数(a,b…)->特定对象.实例方法(a,b…)
引用某类对象的实例方法类名::实例方法接口中实现的方法第一个参数作为调用者,后面参数传递给该方法作为参数(a,b…)->a.实例方法(b,…)
引用构造器类名::new接口中被实现方法的全部参数传给该类方法作为参数(a,b…)->new 类名(a,b…)

假设希望出现一个定时器事件就打印这个事件对象。为此可以这样调用:

Timer t = new Timer(1000,event->System.out.println(event));

由于借用已有的方法实现传递到代码的动作,如果直接把pringln方法传递到Timer构造器就更好了,传递的参数直接作为该方法的参数。具体做法如下:

Timer t = new Timer(1000,System.out::println);

表达式System.out::println是一个方法引用,它等价于Lambda表达式x -> System.out.println(x)。

再看一个例子,假设想要对字符串排序,而不考虑字符的大小写。可以传递一下方法表达式:

Arrays.sort(strings, String::compareToIgnoreCase)

从这些例子可看出,要使用::操作符分割方法名/对象名。主要有3种情况:

  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod

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

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

  • 构造器引用

构造器引用和数组引用很类似,只不过方法名为new。例如,Person::new是Person构造器的一个引用,具体是哪个构造器取决于上下文和传入的参数。假设有一个人名的字符串列表,想把它转换为一个Person对象数组,需要在各个字符串上调用构造器,调用如下:

ArrayList<String> names = ...;
Stream<Person> stream = names.stream().map(Person::new);
List<Person> people = stream.collect(Collections.toList());

map方法会为各个列表元素调用Person(String)的构造器。如果有多个构造器,编译器会从上下文中选择String类型参数的构造器。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值