一、学习背景
最近看了许多较新的教学视频,其中的讲师们在许多代码中使用了Lambda表达式,初学者看来觉得非常难以理解。这导致整个教学视频无法继续看下去。我在当时也是这个感觉,所以去简单的学习了一下Lambda表达式,在这里说一下自己对于Lambda表达式的理解。
二、简述
Runnable runnable = () -> {
System.out.println("test");
};
runnable.run();
这是一段非常简单的Lambda表达式。如果理解起来比较困难的话,他可以完全的转化为如下代码。
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("test");
}
};
runnable.run();
学过Java并发的朋友们都知道实现Runnable接口是实现多线程的一个常用的方法。
但是我们现在用到的不是他的这个特性。
我们知道,接口是不能够被直接实例化的。所以我们如果想对一个接口进行实例化的时候,必须要给他一个实现类。
那么我们转化后的代码可以看出,我们在 “ = ” 的后面实现实现了一个匿名实现类,用来实现Runnable接口的类。
然后将这个实现类赋给了runnable对象。
让我们回到上面的代码。我们可以看出Lambda所做的事情,和转化后的代码所做的事是一样的。
所以个人理解:Lambda表达式可以用来为接口简便的生成一个实现类。
在Lambda表达式中我们可以看出方法体就是代表着Runnable接口中run方法的方法体。
那么这个时候包括我自己刚刚学习的时候也会有一个疑问,凭什么它就代表run的方法体呢?如果有其他的方法呢?
这个时候Lambda表达式的一个特性,或者说是要求完美的解决了这个问题:
Lambda表达式只能用来实例化单抽象方法的接口 ---> “函数式接口”。
这样问题就解决了,我的接口里只有一个方法,那么当然,方法体就是写给他的啊。
那么接下来我简单的说一下Lambda表达式的语法。
三、语法
Lambda表达式一般形式如下:
Runnable runnable = () -> {
System.out.println("test");
};
runnable.run();
如同上方的写法,我们知道run方法是无参的,所以括号里只需要置空即可。
Consumer consumer = (x) -> {};
//只有一个参数,可以省略参数的括号
Consumer consumer1 = x -> {};
//两个参数
BiConsumer biConsumer = (x, y) -> {};
JDK1.8之后为了我们使用Lambda表达式方便,官方增加了许多这样的单方法接口。
上方代码的两个类都是单方法接口,只不过一个是接单参,一个是接双参
但是这里有一点:如果方法仅有一个参数的时候,括号可以被省略。
另外,如上图的x,y,参数类型是可以被省略的。
如果方法体只有一行语句,并且此语句为返回值时,方法体的大括号可以被省略,例子如下:
Function function = (x) -> {
return "abc";
};
//只有一行一句,并且其为返回值,方法体大括号可省略
Function function1 = (x) -> "abc";
四、结合方法引用
方法引用也是JDK1.8中新特性之一。他用方法的名字来指向一个语法。正好可以与Lambda表达式一起合作编写出非常简洁的代码(但真的很难理解)。
对于方法的引用。我个人理解是:这个方法体仅仅用来调用另一个方法。
方法的引用大致分为如下几类:(这些类型的前一个代码均是未优化的,后一个代码均是优化后的)
1、静态方法的引用
public class Example {
public static void main(String[] args) {
//常规写法
Consumer<String> consumer = x -> {
Example.say(x);
};
consumer.accept("abc");
}
private static void say(String o) {
System.out.println(o);
}
}
public class Example {
public static void main(String[] args) {
//在上方代码中,我们的Lambda表达式的内容仅仅是用来调用Example的say静态方法
//另外我们Lambda的参数正好是say所需要的参数
//那么我们就可以改成如下这样
Consumer<String> consumer = Example::say;
consumer.accept("abc");
}
private static void say(String o) {
System.out.println(o);
}
}
2、实例方法的引用
public class Example {
public static void main(String[] args) {
Example e = new Example();
//未经过转变的代码
Consumer<String> consumer = x -> {
e.say(x);
};
consumer.accept("abc");
}
private void say(String o) {
System.out.println(o);
}
}
public class Example {
public static void main(String[] args) {
Example e = new Example();
//我们仅仅调用了e实例的say方法,并且lambda接受的参数与方法相同
//可以转化为如下代码
Consumer<String> consumer = e::say;
consumer.accept("abc");
}
private void say(String o) {
System.out.println(o);
}
}
3、对象方法的引用
public class Example {
public static void main(String[] args) {
BiConsumer<Example, String> consumer = (Example e, String x) -> {
e.say(x);
};
consumer.accept(new Example(), "abc");
}
private void say(String o) {
System.out.println(o);
}
}
public class Example {
public static void main(String[] args) {
//当我们对Lambda表达式传递多个参数的时候
//我们如果想通过Lambda表达式第二个及以后的参数作为
//Lambda表达式第一个参数所持有的方法的参数的话,可以作出如下变化。
BiConsumer<Example, String> consumer = Example::say;
consumer.accept(new Example(), "abc");
}
private void say(String o) {
System.out.println(o);
}
}
4、构造方法的引用
public class Example {
private String content;
public static void main(String[] args) {
//未经过转化的代码
Consumer<String> consumer = (x) -> {
new Example(x);
};
}
Example(String content){
this.content = content;
}
}
public class Example {
private String content;
public static void main(String[] args) {
//如果这个方法仅仅是用来调用一个构造函数的话,并且lambda表达式的参数与构造方法相同
//则可以转化为如下代码
Consumer<String> consumer = Example::new;
}
Example(String content){
this.content = content;
}
}
从中我们可以看出,使用方法引用的最关键的地方就是调用的方法参数与我们Lambda表达式的参数相同。
只有这样我们才可以将Lambda表达式与方法饮用结合在一起,编写出更简洁的代码(当然也更难以理解)。
这是我对Lambda表达式的个人理解,如果有错误请务必指正,感谢阅读。