Lambda 表达式是在Java 8中引入的,并且成为了Java 8亮点。它使得功能性编程变得非常便利,极大地简化了开发工作。
让我们从最简单的例子开始,来学习如何对一个string列表进行排序。我们首先使用Java 8之前的方法来实现:
List names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
静态工具方法Collections.sort接受一个list,和一个Comparator接口作为输入参数,Comparator的实现类可以对输入的list中的元素进行比较。通常情况下,你可以直接用创建匿名Comparator对象,并把它作为参数传递给sort方法。
除了创建匿名对象以外,Java 8 还提供了一种更简洁的方式,Lambda表达式。
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
你可以看到,这段代码就比之前的更加简短和易读。但是,它还可以更加简短:Collections.sort(names, (String a, String b) -> b.compareTo(a));
只要一行代码,包含了方法体。你甚至可以连大括号对{}和return关键字都省略不要。不过这还不是最短的写法:
Collections.sort(names, (a, b) -> b.compareTo(a));
Java编译器能够自动识别参数的类型,所以你就可以省略掉类型不写。可以看出:相对于之前使用匿名内部类的方式,Java8的lambda表达式更精简。
Lambda表达式用处
1、凡是有匿名内部类的地方,都可以用Lambda表达式简化。
2、Java8 Stream集合之流式操作,方法参数均为Lambda表达式。
Lambda语法解析
我们在此抽象一下lambda表达式的一般语法:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
Lambda表达式的定义
Lambda表达式是:一段带有输入参数的可执行语句块,它不用被绑定到一个标识符上,即不需要赋值给一个变量,并且它将来可能被调用。
上面的例子(String a, String b) -> {return b.compareTo(a)}
其实就是一个lambda表达式,并且还可以简写,省略参数类型和return,因此就成为最后的精简版:
(a, b) -> b.compareTo(a)
一个Lambda表达式具有下面这样的语法特征。它由三个部分组成:
- 第一部分为一个括号(),里面用逗号分隔的参数列表,参数即函数式接口里面方法的参数;
- 第二部分为一个箭头符号:->;
- 第三部分为一个大括号{},里面是多条语句构成的方法体,可以是表达式和代码块。
简写版本说明
- 参数类型省略,编译器都可以从上下文环境中推断出lambda表达式的参数类型。
- 当lambda表达式的参数个数只有一个,可以省略小括号。
- 当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号。
- 如果没有参数则只需(),例如 Thread 中的 run 方法就没有参数传入,当它使用 Lambda 表达式后:
Thread t = new Thread(() -> { System.out.println("Hello from a thread in run");
下面列举了Lambda表达式的几个最重要的特征:
● 可选的类型声明:你不用去声明参数的类型。编译器可以从参数的值来推断它是什么类型。
● 可选的参数周围的括号:你可以不用在括号内声明单个参数。但是对于很多参数的情况,括号是必需的。
● 可选的大括号:如果表达式体里面只有一个语句,那么你不必用大括号括起来。
● 可选的返回关键字:如果表达式体只有单个表达式用于值的返回,那么编译器会自动完成这一步。若要指示表达式来返回某个值,则需要使用大括号。
函数式接口
Lambda表达式如何匹配Java的类型系统?语言的设计者们思考了很多如何让现有的功能和lambda表达式友好兼容。于是就有了函数式接口这个概念。函数式接口是一种只有一个方法的接口,函数式接口可以隐式地转换成 Lambda 表达式。
每一个lambda都能够通过一个特定的接口,与一个给定的类型进行匹配。一个所谓的函数式接口必须要有且仅有一个抽象方法声明。每个与之对应的lambda表达式必须要与抽象方法的声明相匹配。
函数式接口的重要属性是:我们能够使用 Lambda 实例化它们,Lambda 表达式让你能够将函数作为方法参数,或者将代码作为数据对待。Lambda 表达式的引入给开发者带来了不少优点:在 Java 8 之前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性很差,Lambda 表达式的应用则使代码变得更加紧凑,可读性增强。
要使用 Lambda 表达式,需要定义一个函数式接口,这样往往会让程序充斥着过量的仅为 Lambda 表达式服务的函数式接口。为了减少这样过量的函数式接口,Java 8 在 java.util.function 中增加了不少新的函数式通用接口,即内置函数式接口。
内置函数式接口
Predicates预言式接口
Predicate
Predicate是一个布尔类型的函数接口,该函数只有一个输入参数。Predicate接口包含了多种默认方法,用于处理复杂的逻辑动词(and, or,negate)
Predicate predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
Predicate nonNull = Objects::nonNull;
Predicate isNull = Objects::isNull;
Predicate isEmpty = String::isEmpty;
Predicate isNotEmpty = isEmpty.negate();
Functions功能式接口
Function
Function接口接收一个参数,并返回单一的结果。默认方法可以将多个函数串在一起(compse, andThen)。
Function toInteger = Integer::valueOf;
Function backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"
Consumers
Consumer
Consumer代表了在一个输入参数上需要进行的操作。
Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
Suppliers
Supplier接口产生一个给定类型的结果。与Function不同的是,Supplier没有输入参数。
Supplier personSupplier = Person::new;
personSupplier.get(); // new Person
Comparators
Comparator接口在早期的Java版本中非常著名。Java 8 为这个接口添加了不同的默认方法。
Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0
Lambda表达式访问其外部变量
以前java的匿名内部类在访问外部变量的时候,外部变量必须用final修饰。在java8对这个限制做了优化,可以不用显示使用final修饰,但是编译器隐式当成final来处理。
可以得到以下结论:
● 可访问 static 修饰的成员变量,如果是 final static 修饰,不可再次赋值,只有 static 修饰可再次赋值;
● 可访问表达式外层的 final 局部变量(不用声明为 final,隐性具有 final 语义),不可再次赋值。
Java 8接口的增强
Java 8 对接口做了进一步的增强。在接口中可以添加使用 default 关键字修饰的非抽象方法。还可以在接口中定义静态方法。如今,接口看上去与抽象类的功能越来越类似了。
默认方法
Java 8 还允许我们给接口添加一个非抽象的方法实现,只需要使用 default 关键字即可,这个特征又叫做扩展方法。在实现该接口时,该默认扩展方法在子类上可以直接使用,它的使用方式类似于抽象类中非抽象成员方法。但扩展方法不能够重载 Object 中的方法。例如:toString、equals、 hashCode 不能在接口中被重载。
静态方法
在接口中,还允许定义静态的方法。接口中的静态方法可以直接用接口来调用。