前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java--lambda(λ)表达式

Java--lambda(λ)表达式

作者头像
SuperHeroes
发布2018-05-22 16:17:43
9980
发布2018-05-22 16:17:43
举报
文章被收录于专栏:云霄雨霁云霄雨霁

在Java引入lambda表达式之前,并不能在Java中传递一个代码段。因为Java是严格的面向对象编程,所以必须构造一个对象,这个对象的类需要有一个方法来包含所需的代码。

Java SE8中加入了lambda表达式来处理代码块,增强Java来支持函数式编程。

lambda表达式的语法:

代码语言:javascript
复制
//表达式形式:参数,箭头以及一个表达式
(String first, String second) -> first.length() - second.length() 
//如果代码要完成的计算无法放在一个表达式中,可以像方法一样使用{},并包含显式return语句
(String first, String second) -> 
{
    if(first.length()<second.length()) return -1;
    else return 1;
}

如果可以推导出一个lambda表达式的参数类型,则可以忽略其类型,如:

代码语言:javascript
复制
//在这里,first和second必然是字符串
Comparaor<String> comp = (first,second)->first.length()-second.length()

如果方法只有一个参数,而且这个参数的类型可以推导出,还可以省略小括号:

代码语言:javascript
复制
//这里event可以写作(event) or (ActionEvent event)
ActionListener listener = event->System.out.println("the time is"+ new Date());

分析上面的所有代码发现,无需指定lambda表达式的返回类型,因为lambda表达式的返回类型总会根据上下文推导得出。

函数式接口:

Java在不支持lambda表达式之前,已经存在很多封装代码块的接口,如Comparator, lambda表达式和这些接口是兼容的。

对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口成为函数式接口(使用lambda表达式比创建一个类并实现该接口更加简单,同时该名称也体现了函数式编程的概念)。

例如,Arrays.sort()方法提供了自定义排序。其第一个参数是待排序的数组,第二个参数是排序规则--一个实现了Comparator接口的实例。因为Comparator是只有一个抽象方法的接口,我们不必再去写一个类去实现这个接口,直接用lambda表达式:

代码语言:javascript
复制
Arrays.sort(arrays, (first,second)->first.length()-second.length());

这行代码可以实现字符串数组arrays中字符串的按长度排序。

上面的实例就是lambda表达式可以转换为函数式接口。但也只能做到这些。相比较而言,其他支持函数式编程的程序设计语言可以声明函数类型,声明这些类型的变量,还可以使用变量保存函数表达式。

方法引用:

如果有一个现成的方法可以完成想要传递的代码段的操作,那么我们可以直接传递该方法:

代码语言:javascript
复制
Timer t = new Timer(1000,System.out::println);

上面代码的功能是每出现一个定时器事件(1秒)就打印这个事件对象,上面的代码等同于:

代码语言:javascript
复制
Timer t = new Timer(1000,event->System.out.println(event));

再比如,如果想对字符串排序,并且不考虑大小写,可以传递以下方法表达式:

代码语言:javascript
复制
Arrays.sort(strings,String::compareToIgnoreCase);

等价于:

代码语言:javascript
复制
Arrays.sort(strings,(x,y)->x.compareToIgnoreCase(y));

从上面例子可以看出,要用 " :: " 分隔对象名/类名与方法名,主要有三种情况:

  1. object::instanceMethod
  2. Class::staticMethod
  3. Class::instanceMethod

当然,this和super关键字也可以用在方法引用中。

构造器引用:

构造器引用和方法引用很类似,只不过方法名为new。例如,Person::new是Person构造器的一个引用。哪一个构造器取决于上下文。

例如,假设有一个字符串列表要把它转换成Person数组,为此要在各个字符串上调用构造器:

代码语言:javascript
复制
ArrayList<String> names = ...;
Stream<Person> stream = names.stream().map(Person::new);
List<Person> people = stream.collect(Collectors.toList());

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

Java无法构造泛型类型T的数组,因为new T[n]会变成new Object[n]。但流苦利用构造器引用可以解决这个问题。例如需要一个Person数组,Stream接口有一个toArray方法可以返回一个Object数组:

代码语言:javascript
复制
Object[] people = stream.toArray();

但我们真正想要的是Person数组,可以这样做:

代码语言:javascript
复制
Person[] people = stream.toArray(Person::new);

变量的作用域:

我们可能会在lambda表达式中访问外围变量,如:

代码语言:javascript
复制
public static void repeatMessage(String text,int delay){
    ActionListener listener = event->
    {
        System.out.println(text);
        Toolkit.getDefaultToolkit().beep();
    };
    new Timer(delay,listener).start();
}

然后我们这样调用:

代码语言:javascript
复制
repeatMessage("Hello",1000);     //每隔1秒打印一个“Hello"

这里会有一个问题,lambda表达式可能在repeatMessage调用返回很久以后才运行,那时参数变量text已经不存在了,如何保留text?

来看一下lambda表达式的组成:

  1. 参数;
  2. 一个代码块;
  3. 自由变量的值,这是指非参数而且不在代码中定义的变量。

这里text就是自由变量,是lambda表达式的数据结构中必须保存的值。我们说它被lambda表达式捕获。

注意:关于代码块和自由变量值有一个术语:闭包。在Java中,lambda表达式就是闭包。

在Java中,要确保捕获的值是明确定义的,且有一个重要的限制:lambda表达式要捕获的变量必须是实际上的最终变量(该变量初始化之后不会再为它赋新值)。

处理lambda表达式:

上面讲了如何编写lambda表达式以及如何将lambda表达式传递到方法中。但如果我们写一个方法,如何保证我们的方法可以处理lambda表达式呢?

例如,我们要重复一个动作n次,将这个动作和重复次数传进repeat:

代码语言:javascript
复制
repeat(10,()->System.out.println("Hello World!"));

要接受这个lambda表达式,需要选择(有时需要提供)一个函数式接口。例如我们选用Runable接口:

代码语言:javascript
复制
public static void repeat(int n,Runnable action){
    for(int = 0; i < n; i++) action.run();
}

当我们吧lambda表达式传入方法的第二个参数后,调用action.run()时会执行lambda表达式的主体。

补充:

什么时候使用lambda表达式:

  • 在一个单独的线程中运行代码
  • 多次运行代码
  • 在算法的适当位置运行代码(例如排序中的比较操作)
  • 发生某种事件时执行代码
  • 只在必要时才运行的代码

常用的函数式接口:

函数式接口

参数类型

返回类型

抽象方法名

描述

Runnable

void

run

作为无参数或返回值的动作执行

Supplier<T>

T

get

提供一个T类型的值

Consumer<T>

T

void

accept

处理一个T类型的值

BiConsumer<T>

T,U

void

accept

处理T和U类型的值

Function<T,R>

T

R

apply

有一个T类型参数的函数

BiFunction<T,U,R>

T,U

R

apply

有T和U类型参数的函数

UnaryOperator<T>

T

T

apply

类型T上的一元操作符

BinaryOperator<T>

T,T

T

apply

类型T上的二元操作符

Predicate<T>

T

boolean

test

布尔值函数

BiPredicate<T>

T,U

boolean

test

有两个参数的布尔值函数

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • lambda表达式的语法:
  • 函数式接口:
  • 方法引用:
  • 构造器引用:
  • 变量的作用域:
  • 处理lambda表达式:
  • 补充:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档