lambda介绍
lambda是JDK8新增的功能,它增加了新的语法元素,是Java语言的表达能力得以提升,并流线化了一些常见结构的实现方式。其次,lambda表达式的加入也导致API库中增加了新的功能,包括利用多核环境的并行处理功能变得更加容易,以及支持对数据执行管道操作的新的流API。
lambda表达式本质上就是一个匿名方法。这个方法不是独立执行的,而是用于实现由函数式接口定义的另一个方法。
lambda表达式语法
我们先来看一个简单的lambda表达式:
() -> 1.5
()
表示方法的参数,这里表示没有参数,->
表示lambda要执行的动作,也就是方法体中的内容,1.5
表示返回值,省略了return
。这个lambda表达式等同于下面这个方法:
double getValue() {
return 1.5;
}
再来看一个稍微复杂一点的lambda表达式:
(int n) -> (n % 2) == 0
这里(double n)
表示需要一个double类型的参数,方法体内判断n是否一个偶数
(n % 2) == 0
的执行结果是一个boolean类型的值,也作为lambda表达式的返回值。等同于下面这个方法:
boolean test(int n) {
return (n % 2) == 0;
}
函数式接口
函数式接口是仅包含一个抽象方法的接口。一般来说,这个方法指明了接口的目标用途。因此,函数式接口通常表示单个动作,例如Runnable是一个函数式接口。
下面是函数式接口的一个例子:
interface MyNumber {
double getValue();
}
上面讲到lambda表达式实际上就是对应一个方法实现,我们知道在Java中不能单独定义方法,方法一定要定义在类(或接口)中。所以lambda表达式需要关联一个函数式接口,而lambda表达式等同于一个实现该函数式接口的内部类的方法。
我们先来看下要实现一个函数式接口的内部类如何做:
MyNumber myNum = new MyNumber() {
@Override
public double getValue() {
return 1.5;
}
};
上面这个内部类可以用lambda表达式代替:
MyNumber myNum = () -> 1.5;
完整的lambda表达式
事实上完整的lambda是这样的:
(int n) -> {
return (n % 2) == 0;
}
这样来看的是不是就与方法特别像了?但是这样写起来与定义方法并没有多大的区别,一样的繁琐。于是lambda表达式有一些重要特性来简化:
- 可选类型声明 :不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号 :一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号 :如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字 :如果主体只有一个表达式返回值则编译器会自动返回值;但是如果存在大括号,则需要指定明表达式返回了一个数值。
简化最终也就是上面看到的两个例子的样子。我们来多看几个例子:
//不需要参数,返回值为 5
() -> 5
//接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
//接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
//接收2个int型整数,返回他们的和
(int x, int y) -> x + y
//接受一个 string 对象,并在控制台打印,不返回任何值
(String s) -> System.out.print(s)
泛型函数式接口
lambda表达式自身不支持泛型,但可以通过关联泛型的函数式接口表现出泛型的特性。
我们先声明一个泛型接口:
interface SomeFunc<T> {
T func(T t);
}
接着使用lambda表达式定义两个实现:
public class Test {
public static void main(String[] args) {
SomeFunc<String> reverse = str -> {
StringBuilder sb = new StringBuilder();
for (int i = str.length() - 1; i >= 0; i--) {
sb.append(str.charAt(i));
}
return sb.toString();
};
System.out.println(reverse.func("abcdefg"));
SomeFunc<Integer> factorial = n -> {
int result = 1;
for (int i = 1; i <= n; i++) {
result = i * result;
}
return result;
};
System.out.println(factorial.func(5));
}
}
SomeFunc接口的func()方法的参数和返回值都是泛型T,表示它可以接收任意参数。reverse和factorial实际上是两个不同的lambda表达式,一个String类型,另一个是Integer类型,他们都可以关联到SomeFunc接口。
作为参数传递lambda表达式
作为参数传递lambda表达式是一种常见且强大的用途,着极大地增强了Java的表达能力。
为了将lambda表达式作为参数传递,我们在接收lambda表达式的方法参数声明成函数式接口的类型:
public static <T> T transfer(T original, SomeFunc<T> func) {
return func.func(original);
}
这个方法接收两个参数,一个任意对象,一个函数式接口用于接收lambda表达式,这个函数式接口就是上文的SomeFunc接口,func方法接收一个任意对象,返回该对象。通过传入不同的lambda,我们可以实现对该对象的的任意变形操作。
public static void main(String[] args) {
String s = "abcdefg";
System.out.println(transfer(s, str -> {
StringBuilder sb = new StringBuilder();
for (int i = str.length() - 1; i >= 0; i--) {
sb.append(str.charAt(i));
}
return sb.toString();
}));
System.out.println(transfer(s, str -> str.toUpperCase()));
}
这里我们传入两个不同的lambda实现对同一个String的不同变形操作。
方法引用
有时候我们作为参数传递的lambda表达式中可能仅仅是调用其他类的方法,例如这样:
public class Test {
public interface SomeFunc<T> {
T func(T t);
}
public static String func(String str) {
StringBuilder sb = new StringBuilder();
for (int i = str.length() - 1; i >= 0; i--) {
sb.append(str.charAt(i));
}
return sb.toString();
}
public static <T> T transfer(T original, SomeFunc<T> func) {
return func.func(original);
}
public static void main(String[] args) {
String s = "abcdefg";
System.out.println(transfer(s, str -> Test.func(str)));
}
}
这种情况下,transfer(s, str -> Test.func(str))
可以简写成transfer(s, Test::func)
表示直接把Test的静态方法func作为lambda表达式传递。::
是JDK8新增的分隔符,专门用于此目的。
也就是说要创建静态方法引用,使用如下语法:
ClassName::methodName
如果是实例方法引用:
objRef::methodName
还有一个特殊的情况,前文中我们有一个这样的使用 transfer(s, str -> str.toUpperCase())
,其实可以用 transfer(s, String::toUpperCase)
代替。
toUpperCase
是String的实例方法,并且没有参数:
public String toUpperCase() {
return toUpperCase(Locale.getDefault());
}
但在这里却写成了类似静态方法的引用。使用这种形式时,函数式接口的第一个参数匹配调用对象,第二个参数(如果有)匹配方法指定的参数。
这里的函数式接口是:
interface SomeFunc<T> {
T func(T t);
}
参数t对应toUpperCase方式的实例也就是str,toUpperCase方法返回值是String,满足上面的规则。
当然如果记不住这样方法引用也没有关系,按照原本的写法就可以正常运行。IDEA编译器也会提示你转换成方法引用的写法。
预定义的函数式接口
前面的例子我们都是自己定义了函数式接口,可能你也注意到通过泛型定义的函数式接口可以承担很多功能。考虑到这个,很多时候我们并不需要自己定义函数式接口,JDK8的新包java.util.function中预定义了很多有用的函数式接口。
Function
Function表示接受一个参数并生成结果的函数。
public interface Function<T, R> {
R apply(T t);
}
针对基本数据类型,为了避免产生冗余的包装类,定义了一系列基本数据类型的Function:
- DoubleFunction
- IntFunction
- LongFunction
- IntToDoubleFunction
- IntToLongFunction
- LongToDoubleFunction
- LongToIntFunction
- ToDoubleFunction
- ToIntFunction
- ToLongFunction
还有BiFunction,与Function类似,接受两个参数并生成结果的函数:
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
Operator
Operator是特殊的一种Function,表示参数和返回是同一种类型:
public interface UnaryOperator<T> extends Function<T, T> {}
同样的也有针对基本数据类型扩展类,请自行查看。
Consumer
Consumer表示接受单个输入参数而不返回结果的操作,主要用于对参数t的处理
public interface Consumer<T> {
void accept(T t);
}
同样的也有针对基本数据类型,以及Bi的扩展类,请自行查看。
Supplier
Supplier表示内容的提供者,即不需要参数有一个返回值。
public interface Supplier<T> {
T get();
}
同样的也有针对基本数据类型扩展类,请自行查看。
Predicate
Predicate接收一个参数并返回boolean:
public interface Predicate<T> {
boolean test(T t);
}
同样的也有针对基本数据类型扩展类,请自行查看。