盒子
盒子
文章目录
  1. 前言
  2. Functional Interface
  3. default 方法
  4. static 方法
  5. Lambda 表达式初窥
    1. Lambda 表达式基本形式
    2. Target Type
    3. Lambda 和使用匿名内部类实现的区别
    4. Effectively Final 变量
    5. Method Reference
  6. 例子

Java8新特性之Lambda表达是入门

前言

我看了了一些 Lambda 表达式的文章,发现有些文章讲的很好,比如:

我也不想重复造轮子,因此本篇文章的侧重点在于总结 Lambda 表达式的核心概念以及使用 Lambda 表达式的场景举例。

Functional Interface

只包含一个方法的接口称为 Functional Interface(这里的只包含一个方法 不是只有一个方法,因为可能有 default 方法或 static 方法(这些后面会介绍),这里指的是只有一个普通抽象方法)。

interface A{
void f1();
}
@FunctionalInterface //Compile Error,因为 B 有2个抽象方法
interface B extends A{
void f2();
}

比如 Comparator 接口:

@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> reversed() {
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
}
}

解释:

  • @FunctionalInterface : 此注解只是为了表明 Comparator 接口是一个 Functional Interface(不加也没关系),当创建的接口违反了要成为 Functional Interface 的规则,则会编译错误,因此这个注解只是为了验证代码的正确性而已。因此如果你以后自己定义了一个 Functional Interface,建议在最开头写上注解 @FunctionalInterface
  • Comparator 接口只新定义了一个普通抽象方法,就是 int compare(T o1, T o2); 其他方法要么是 defaultstatic 方法(我们发现 Comparator 接口既有 default 方法,又有 static 方法,这些后面会介绍,这是 Java8 对于接口的新特性),要么是把继承自 Object 类的方法重写了一下(equals 方法)
  • Function<? super T, ? extends U> : Function 接口是 Java8 新增的 Functional Interface 之一(java.util.function 包中包含了很多内置的 Functional Interface),Function<T,R> 提供了 R apply(T t) 函数,即给定一个输入t,经过一系列转换返回一个R实例(可以想象成一个名为 apply 的盒子,输入是T类型的实例,输出是R类型的实例,具体盒子的实现可以自定义)。
  • comparing 函数中加了泛型。其中 <? super T, ? extends U> 表示 Function 中的 A 必须要是 T 的父类,B 必须是 U 的子类。

default 方法

default 方法的出现是为了降低未来对于已有接口修改的难度,即如果想对一个已有接口新加一个或多个方法,因为以前不允许在接口中加入函数实现,因此你首先必须要在接口中定义一些抽象方法,并且在实现该接口的所有类中都实现新加的方法,很麻烦对不对(比如世界上有千千万万 Java 程序员使用了 Comparator 接口,如果新版本 JDK 在 Comparator 中新定义了一个抽象方法,那是毁灭性的!千千万万的程序员就要崩溃了,估计用 Java 的人也会大大减少…);default 帮你解决这个问题,你如果想在接口中新加方法,并且不想影响实现该接口的类,只需要将他声明为 default 并实现他即可,当然实现该接口的类也可以 Overwrite 该 default 方法,当然也可以什么都不做(就像 Comparator 接口中也新加了很多 default 方法,你完全不需要改动写过的现有代码)’

这里举个例子说明 default 在 JDK 中的应用:

  • List 接口中新增了 sort 方法,自从有了这个方法,就不需要调用 Arrays.sort() 方法了。对 ArrayList 类型的实例 list 排序,只调用 list.sort() 即可。
  • Iterable 接口中新增了 forEach 方法,我们可以直接使用 list.forEach() 对每个元素做一些操作,不必再自己 for 循环了。

static 方法

Java8 开始在接口中能够声明静态方法,这里举个例子说明 static 方法出现的原因:想在接口中定义一些辅助方法,比如原本创建一个实现 Comparator 接口的对象需要 new,现在只需要 Comparator.comparing() 方法就可以创建这样一个对象,很方便。

Lambda 表达式初窥

Lambda 表达式基本形式

Lambda 表达式以 (ParamList) -> { 函数体 }(ParamList) -> 单个表达式形式存在。前者等价于创建一个实现某个 Functional Interface 的对象,并实现该接口的方法,(ParamList) 必须和 Functional Interface 需要实现的方法的参数一一对应,{ 函数体 } 就是实现方法的函数体。当接口方法的函数体中代码很简单,只需要一个 return 语句就可以搞定时,使用 Lambda 表达式的后者表现形式更简洁,单个表达式 就是 return 语句,只是少了 return 这个单词。

(ParamList) 中只有一个参数时,() 可省略,即 (Param) 等价于 Param

例子:

为了创建一个用于整数之间比较的 Comparator 对象,需要以下6行代码:

Comparator<Integer> c = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};

实际上用 Lambda 表达式1行解决:

Comparator<Integer> c = (Integer o1, Integer o2) -> o1.compareTo(o2);

Lambda 表达式是一个匿名方法,为了解决实现 Functional Interface 的匿名内部类太过麻烦的问题(本来1行代码能解决的问题用了6行)

Target Type

Lambda 表达式有一个 Target Type 的概念,意思是这个 Lambda 表达式将返回的类型。同一个 Lambda 表达式在不同的上下文中的 Target Type 是不一样的(Target Type 是通过类型推导得出),比如:

Callable<String> c = () -> “done”;

PrivilegedAction<String> a = () -> “done”;

虽然 Lambda 表达式一样,但是前者的 Target Type 是 Callable,后者的 Target Type 是 PrivilegedAction。

我们也能够通过强制类型转换的方式帮助编译器确定 Target Type:

Object o = (Runnable) () -> { System.out.println("hello")};

Lambda 和使用匿名内部类实现的区别

在 Lambda 表达式中使用 this 关键字表示的意思是外部类,而内部类中使用 this 关键字表示的意思是内部类。Lambda 表达式虽然看上去像内部类,但是本身并不依赖外部类,是独立存在的(内部类的生存依赖于外部类,从内部类的命名本身就能看出)

比如:

public class Hello {
Runnable r1 = () -> { System.out.println(this); }
Runnable r2 = () -> { System.out.println(toString()); }
Runnable r3 = new Runnable()[
public void run(){
System.out.println(this);
System.out.println(toString());
}
}
public String toString() { return "Hello"; }
public static void main(String... args) {
new Hello().r1.run();
new Hello().r2.run();
}
}

r1 和 r2 都是输出 “Hello”,r3 输出 Hello$1@5b89a773。可以看出 Lambda 表达式和一般匿名内部类有区别。

接下去再举一个例子:

int a = 1;
Runnable r1 = new Runnable() {
@Override
public void run() {
int a = 2; //OK
}
};
Runnable r2 = () -> {
int a = 2; //Compile Error
};

可以看出 Lambda 表达式的 block statement 的 scope 和外部类是一个 Level 的,而内部类不是。

Java 是 Lexical Scoping 的,程序语言可能有两种 scoping 方式:Lexical Scoping 和 dynamic Scoping。

f(){
g();
}
g(){
int a;
}

在上面的代码中,如果语言是 lexical scoping 的,则f()函数中不能访问g()的局部变量a,如果是 dynamic scoping 的,则f()函数能够访问g()的局部变量a

Effectively Final 变量

原本如果内部类要使用外部的变量,则外部变量一定要定义成 final 的,Java8 以后,如果 Lambda 表达式和内部类要使用外部的变量,则不需要一定定义成 final 的,只需要是 “Effectively final” 即可,意思是你虽然不定义成 final,但是你实际上是 final 的,即使你加了个 final 也不会编译错误。

int b = 1;
Runnable r3 = () -> {
System.out.println(b); //OK
};
Runnable r4 = new Runnable(){
public void run(){
System.out.println(b); //OK
}
};

和原来的 final 变量一样,如果要向 final ArrayList arr 的 arr 变量中加入几个元素,这是允许的,但是要改变 arr 的引用,这是不允许的。

Method Reference

这是 Lambda 表达式的特殊情况,当接口实现方法的函数体中只包含 return 语句,并且 return 语句只是调用了其他方法,没有其他额外的代码,则这时候我们”可能”可以更加简化语法。

Method Reference 的语法有很多种,因为方法有静态方法、常规方法、构造方法、父类的常规方法等,对于不同的方法,Method Reference 的语法也是不同的。

  • 静态方法:ClassName::MethodName
  • 实例上的一般方法:instance :: MethodName
  • 超类上的方法:super::MethodName
  • 任意对象的一般方法:ClassName::MethodName
  • 类的构造函数:ClassName::new
  • 数组构造函数:TypeName[]::new

比如

Comparator<Integer> c = (Integer o1, Integer o2) -> o1.compareTo(o2);

等价于

Comparator<Integer> c = Integer::compareTo;

我们能够看到上面的 Method Reference 方式(这里是任意对象的一般方法)比 Lambda 表达式更简单(虽然不那么容易理解),那么问题来了,compareTo 怎么连参数都没有指定,那怎么确定啊?其实 Method Reference 有个规则:

functional interface 的方法的参数一一按顺序对应于 Method Rerference 的方法参数。

因此 Integer::compareTo 等价于 o1.compareTo(o2)

个人观点:Method Reference 语法比较晦涩,而且给别人看也不太好理解,所以我觉得用 Lambda 表达式也挺好…是不是太没追求了…

例子

这里就以一个例子讲解下 Lambda 表达式。

class Person{
String name;
int age;
public String getName(){
return name;
}
}
//1. 最普通的方法
Comparator<Person> c1 = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.name.compareTo(o2.name);
}
};
//2. Lambda 表达式方法
Comparator<Person> c2 = (Person o1, Person o2) -> o1.name.compareTo(o2.name);
//3. 应用类型推导
Comparator<Person> c3 = (o1, o2) -> o1.name.compareTo(o2.name);
//4. 使用Comparator 的静态方法
Comparator<Person> c4 = Comparator.comparing((p) -> p.name);
//也可以写成: Comparator<Person> c4 = Comparator.comparing(p -> p.name);
//5. 使用Method Reference
Comparator<Person> c5 = Comparator.comparing(Person::getName);

上面给了5种方法创建一个 Comparator 对象,目的是对多个 Person 排序,比较的 key 是 name 属性。

  • 第一种方法是 Java8 以前的方法。
  • 第二种方法是使用 Lambda 表达式,(Person o1, Person o2) 表示实现方法的参数;o1.name.compareTo(o2.name) 等价于 return o1.name.compareTo(o2.name)
  • 第三种编译通过的原因是因为根据类型推导,编译器能够知道 Lambda 表达式的类型是 Comparator\,因此方法参数 Person 不写,编译器也知道。
  • 第四种方法使用了 Java8 中 Comparator 接口新加的 static 方法 comparing,参数接收一个 Function<T,R> 对象,根据 Comparator<Person> 得知 T 就是 Person,根据 p.name 得知 R 就是 String
  • 第五种方法使用了 Method Reference
支持一下
扫一扫,支持xiazdong