Java8新特性(一) Lambda表达式与函数式接口
一. 基本概念
Lambda表达式是Java 8中引入的一个重要的新特性,该表达式提出了一种新的语法规则,用于对某些(函数式接口)匿名内部类的书写方式进行简化。除此之外,Lambda表达式是函数式编程思想的一个重要体现,它允许我们通过表达式的形式来定义和传递功能,并且更加关注数据本身。以线程创建为例,Lambda表达式的基本语法规则如下:
(参数列表)->{ 方法体(代码); }
//1. Runnable匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread running...");
}
}).start();
//2. Runnable Lambda表达式
new Thread(()->{
System.out.println("Thread running...");
}).start();
二. 语法特性
1. 限制条件
(1) Lambda表达式只能用于简化函数式接口的匿名内部类的实现,即只含有一个抽象方法的接口(可以包含默认default方法),或者是声明了 @FunctionalInterface 注解的接口。注意:Lambda表达式不能够简化类或者抽象类的创建,如果试图使用Lambda表达式去创建一个类或者抽象类将会报错如下 ”Target type of a lambda conversion must be an interface“;
(2) Lambda表达式可以看作是一种特殊的匿名内部类的实现,也被称为匿名函数,匿名内部类的所有使用限制都会对其生效;
2. 省略规则
(1) 参数列表中的参数类型可以省略,JVM编译器可以通过上下文自动做"类型推断",但是所有参数类型是否省略必须统一;
参数列表中若只有一个参数,则可以省略参数列表的小括号(同时必须省略参数类型);
参数列表中若没有参数(即无参抽象方法),则参数列表的小括号不可省略;
(2) 若方法体中只有一句代码,则方法体的大括号可以省略(包括结尾分号);若该代码是return语句,则在省略大括号时必须进一步去掉return关键字;
(3) 若方法体由多句代码组成,则方法体格式必须完整;
// 1.保留参数类型
(int x,int y) -> {
int res = x + y;
return res;
}
// 2.省略参数类型
(x,y) -> {
int res = x + y;
return res;
}
// 3.只有一个参数(无返回值)
x -> {
int res = x * x;
System.out.print(res);
}
// 4.无参数
() -> {System.out.print("无参表达式");}
// 5.单行方法体-返回其2倍的值
x -> 2 * x
// 6.单行方法体-返回他们的差值
(x, y) -> x – y
// 7.单行方法体-无返回值
(String str) -> System.out.print(str)
3.变量捕获
类似于匿名内部类,Lambda表达式同样可以捕获外部作用域中的变量数据,并可以直接使用这些变量而无需声明。Lambda表达式可以捕获的变量数据类型如下:
实例变量(Instance Variables):若Lambda表达式定义在非静态域中,则可以捕获并访问包含它的实例的成员实例变量,并可以使用OuterClass.this关键字来指明外部类的引用;
静态变量(Static Variables):Lambda表达式定义在静态/非静态域中都可以随时捕获并访问包含它的类的成员静态变量;
方法参数(Method Parameters):Lambda表达式可以捕获并访问包含它的方法的参数,本质上与本地局部变量一致;
本地变量(Local Variables):Lambda表达式可以捕获并访问声明为final的本地局部变量。从Java 8开始,final关键字可以省略(自动添加),但该变量的值实际上不可修改(该表达式域内外均不可修改);
在Lambda表达式中捕获的成员变量(实例变量、静态变量)与其在外部作用域中发生的改变将会同步;但方法参数和本地局部变量经由Lambda表达式访问之后,就会添加final修饰,此时变量的值在表达式域的内外均已不可修改。
public class LambdaDemo01 {
private int instanceVariable = 10;
private static int staticVariable = 20;
public static void staticMethod(int methodVariable){
int localVar = 30;
// 静态域中 Lambda表达式捕获外部变量
Runnable runnable = () -> {
staticVariable += 1; //修改外部类静态成员
// methodVariable += 1; //error: Variable used in lambda expression should be final or effectively final
// localVar += 1; //error: Variable used in lambda expression should be final or effectively final
System.out.println("Static variable: " + staticVariable);
System.out.println("Method variable: " + methodVariable);
System.out.println("Local variable: " + localVar);
};
runnable.run();
}
public void instanceMethod(int methodVariable){
int localVar = 30;
// 非静态域中 Lambda表达式捕获外部变量
Runnable runnable = () -> {
instanceVariable += 1; //修改外部类实例成员
System.out.println("Instance variable: " + LambdaDemo01.this.instanceVariable); //LambdaDemo01.this外部类引用
System.out.println("Static variable: " + staticVariable);
System.out.println("Method variable: " + methodVariable);
System.out.println("Local variable: " + localVar);
};
instanceVariable += 2; //synchronous change
runnable.run();
}
public static void main(String[] args) {
LambdaDemo01.staticMethod(40);
LambdaDemo01 lambdaDemo01 = new LambdaDemo01();
lambdaDemo01.instanceMethod(40);
}
}
4.应用场景
4.1 简化函数式接口
Lambda表达式提供了一种更简洁、更便捷的方式来声明匿名函数(函数式接口的实例),这使得Java语言更加灵活,是Java函数式编程的实现基础,另一方面它也使得代码更为紧凑、简洁,并提高了可读性、减少了代码冗余。
4.2 配合Stream API
Java 8新特性中的Stream API(java.util.stream) 是一种抽象流式接口,其是一个来自数据源的元素队列并支持一系列聚合操作,比如过滤、筛选、映射、遍历等;Stream可以以一种声明的方式处理集合与数据,是函数式编程模式的重要体现。Lambda表达式常用于配合Stream API实现各种数据处理操作,使得数据处理更加简洁、直观。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
/**
* Returns a stream consisting of the elements of this stream that match
* the given predicate.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* predicate to apply to each element to determine if it
* should be included
* @return the new stream
*/
Stream<T> filter(Predicate<? super T> predicate);
/**
* Returns a stream consisting of the results of applying the given
* function to the elements of this stream.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param <R> The element type of the new stream
* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function to apply to each element
* @return the new stream
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
//...
}
4.3 简化集合操作
在Java 8及以上的版本中,为了能够让Lambda表达式和Java的集合类集更好的配合使用,集合Collection
接口新增了一些默认方法,例如forEach()
、removeIf()
和stream()
等,这使得使用Lambda表达式更加方便、数据处理更加高效。此处以集合遍历forEach()
操作为例,介绍如下:
(1)List
//1.List forEach 源码
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
//2.example
List<String> names = Arrays.asList("Alice", "Bob", "Tom","Krito");
// 参数不固定为t,其他参数名称也可以(name)
names.forEach(name -> {
int len = name.length();
System.out.println(name + " with length = " + len);
});
(2)Map
//1.Map forEach 源码
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
//2.example
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 25);
ages.put("Bob", 22);
ages.put("Tom", 27);
ages.put("Krito",25);
// 双参数
ages.forEach((key, value) -> System.out.println(key + " : " + value));
三. 实现原理
我们知道内部类经过编译器编译之后会在字节码中生成一个实际的类,那么这个类就要遵循JVM的一系列规定,执行包括加载,验证,准备,初始化等流程,这个过程往往代价是比较昂贵的。因此,Lambda表达式不仅是简化了匿名内部类的实现,还要在效率和开销方面进行优化。
与内部类实现原理不同的是,Lambda表达式并不会在编译期间生成对应的class字节码文件,而是编译时在外部类中生成了一个以
lambda$
为前缀的私有静态方法,用于承载Lambda表达式中的执行逻辑;除此之外,对于类的实现,Lambda表达式是在运行期间,通过invokedynamic指令来动态生成接口实现类并链接调用外部类中生成的私有静态方法。实际上,Lambda写法除了上面提到的转化为静态方法执行之外,还有一种转化为静态内部类的实现,此处不再赘述。Lambda表达式的实现简单复现如下。
public class LambdaDemo02 {
public static void main(String[] args) {
BinaryOperator<Integer> lambda = (a,b) -> a + b;
// 简单等价类
//BinaryOperator<Integer> lambda = new LambdaBinaryOperator();
}
//编译期间生成lambda逻辑方法
private static Integer lambda$main$0(Integer a,Integer b){
return a+b;
}
//运行期间动态生成并链接
static final class LambdaBinaryOperator implements BinaryOperator<Integer> {
@Override
public Integer apply(Integer a, Integer b) {
return lambda$main$0(a,b);
}
}
}
四. 函数式接口
只包含一个抽象方法的接口被称之为函数式接口,当然该接口也可以包含其他非抽象方法(比如默认方法default)。函数式接口可以通过添加 @FunctionalInterface 注解进行标识,该注解可以自动检查并限制函数式接口的格式声明,但是无论是否加上该注解只要接口中仅有一个抽象方法,就都是函数式接口。
除了自定义实现函数式接口,JDK 1.8 API 包含了很多内置的函数式接口,并为它们都添加了 @FunctionalInterface 注解,以用来支持 Lambda 表达式的使用。核心内置接口主要包括Consumer消费型接口、Function函数型接口、Predicate判断型接口和Supplier供给型接口,介绍如下。
1. Consumer消费型接口
消费型接口的抽象方法特点是,有形参但返回值类型是void,用于对方法中传入的参数进行消费操作;其源码与常见的子接口如下。
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
2. Function函数型接口
函数型接口的抽象方法特点是,既有参数又有返回值,用于对方法中传入的参数进行计算或转换,并返回结果;其源码与常见的子接口如下。
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
/**
* Returns a composed function that first applies the {@code before}
* function to its input, and then applies this function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of input to the {@code before} function, and to the
* composed function
* @param before the function to apply before this function is applied
* @return a composed function that first applies the {@code before}
* function and then applies this function
* @throws NullPointerException if before is null
*
* @see #andThen(Function)
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
//...
}
3. Predicate判断型接口
判断型接口的抽象方法特点是,既有参数又有返回值,但返回值是boolean类型,用于对方法中传入的参数条件进行判断,并返回其判断结果;其源码与常见的子接口如下。
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/**
* Returns a predicate that represents the logical negation of this
* predicate.
*
* @return a predicate that represents the logical negation of this
* predicate
*/
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
//...
}
4. Supplier供给型接口
供给型接口的抽象方法特点是,无参但有返回值,用于在方法中生成创建并返回对象或数据;其源码与常见的子接口如下。
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}