Java编程的精髓是面向接口编程,Java8最大变化正在于接口。
- 默认方法(default method)
- 函数式接口(functional interface)
默认方法颠覆了必须实现接口中方法的特性。默认方法最大的优点是复用,否则必须定义一层抽象类。
函数式接口由@FunctionalInterface 修饰的接口,关于这个注解,请看JDK文档:
An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification.
什么样的接口才能被注解为函数式接口呢?
Java8接口中的方法可以分为3类:
- 抽象方法
- 默认方法(接口中实现,由default修饰)
- 静态方法(接口中实现,由static修饰)
如果接口中有且只有一个抽象方法,则可以被注解为函数式接口。举几个例子:
java.lang.Runnable
java.util.concurrent.Callable
java.util.Comparator
java.util.function.Predicate<T>
java.util.function.Consumer<T>
java.util.function.Function<T, R>
java.util.function.Supplier<T>
java.util.function.UnaryOperator<T>
java.util.function.BinaryOperator<T>
继续追问:定义函数式接口有什么好处呢?答案是可以隐式地转化为Lambda 表达式。这里就引出了本文标题中的主角——Lambda 表达式。
Lambda 表达式是函数式编程中的一种思想,与命令式编程中的变量不同。普通的编程代表的是数值、指针或者引用,而Lambda表达式表示的是一种行为(抽象为函数)。
比如Iterable接口的实现类,经常需要循环遍历,写起来往往需要一个循环块,往往比较啰嗦。Iterable接口里增加了默认方法forEach:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
请注意上述方法中的this关键字!!在lambda中,this不是指向lambda表达式产生的那个SAM对象,而是声明它的外部对象。
action表示函数式编程接口Consumer,从代码中可以猜出,Consumer的唯一抽象方法是accept。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
比如有一个List,现在要遍历打印其中每一个元素,可以写成:
List<Integer> list = Arrays.asList(0, 1, 2, 3);
Consumer<Integer> consumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
};
list.forEach(consumer);
看起来还是嗦螺,这时Lambda 表达式就有了用武之地,用它来代替函数式接口的实现类。
Consumer<Integer> consumer = integer -> System.out.println(integer);
list.forEach(consumer);
或者
list.forEach(integer -> System.out.println(integer));
或者
list.forEach(System.out::println);
到这里,可以看出,函数式接口表示一套行为,而函数式接口可以用Lambda 表达式简化,所以Lambda 表达式又被称为匿名函数,或者也可以理解为一段带有输入参数的可执行语句块。
函数有几个要素:修饰符,返回值,函数名,参数,方法体。在调用函数的时候需要明确方法名和参数,而使用Lambda 表达式则可以省略方法名。通过几个例子来学习下的写法。
函数的参数需要写在圆括号()里,函数体需要写在花括号{}里。
Lambda 表达式的基本形式是(arg1, arg2 ...)-> { statement1; statement2; ... }
如果函数参数只有一个,则可以省略圆括号。
如果方法体内只有一行语句,则可以省略花括号和return关键字。
当没有参数时,前面的( )一般不能省略。
Runnable noArguments = () -> System.out.println("Hello World");
如果省略前面的( ),则可以写成下面的形式,不过功能非常单一,只能打印换行符。
Runnable noArguments = System.out::println
还有一个闭包问题,就是Lambda 表达式要使用方法内的变量时,需要用final修饰或者没有改变改变过值的变量。
小结:
- Java8对接口有哪些改变?
- 函数式接口是什么?
- Lambda 表达式是什么?怎么写?有哪些常用的?