Java Lambda 表达式

概念

  • Lambda
    大写 Λ,小写 λ。读音:lan b(m) da(兰木达)['læmdə]
  • Lambda 表达式
    Lambda expression :基于数学中的 λ 演算得名,直接对应于其中的 Lambda 抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数,在 Java 中又称为闭包或匿名函数。而 λ演算 是函数式编程的基础,所以 Lambda 表达式具有部分函数式语言的特征。特点就是简()单()优()雅。

重要特性

Lambda 表达式的语法

1
2
3
(params) -> expression
(params) -> statement
(params) -> { statements }

示例:

1
2
3
4
5
6
7
8
9
10
11
// Java 8 之前匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Before Java8, too much code for too little to do");
}
}).start();

// Java 8 Lambda
new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!")
).start();

其他示例:

  • 参数可以是零个或多个
  • 参数类型可指定,可省略(根据表达式上下文推断)
  • 参数包含在圆括号中,用逗号分隔
  • 表达式主体可以是零条或多条语句,包含在花括号中
  • 表达式主体只有一条语句时,花括号可省略
  • 表达式主体有一条以上语句时,表达式的返回类型与代码块的返回类型一致
  • 表达式只有一条语句时,表达式的返回类型与该语句的返回类型一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 零个
()-> System.out.println("no argument");

// 一个
x->x+1

// 两个
(x,y)->x+y

// 省略参数类型
View.OnClickListener oneArgument = view->Log.d(TAG,"one argument");
// 指定参数类型
View.OnClickListener oneArgument = (View view)->Log.d(TAG,"one argument");

// 多行语句
// 返回类型是代码块返回的 void
View.OnClickListener multiLine = (View view)->{
Log.d(TAG,"multi statements");
Log.d(TAG,"second line");
}

// 返回类型是表达式主体,语句的返回类型 int
(int x)->x+1

方法引用 Method Reference

格式:类名::方法名。注意:只需要写方法名,不需要写括号。方法引用来简写 Lambda 表达式中已经存在的方法。示例:

1
2
3
4
5
6
7
8
9
10
11
12
// Java 8 之前使用 for-each 遍历
List features = Arrays.asList("Lambdas", "Default Method", "Stream API");
for (String feature : features) {
System.out.println(feature);
}

// Java 8 Lambda 表达式
List features = Arrays.asList("Lambdas", "Default Method", "Stream API");
features.forEach(n -> System.out.println(n));

// 使用方法引用简化 Lambda 表达式
features.forEach(System.out::println);

方法引用的四种形式:

  • 引用静态方法
    ContainingClass::staticMethodName
  • 引用某个类型的任意对象的实例方法
    ContainingType::methodName
  • 引用构造方法
    ClassName::new
  • 引用某个对象的实例方法
    containingObject::instanceMethodName

函数式接口 FI

函数式接口:指仅含有一个抽象方法的接口。首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。以 @Functionalnterface 标注,简称 FI 。但是加不加 @FunctionalInterface 对于接口是不是函数式接口没有影响,该注解只是提醒编译器去检查该接口是否仅包含一个抽象方法。

1
2
3
4
// 首先是接口,其次只有一个抽象方法  
interface OnClickListener {
void onClick(View v);
}
  • 引申
    Java 8 中引入了新特性,向接口中引入默认方法静态方法,以此来减少抽象类和接口之间的差异。也就是说新特性中,接口可以和抽象类一样,具有默认或静态的方法实现,如示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
文件:java.util.function.Predicate.java
@FunctionalInterface
public interface Predicate<T> {
// 只包含一个抽象方法,所以是函数式接口
boolean test(T t);
...
// 默认方法,关键字 default
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
// 静态方法,关键字 static
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}

以前如果接口中新增方法,需要修改所有的实现类增加方法的对应实现。 Java 8 的这个新特性就是主要解决这类问题的,放到接口中后,实现类不需要重写,确保老版本的代码能够兼容。

  • Lambda 实现函数式接口
    Lambda 通过该方式,大大简化了代码量,也是最基本的表达式之一。
1
2
3
4
5
6
7
8
9
10
// Java 8 之前匿名内部类
button.setOnClickListener(new View.onOnClickListener(){
@Override
public void onClick(View v){
v.setText("abc");
}
});

// Lambda 表达式简化函数式接口
button.setOnClickListener(v -> v.setText("abc"));

this 关键字

Lambda 表达式即将正式取代 Java 代码中的匿名内部类,他们在关键字 this 上有很大不同:匿名类的 this 关键字指向匿名类,而 Lambda 表达式的 this 关键字指向包围 Lambda 表达式的类。

局部变量

Lambda 表达式对局部变量有个限制,那就是只能引用 final 局部变量,这就是说不能在 Lambda 表达式内部修改定义在域外的变量。示例:

1
2
3
4
5
6
7
8
9
10
List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7});
int factor = 2;
primes.forEach(element -> { factor++; });

// 编译报错
Compile time error : "local variables referenced from a lambda
expression must be final or effectively final"

// 但是可以直接访问
primes.forEach(element -> { System.out.println(factor*element); });

集合的流式操作

流式操作: JDK8Stream 是一个受到函数式编程和多核时代影响而产生的东西。java.util.stream 包,实现了集合的流式操作,流式操作包括集合的过滤,排序,映射等功能。根据流的操作性,又可以分为串行流和并行流。根据操作返回的结果不同,流式操作又分为中间操作和最终操作。大大方便了我们对于集合的操作:

  • 最终操作:返回一特定类型的结果
  • 中间操作:返回流本身

MapReduce 应用

MapReduce 操作是函数式编程的核心操作,map 将集合类(例如列表)元素进行转换,也就是将参数转换成想要的返回值;reduce 函数可以将所有值合并成一个,又被称为折叠操作。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Java 8 之前,为每个订单加上12% 的税
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double total = 0;
for (Integer cost : costBeforeTax) {
double price = cost + .12*cost;
total = total + price;
}
System.out.println("Total : " + total);

// Java 8 Lambda 表达式
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double bill = costBeforeTax.stream()
.map((cost) -> cost + .12*cost)
.reduce((sum, cost) -> sum + cost)
.get();
System.out.println("Total : " + bill);

过滤器 filter

过滤是在大规模集合上的一个常用操作,而使用 Lambda 表达式和流 API 过滤大规模数据集合非常简单。示例:

1
2
3
4
5
6
// 创建一个字符串列表,每个字符串长度大于 2
List<String> filtered = strList.stream()
.filter(x -> x.length()> 2)
.collect(Collectors.toList());
System.out.printf("Original List : %s, filtered list : %s %n",
strList, filtered);

参考文档

0%