专栏首页云霄雨霁Java--lambda(λ)表达式

Java--lambda(λ)表达式

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

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

lambda表达式的语法:

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

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

//在这里,first和second必然是字符串
Comparaor<String> comp = (first,second)->first.length()-second.length()

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

//这里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表达式:

Arrays.sort(arrays, (first,second)->first.length()-second.length());

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

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

方法引用:

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

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

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

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

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

Arrays.sort(strings,String::compareToIgnoreCase);

等价于:

Arrays.sort(strings,(x,y)->x.compareToIgnoreCase(y));

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

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

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

构造器引用:

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

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

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数组:

Object[] people = stream.toArray();

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

Person[] people = stream.toArray(Person::new);

变量的作用域:

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

public static void repeatMessage(String text,int delay){
    ActionListener listener = event->
    {
        System.out.println(text);
        Toolkit.getDefaultToolkit().beep();
    };
    new Timer(delay,listener).start();
}

然后我们这样调用:

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:

repeat(10,()->System.out.println("Hello World!"));

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

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

有两个参数的布尔值函数

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java--动态代理

    代理类MyInvocationHandler 实现(必须实现InvocationHandler接口 ):

    SuperHeroes
  • Java--集合类之Vector、BitSet、Stack、Hashtable

    SuperHeroes
  • 查找----基于二叉查找树

    SuperHeroes
  • weex-11-组件slider的使用

    1.怎么让banner的宽度和屏幕的宽度相等 2.怎么让banner自动轮播和轮播间隔 3.如何添加指示器 4.如何设置指示器的颜色和大小 5.点击轮播...

    酷走天涯
  • 马蜂窝联合腾讯文旅联合发布《2020年春节旅游趋势报告》

    ? 2020年1月10日,春运的号角正式吹响。这场全球年度最大规模的人口“迁徙”,也拉开了春节假期的序幕。 伴随着人们生活水平的提高,越来越多的国人在春节期间...

    腾讯文旅
  • 云成本管理方法论(三)——云优化管理之判定规则

    云优化管理四个管理维度中管理时点在通用管理模型基础上不需要额外补充,所以主要说明其他三个维度(管理对象、判定规则和管理措施)。另外,为了贴近我们熟悉的优化概念,...

    jancco
  • 前Facebook高管也搞起了“脑机接口”: 用一顶帽子实现“灵感下载”

    大数据文摘
  • 剑指offer - 数组中重复的数字 - JavaScript

    题目描述:找出数组中重复的数字。在一个长度为 n 的数组 nums 里的所有数字都在 0 ~ n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了...

    心谭博客
  • Android自定义控件之刻度尺控件

    今天我做的是一个自定义刻度尺控件,由于项目需求需要使用刻度尺那样滑动选择,由于对自定义控件的认识还不够深入,于是花了一周多时间才把这个控件给整出来,也是呕心沥血...

    砸漏
  • 「PostgreSQL」用MapReduce的方式思考,但使用SQL

    对于那些考虑使用Citus的人来说,如果您的用例看起来很合适,我们通常愿意花一些时间与您一起帮助您了解Citus数据库及其可以提供的性能类型。我们通常与我们的一...

    首席架构师智库

扫码关注云+社区

领取腾讯云代金券