Java 8 新特性 - Lambda 表达式
Java 8 新特性
Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。
Java8 新增了非常多的特性,我们主要讨论以下几个:
- Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数)传递进方法中。
- 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
- Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Date Time API − 加强对日期与时间的处理。
- 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
- Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
更多的新特性可以参阅官网:What’s New in JDK 8
什么是 lambda 表达式
lambda 表达式 本质上是一个匿名方法,是匿名类(函数接口)的一种简单写法。
lambda 表达式 允许把函数作为一个方法的参数(函数作为参数)传递进方法中。
语法
(parameters) -> expression
或
(parameters) -> { statements; }
以下是lambda表达式的重要特征:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
Lambda 表达式实例
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
Lambda 表达式的类型检查、类型推断
Lambda 表达式的类型,叫做“目标类型(target type)”。Lambda 表达式的目标类型是 函数式接口详解,具体的哪个函数式接口需要从上下文推断,例如:
例子 1 :
List<String> list = new ArrayList<>();
list.add("lxyng");
// add(...)
list.forEach(s -> System.out.println(s));
/*
此处 lambda 表达式的目标类型是:Consumer<T>,
Consumer<T> 是一个函数式接口,其中的抽象方法accept(T t),接收一个参数,没有返回值;
s -> System.out.println(s), 的参数类型是 String, 参数是 s, 主体是 System.out.println(s), 无返回值 void
*/
//为什么? 查看源码可知:
interface List<E> extends Collection<E>
interface Collection<E> extends Iterable<E>
public interface Iterable<T> {
default void forEach(Consumer<? super T> action) { ... }
}
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
//函数式接口里可以有默认方法,Object 方法,静态方法
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
例子 2 :
// Lambda 表达式
Thread thread = new Thread(() -> System.out.println("使用 Lambda 表达式创建线路"));
/*
() -> System.out.println("使用 Lambda 表达式创建线路")
此表达式的目标类型是:Runnable, 没有参数,没有返回值
*/
//Thread Runnable 类部分源码
public class Thread(Runnable runnable){
...
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
...
}
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
你可以用一个 Lambda 表达式为一个函数式接口赋值:
Runnable r1 = () -> {System.out.println("Hello Lambda!");};
然后再赋值给一个Object:
Object obj = r1;
但却不能这样干:
//ERROR! Object is not a functional interface!
Object obj = () -> {System.out.println("Hello Lambda!");};
必须显式的转型成一个函数接口才可以:
Object o = (Runnable) () -> { System.out.println("hi"); };
一个 Lambda 表达式只有在转型成一个函数式接口后才能被当做Object使用。所以下面这句也不能编译:
System.out.println( () -> {} ); //错误! 目标类型不明
必须先转型:
System.out.println( (Runnable)() -> {} ); // 正确
假设你自己写了一个函数接口,长的跟Runnable一模一样:
@FunctionalInterface
public interface MyRunnable {
public void run();
}
那么
Runnable r1 = () -> {System.out.println("Hello Lambda!");};
MyRunnable2 r2 = () -> {System.out.println("Hello Lambda!");};
都是正确的写法。这说明一个 Lambda 表达式可以有多个目标类型(函数接口),只要函数匹配成功即可,但需注意一个 Lambda 表达式必须至少有一个目标类型。
JDK预定义了很多函数接口以避免用户重复定义。
java.lang.Runnable,
java.awt.event.ActionListener,
java.util.Comparator,
java.util.concurrent.Callable
java.util.function包下的接口,如Consumer、Predicate、Supplier等
最典型的是Function
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
这个接口代表一个函数,接受一个T类型的参数,并返回一个R类型的返回值。
Consumer,跟Function的唯一不同是它没有返回值。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Predicate,用来判断某项条件是否满足。经常用来进行筛滤操作:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
综上所述,一个λ表达式其实就是定义了一个匿名方法,只不过这个方法必须符合至少一个函数接口。
Lambda 表达式的使用
匿名内部类,回调函数等
/** 创建线路的两种写法 */
//匿名内部类写法
Thread myThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用内部类创建线路");
}
});
// Lambda 表达式
Thread thread = new Thread(() -> System.out.println("使用 Lambda 表达式创建线路"));
// 使用匿名内部类
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello world !");
}
};
// 使用 lambda 表达式
Runnable runnable2 = () -> System.out.println("Hello world !");
// 直接调用 run 方法(没开新线程哦!)
runnable1.run();
runnable2.run();
Thread 类构造方法接收一个 Runnable 接口作为参数,Runnable 是一个函数接口,只有一个 run() 方法,使用 Lambda 表达式比匿名内部类更简洁直观, Lambda 表达式的目标类型是 Runnable, Lambda 表达的目标类型最终是由于调用者决定的,调用者的参数类型决定表达式的目标类型。
Lambda 表达式与集合类批处理操作
集合类的批处理操作。是Java8的另一个重要特性,它与λ表达式的配合使用乃是Java8的最主要特性
for(Object o: list) {
System.out.println(o);
}
//可以写成:
list.forEach(o -> {System.out.println(o);});
Java8为集合类引入了另一个重要概念:流(Stream 详解), Lambda 表达式与 Stream 相结合,可以使函数编程式代码更简洁,美观。
list.forEach(o -> {System.out.println(o);});
public class Person {
private String name;
private int gender, age; //gender 1:男, 0:女
public Person(String name, int gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
// Getter and Setter
}
List<Person> team1= new ArrayList<Person>() {
{
add(new Person("Elsdon", 1, 22));
add(new Person("Tamsen", 0, 15));
add(new Person("Floyd", 1, 33));
add(new Person("Sindy", 1, 16));
add(new Person("Vere", 0, 44));
}
};
List<Person> team2= new ArrayList<Person>() {
{
add(new Person("Jarrod", 1, 33));
add(new Person("Victor", 0, 18));
add(new Person("Tori", 1, 25));
add(new Person("Osborne", 0, 10));
}
};
System.out.println("所有队员的姓名:");
team1.forEach((p) -> System.out.printf("%s %s; ", p.getName(), p.getAge()));
team2.forEach((p) -> System.out.printf("%s %s; ", p.getName(), p.getAge()));
System.out.println("下面是年龄18岁以上的2位男队员,结果按年龄排序:")
team1.stream()
.filter((p) -> (p.getAge() > 18))
.filter(p -> (p.getGender = 1))
.limit(2) //limit(2) 限制结果个数为2, 详细的 Stream<T> API 请看另一篇文章
.sorted( (p, p2) -> (p.getAge() - p2.getAge()) ) //结果根据年龄排序
.forEach((p) -> System.out.printf("%s %s; ", p.getName(), p.getAge()));
Lambda 表达式 与 Stream 流相结合的函数式编程,或者说是流式编程,代码、逻辑都很直观清晰。详细的 Stream 文章请移步流(Stream 详解)。