Java Lambda表达式

看一下下面这个接口:

public interface MyEventConsumer {

public void consume(Object event);

}

可以使用匿名接口实现方式来实现此接口,如下所示:

MyEventConsumer consumer = new MyEventConsumer() {

public void consume(Object event){

System.out.println(event.toString() + " consumed");

}

};

此匿名MyEventConsumer实现可以具有自己的内部状态。

重写匿名接口实现:

MyEventConsumer myEventConsumer = new MyEventConsumer() {

private int eventCount = 0;

public void consume(Object event) {

System.out.println(event.toString() + " consumed " + this.eventCount++ + " times.");

}

};

请注意,匿名MyEventConsumer接口实现现在具有一个名为eventCount的属性。

Lambda表达式不能具有此类属性。因此,lambda表达式是无状态的。

Lambda类型推断

============================================================================

在Java 8之前,在进行匿名接口实现时,必须指定要实现的接口。这是本文开头的匿名接口实现示例:

stateOwner.addStateListener(new StateChangeListener() {

public void onStateChange(State oldState, State newState) {

// do something with the old and new state.

}

});

使用lambda表达式时,通常可以从相关的代码中推断出类型。例如,可以从addStateListener()方法(StateChangeListener接口上的抽象方法)的方法声明中推断参数的接口类型。

这称为类型推断。编译器通过在其他地方寻找类型来推断参数的类型——在这种情况下为方法定义。这是本文开头的示例,lambda表达式中并未声明参数的类型:

stateOwner.addStateListener(

(oldState, newState) -> System.out.println(“State changed”)

);

在lambda表达式中,通常可以推断参数类型。在上面的示例中,编译器可以从onStateChange()方法声明中推断其类型。因此,从onStateChange()方法的方法声明中就可以推断出参数 oldState 和 newState 的类型。

Lambda参数

==========================================================================

由于Java lambda表达式实际上只是方法,因此lambda表达式可以像方法一样接受参数。前面显示的lambda表达式的(oldState,newState)部分指定lambda表达式使用的参数。这些参数必须与函数式接口的抽象方法参数匹配。在当前这个示例,参数必须与StateChangeListener接口的onStateChange()方法的参数匹配:

public void onStateChange(State oldState, State newState);

首先,lambda表达式中的参数数量必须与方法匹配。

其次,如果你在lambda表达式中指定了任何参数类型,则这些类型也必须匹配。我还没有向你演示如何在lambda表达式参数上设置类型(本文稍后展示),但是在大多数情况下,你不会用到它。

无参数


如果lambda表达式匹配的方法无参数,则可以这样写lambda表达式:

() -> System.out.println(“Zero parameter lambda”);

请注意,括号中没有内容。那就是表示lambda不带任何参数。

一个参数


如果Java lambda表达式匹配的方法有一个参数,则可以这样写lambda表达式:

(param) -> System.out.println("One parameter: " + param);

请注意,参数在括号内列出。

当lambda表达式是单个参数时,也可以省略括号,如下所示:

param -> System.out.println("One parameter: " + param);

多个参数


如果Java lambda表达式匹配的方法有多个参数,则需要在括号内列出这些参数。代码如下:

(p1, p2) -> System.out.println("Multiple parameters: " + p1 + ", " + p2);

仅当方法是单个参数时,才可以省略括号。

指定参数类型


如果编译器无法从lambda匹配的函数式接口抽象方法推断参数类型,则有时可能需要为lambda表达式指定参数类型。不用担心,编译器会在这种情况下会有提醒。这是一个Java lambda指定参数类型示例:

(Car car) -> System.out.println("The car is: " + car.getName());

如你所见,car参数的类型(Car)写在参数名称的前面,就像在其他方法中声明参数或对接口进行匿名实现时一样。

Java 11中的var参数类型


在Java 11中,你可以使用var关键字作为参数类型。

var关键字在Java 10中作为局部变量类型推断引入。从Java 11开始,var也可以用于lambda参数类型。这是在lambda表达式中使用Java var关键字作为参数类型的示例:

Function<String, String> toLowerCase = (var input) -> input.toLowerCase();

Lambda表达式主体

=============================================================================

lambda表达式的主体以及它表示的函数/方法的主体在lambda声明中的->的右侧指定:

这是一个示例:

(oldState, newState) -> System.out.println(“State changed”)

如果你的lambda表达式需要包含多行,则可以将lambda函数主体括在{}括号内,Java在其他地方声明方法时也需要将其括起来。这是一个例子:

(oldState, newState) -> {

System.out.println("Old state: " + oldState);

System.out.println("New state: " + newState);

}

Lambda表达式返回值

==============================================================================

你可以从Java lambda表达式返回值,就像从方法中返回值一样。你只需向lambda表达式主体添加一个return,如下所示:

(param) -> {

System.out.println("param: " + param);

return “return value”;

}

如果你的lambda表达式只需要计算一个返回值并将其返回,则可以用更短的方式指定返回值。例如这个:

(a1, a2) -> { return a1 > a2; }

你可以写成:

(a1, a2) -> a1 > a2;

然后,编译器会断定表达式 a1> a2 是lambda表达式的返回值。

Lambdas作为对象

=============================================================================

Java lambda表达式本质上是一个对象。你可以将变量指向lambda表达式并传递,就像处理其他任何对象一样。这是一个例子:

public interface MyComparator {

public boolean compare(int a1, int a2);

}

MyComparator myComparator = (a1, a2) -> return a1 > a2;

boolean result = myComparator.compare(2, 5);

第一个代码块显示了lambda表达式实现的接口。

第二个代码块显示了lambda表达式的定义,lambda表达式如何分配给变量,以及最后如何通过调用其实现的接口方法来调用lambda表达式。

变量捕获

======================================================================

在某些情况下,Java lambda表达式能够访问在lambda表达式主体外部声明的变量。

Java lambdas可以捕获以下类型的变量:

  • 局部变量

  • 实例变量

  • 静态变量

这些变量捕获的每一个将在以下各节中进行描述。

局部变量捕获


Java lambda可以捕获在lambda主体外部声明的局部变量的值。为了说明这一点,首先看一下这个函数式接口:

public interface MyFactory {

public String create(char[] chars);

}

现在,看一下实现MyFactory接口的lambda表达式:

MyFactory myFactory = (chars) -> {

return new String(chars);

};

现在,此lambda表达式仅引用传递给它的参数值(chars)。但是我们可以改变一下。这是引用在lambda函数主体外部声明的String变量的更新版本:

String myString = “Test”;

MyFactory myFactory = (chars) -> {

return myString + “:” + new String(chars);

};

如你所见,lambda表达式主体现在引用了在lambda表达式主体外部声明的局部变量myString。当且仅当被引用的变量是“有效只读(如果一个局部变量在初始化后从未被修改过,那么它就是有效只读)”时才有可能,这意味着在赋值之后它不会改变其值。如果myString变量的值稍后更改,则编译器将抱怨从lambda主体内部对其的引用。

实例变量捕获


Lambda表达式还可以捕获创建Lambda的对象中的实例变量。这是示例:

public class EventConsumerImpl {

private String name = “MyConsumer”;

public void attach(MyEventProducer eventProducer){

eventProducer.listen(e -> {

System.out.println(this.name);

});

}

}

注意lambda表达式主体中对this.name的引用。这将捕获封闭的EventConsumerImpl对象的 name 实例变量。甚至可以在捕获实例变量后更改其值——该值将反映在lambda内部。

this语义实际上是Java lambda与接口的匿名实现不同的地方之一。匿名接口实现可以有自己的实例变量,这些实例变量可以通过this进行引用。但是,lambda不能拥有自己的实例变量,因此它始终指向封闭的对象。

注意:EventConsumer的设计不是很优雅。我只是这样写来说明实例变量捕获。

静态变量捕获


Java lambda表达式还可以捕获静态变量。

因为只要可以访问静态变量(包作用域或public作用域),Java应用程序中的任何地方都可以访问静态变量。

这是一个创建lambda表达式的示例类,该lambda表达式从lambda表达式主体内部引用静态变量:

public class EventConsumerImpl {

private static String someStaticVar = “Some text”;

public void attach(MyEventProducer eventProducer){

eventProducer.listen(e -> {

System.out.println(someStaticVar);

});

}

}

lambda捕获到静态变量后,它的值也可以更改。

同样,上述类设计不太合理。不要对此考虑太多。该类主要用于向你显示lambda表达式可以访问静态变量。

Lambda方法引用

============================================================================

如果你的lambda表达式所做的只是用传递给lambda的参数调用另一个方法,则Java lambda实现提供了更简洁的方式表示该方法调用。

首先,这是一个函数式接口:

public interface MyPrinter{

public void print(String s);

}

以下是创建实现MyPrinter接口的Java lambda表达式的示例:

MyPrinter myPrinter = (s) -> { System.out.println(s); };

由于lambda主体仅由一个语句组成,因此我们实际上可以省略括号{}。另外,由于lambda方法只有一个参数,因此我们可以省略该参数周围的括号()。更改之后的lambda表达式:

MyPrinter myPrinter = s -> System.out.println(s);

由于所有lambda主体所做的工作都是将字符串参数转发给System.out.println()方法,因此我们可以将上述lambda声明替换为方法引用。以下是lambda表达式引用方法的实例:

MyPrinter myPrinter = System.out::println;

注意双冒号::。它会向Java编译器发出信号,这是方法引用。引用的方法是双冒号之后的内容。拥有被引用方法的任何类或对象都在双冒号之前。

你可以引用以下类型的方法:

  • 静态方法

  • 参数对象的实例方法

  • 实例方法

  • 构造方法

以下各节介绍了每种类型的方法引用。

静态方法引用


最容易引用的方法是静态方法。

首先是函数式接口的示例:

public interface Finder {

public int find(String s1, String s2);

}

这是一个静态方法:

public class MyClass{

public static int doFind(String s1, String s2){

return s1.lastIndexOf(s2);

}

}

最后是引用静态方法的Java lambda表达式:

Finder finder = MyClass::doFind;

由于Finder.find()和MyClass.doFind()方法的参数匹配,因此可以创建实现Finder.find()并引用MyClass.doFind()方法的lambda表达式。

参数方法引用


也可以将其中一个参数的方法引用到lambda。

函数式接口如下:

public interface Finder {

public int find(String s1, String s2);

}

该接口用于表示能在s1中搜索s2的出现的部分。下面是一个Java lambda表达式的示例,它调用indexOf() 搜索:

Finder finder = String::indexOf;

这等价以下lambda定义:

Finder finder = (s1, s2) -> s1.indexOf(s2);

请注意简洁方式版本是如何引用单个方法的。Java编译器尝试将引用的方法与第一个参数类型相匹配,使用第二个参数类型作为被引用方法的参数。

实例方法引用


第三,还可以从lambda表达式中引用实例方法。

首先,让我们来看一个函数式接口定义:

public interface Deserializer {

public int deserialize(String v1);

}

此接口表示一个组件,该组件能够将字符串“反序列化”为int。

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
式版本是如何引用单个方法的。Java编译器尝试将引用的方法与第一个参数类型相匹配,使用第二个参数类型作为被引用方法的参数。

实例方法引用


第三,还可以从lambda表达式中引用实例方法。

首先,让我们来看一个函数式接口定义:

public interface Deserializer {

public int deserialize(String v1);

}

此接口表示一个组件,该组件能够将字符串“反序列化”为int。

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-ThVpLo3J-1714797424957)]

[外链图片转存中…(img-vn9eJiRF-1714797424958)]

[外链图片转存中…(img-pzWNuON7-1714797424958)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值