在Lambda出现之前的函数编程中,通过单个接口表示类型,实例作为函数对象,表示函数或者要采取的动作。而使用函数对象的方式往往是通过声明匿名类,示例如下
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名类");
}
});
上面的代码中,实际传递功能的只有System.out.println(“匿名类”)这一段代码。可以看出,在Java中传递一个代码段并不容易,不能直接传递代码段,因为Java是一种面向对象语言,所以必须构造一个对象,这个对象的类需要有一个方法能包含所需的代码。这种方式是非常繁琐的,为了一行功能代码需要编写很多通用的代码行。
在Java8中,出现了Lambda表达式,将定义了执行过程的匿名内部类的创建过程进行了简化。
相关概念
函数接口
在Java8中,带有单个抽象方法的接口是特殊的,它们被称为函数接口。对于函数接口,可以有默认方法和static方法,但是抽象方法只能有一个。
Lambda表达式
Java8以后允许用Lambda表达式来表示函数接口,它是对匿名类表示的函数的简化。
lambda 表达式是一个特定规则的可传递的代码块,可以在以后调用中执行一次或多次;它代表的是一种行为,并不是执行的结果。
匿名类和Lambda表达式的区别
- 匿名类中的this指的是类本身,在Lambda中this指的是它的调用类
- 匿名类可以屏蔽包含类的变量,Lambda表达式不能
- 匿名类的类型是在初始化时确定的,Lambda表达式的类型取决于它的上下文
Lambda表达式结构
Lambda的基本语法是(parameters) -> expression或(请注意语句的花括号)(parameters) -> { statements; }
即:参数,箭头(->) 以及一个表达式。
- 参数
- 代码块
- 自由变量的值。这是指非参数而且不在代码中定义的变量。这个值在初始化之后就不能被改变,必须为final的,从函数角度来说,对于同一个函数,多次传入相同的参数,那么获取的结果应该是一致的,因此对于函数中的自由变量,应该是不变的。这里假设一个数学函数 2x + 1,x为参数,2为自由变量,那么这个2应该是一致不变化的。
示例
Function<Integer, String> f = (Integer a) -> a.toString();
- 即使lambda 表达式没有参数, 仍然要提供空括号,就像无参数方法一样
Runnable run = ()->System.out.println("执行");
- 如果可以推导出一个lambda 表达式的参数类型,则可以忽略其类型。对于需要参数的函数,一般情况下不需要指定参数类型,虚拟机会根据上下文进行类型推导,如果编译期报错,则必须加上参数类型。
Function<Integer, String> f = a -> a.toString();
::的使用
Lambda使用场景
- 使用匿名函数的地方大多都可以用Lambda表达式替代,且优先于使用匿名函数。
- 反复调用函数表达式,或者延迟调用
不能用表达式取代匿名类的场景
- 创建抽象类的实例
- 为多个抽象方法的接口创建实例
- 需要在匿名方法中引用this实例的情景,lambda表达式中this实例是对外部的引用。
Java自带函数接口使用
使用方式以Function举例:结合default方法
- Function<T, R>
Function<String, Integer> f = x -> Integer.valueOf(x);
Function<String, String> s = y -> y + "1";
Function<String, Integer> d = f.compose(s);
System.out.println(d.apply("123"));
Function<String, String> a = x -> {
System.out.println("a输出" + x);
return x + "0";
};
Function<String, String> b = x -> {
System.out.println("b输出" + x);
return x + "1";
};
Function<String, String> c = x-> {
System.out.println("c输出" + x);
return x + "2";
};
a = a.compose(b);
a = a.andThen(c);
System.out.println("输出" + a.apply("abc"));
Java自带函数接口
其他
- lambda 表达式中捕获的变量必须实际上是最终变量(effectivelyfinal。)实际上的最终变量是指,这个变量初始化之后就不会再为它赋新值。
- 函数式接口最好带有@FunctionalInterface的标注
@FunctionalInterface
public interface Function<T, R> {
...
}
- 序列化问题-参考 Lambda表达式的序列化
- 如果Lambda表达式抛出一个异常,那么抽象方法所声明的throws语句也必须与之匹配
- 为了避免使用泛型带来的装箱操作,因此增加了相关的接口,如:IntPredicate等