对Lamda表达式的简单理解

第一次见Lamda表达式

第一次看到这行代码的时候,我是百思不得姐:

1
Toolkit.let(mUserTabFragment, f -> f.setUserVisibleHint(true));

点击进入let函数看到其定义如下:

1
2
3
4
5
6
public static <T> void let(@Nullable T input, Consumer<T> consumer) {
if (input == null || consumer == null) {
return;
}
consumer.accept(input);
}

可以看出let的第二个参数是一个Consumer,但是上面的f->f.setUserVisibleHint(isVisibleToUser));是什么鬼,是Consumer吗?答案是 是的,这样看就明白了,首先,我们创建一个Consumer对象:

1
2
3
4
5
6
Consumer<Fragment> consumer = new Consumer<Fragment>() {
@Override
public void accept(Fragment f) {
f.setUserVisibleHint(true));
}
};

上面代码的Lamda形式为:

1
Consumer<Fragment> consumer = (f)-> f.setUserVisibleHint(true));

所以 可以直接将等号右边带入let函数的第二个参数,就变成了那个样子:

1
Toolkit.let(mUserTabFragment, f -> f.setUserVisibleHint(true));

现在应该就明白了第二个参数到底是啥了,其实就是一种Lamda表达式。

函数式接口(Functional Interface)

那么到底什么是Lamda表达式呢?在使用Java的Lamda表达式之前,有一个重要的概念,叫函数式接口:

函数式接口(Functional Interface)就是有且仅有一个方法的接口

例如我们平时经常用到的Callable, Runnable都是函数式接口,在这里的Consumer也不例外,Consumer也是java1.8版本新增的函数式接口,与Runnable和Callable一样。假如你想定义一个回调,这个时候你又不像去专门定义一个回调接口On,就可以用Runnable或Callable等这样的接口实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A{
B b = new B();
void doHere(){
// b.runnable = new Runnable() {
// @Override
// public void run() {
// System.out.println("Do something here.");
// }
// }; 这段注释代码是不用lamda表达式的形式
b.runnable = ()-> System.out.println("Do something here.");
}
}

class B{
Runnable runnable;
private void callHere() {
runnable.run();
}
}

可以看出Lamda表达式就是对函数式接口的一种省略的写法,上面的例子中,你可以用Lamda表达式写成一行,减小代码量,也可以不用Lamda表达式,写成6行,它们的运行结果都是一样的。

讲到这可能有的同学又有点蒙了,我Lamda表达式还没搞明白,又给我讲一堆函数式接口干嘛?别急,Lamda表达式的写法也有一定的规则,怎么写Lamda表达式跟这些函数式接口有紧密的关系,那么我们先说明下Consumer、Runnable、Callable这些接口是干什么用的呢?他们的区别是什么?

不同函数式接口的区别就是参数的个数和有无返回值而已,你可以根据需要,选择使用哪个接口。

  • 例如上述例子,我只是需要类B在某个时刻通知A执行一个操作,不需要返回值,也不带参数,那就用Runnable就行,意味着这段代码注重的是运行;
  • 如果不要参数,但是需要返回值,就用Callable,其名字也意味这点;
  • 相反如果需要一个参数,但是不需要返回值,就可以用Consumer,其名字也是一样,消费,给你一个参数,你拿去用就好,不用返回值给我;
  • 同样的接口还有很多:BiFunction, Function0, Function1,…,Function22。Funtion22就是有22个参数和一个返回值(Function的意思就是即有参数又有返回值)

列几个函数式接口给大家感受下:

1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface Consumer<T> {

/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
1
2
3
4
5
6
7
8
9
10
11
@FunctionalInterface
public interface Function<T, R> {

/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}

Lamda表达式的语法规则

说完函数式接口,我们再回来说Lamda表达式的语法格式,Lamda表达式语法主要分为参数和表达式两部分:

(parameters) ->{ expression; }

其中,如果表达式只有一行,则可以省略花括号和分号;如果参数只有一个,也可以省略小括号,但是如果参数是空的,记住不能省略小括号。

例如上面的那几种函数式接口的写法就是:

1
2
3
4
5
6
7
8
9
10
11
// 1. 没有餐素,也不返回任何值,类似于Runnable的接口的Lamda表达式形式:
() -> System.out.print(s)

// 2. 不需要参数,但需要一个返回值, 类似于Callable的接口的Lamda表达式形式:
() -> 5

// 3. 接收一个参数(数字类型),有返回值,类似于Function的接口的Lamda表达式形式:
x -> 2 * x

// 4. 接受2个参数(数字),有返回值,类似于BiFunction的接口的Lamda表达式形式:
(int x, int y) -> x + y

以上就是对Java中Lamda表达式的基本知识点的理解。

关于 @FunctionalInterface 注解

Java 8为函数式接口引入了一个新注解@FunctionalInterface,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。

正确例子,没有报错:

1
2
3
4
5
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
}

错误例子,接口中包含了两个抽象方法,违反了函数式接口的定义,Eclipse 报错提示其不是函数式接口。

img

提醒:加不加 @FunctionalInterface 对于接口是不是函数式接口没有影响,该注解只是提醒编译器去检查该接口是否仅包含一个抽象方法

函数式接口里允许定义默认方法

函数式接口里是可以包含默认方法,因为默认方法不是抽象方法,其有一个默认实现,所以是符合函数式接口的定义的;

如下代码不会报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);

default void doSomeMoreWork1()
{
// Method body
}

default void doSomeMoreWork2()
{
// Method body
}
}

函数式接口里允许定义静态方法

函数式接口里是可以包含静态方法,因为静态方法不能是抽象方法,是一个已经实现了的方法,所以是符合函数式接口的定义的;

如下代码不会报错:

1
2
3
4
5
6
7
8
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
static void printHello(){
System.out.println("Hello");
}
}

函数式接口里允许定义 java.lang.Object 里的 public 方法

函数式接口里是可以包含Object里的public方法,这些方法对于函数式接口来说,不被当成是抽象方法(虽然它们是抽象方法);因为任何一个函数式接口的实现,默认都继承了 Object 类,包含了来自 java.lang.Object 里对这些抽象方法的实现;

如下代码不会报错:

1
2
3
4
5
6
7
8
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);

@Override
boolean equals(Object obj);
}
Author

calvinche

Posted on

2018-12-08

Licensed under

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×