lambda是希腊字母λ的读音,意指抽象函数。在java中lambda表达式的语法结构为:
(params)->{statement}
params是参数,可以是一个或者多个,statement是执行语句,也可以是一个或者多个,->是表达式符号,表示goesto
lambda表达式的使用,lambda表达式本质是对接口实现的一种简化,如下,假如我们定义一个接口:
public interface TestFunctionInterface {
R transform(T t);
}
这个接口只有一个方法,该方法接受一个参数类型为T的参数,通过转换返回一个类型为R的结果。如果我们项目中需要使用到Integer转换为String,和String转换为Integer的场景,我们常规的做法是写两个实现类:
public class StringToIntFunction implements TestFunctionInterface {
@Override
public Integer transform(String t) {
return Integer.valueOf(t);
}
}
public class IntToStringFunction implements TestFunctionInterface {
@Override
public String transform(Integer t) {
return Integer.toString(t);
}
}
然后在分别实例化这两个实现类,调用其tranform方法:
public void printTransform() {
int num = 1;
String str = "9";
System.out.println(new IntToStringFunction().transform(num));
System.out.println(new StringToIntFunction().transform(str));
}
或者我们使用稍微高大上点的匿名内部类的方式去实现:
public void printTransform() {
int num = 1;
String str = "9";
System.out.println(new TestFunctionInterface() {
@Override
public String transform(Integer t) {
return Integer.toString(t);
}
}.transform(num));
System.out.println(new TestFunctionInterface() {
@Override
public Integer transform(String t) {
return Integer.valueOf(t);
}
}.transform(str));
}
但是现在java8支持lambda表达式,就不需要那么费劲,我们只需要用lambda表达式的方式实现接口就行。
public void printTransform() {
int num = 1;
String str = "9";
System.out.println(((TestFunctionInterface)i -> Integer.toString(i)).transform(num));
System.out.println(((TestFunctionInterface) s -> Integer.valueOf(s)).transform(str));
}
上述中写法i->Integer.toString(i)这部分lambda表达式就是对接口的实现,但是前面说到脱离接口的lambda表达式不仅是语法不允许的,更是没有意义的,因为编译器不清楚你这个lambda表达式是为了实现哪个接口,因此我们需要在lambda表达式之前加上接口类型TestFunctionInterface
不过大部分情况下我们不会这么去使用,会显得很累赘逻辑不清晰,大部分情况我们应该去定一个一个公共的接收lambda表达式所实现的函数式接口作为参数的方法。
public R transform(T t, TestFunctionInterface function) {
return function.transform(t);
}
然后调用的时候直接传入lambda表达式即可。
public void printTransform() {
int num = 1;
String str = "9";
System.out.println(transform(num, i -> Integer.toString(i)));
System.out.println(transform(str, s -> Integer.valueOf(s)));
}
能使用lambda表达式的接口必须是函数式接口,函数式接口的要求是必须只有一个抽象方法,因为java8以后接口支持了default方法,因此并不要接口中只能有一个方法,但必须是只有一个抽象方法。函数式的接口声明需要加上@FunctionalInterface,表明这是一个函数式接口,也可以不加该注解,接口如果只有一个抽象方法编译器会自动判断是否是函数式接口。
除了自定义的函数式接口,Java8本身还提供了一个函数式接口Function。
Function的源码的定义如下:
@FunctionalInterface
public interface Function {
R apply(T t);
default Function compose(Function super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default Function andThen(Function super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static Function identity() {
return t -> t;
}
}
根据Function接口的源码也印证了上面说到的关于lambda表达式对接口的要求,只能有一个抽象方法,Function接口本身只有一个抽象方法apply,这个方法和上面我们自定义用于测试的接口的方法意义很像,都是传入一个类型为T的参数,得到一个类型为R的结果,其中具体的操作由节课实现类实现。
Function接口的另外两个方法是在多个函数接口组合的时候使用,compose接口的源码很容易看得出是将before函数的运行结果传入给当前函数执行。andThen方法是将当前函数的运行结果传入给after函数执行。
看下例子,同样是我们上面提到的关于类型转换的方法,我们可以在不自定义函数接口的情况下直接使用Function接口实现。
public R transform1(T t, Function function) {
return function.apply(t);
}
public void printTransform() {
int num = 1;
String str = "9";
System.out.println(transform1(num, i -> Integer.toString(i)));
System.out.println(transform1(str, s -> Integer.valueOf(s)));
}
下面的代码是对Function接口的测试:
public static int testApply(int num, Function function) {
return function.apply(num);
}
public static int testCompose(int num, Function before, Function after) {
//这部分根据源码等同于 after.apply(before.apply(num))
return after.compose(before).apply(num);
}
public static int testAndThen(int num, Function before, Function after) {
//这部分根据源码等同于before.appy(after.apply(num))
return after.andThen(before).apply(num);
}
public static void main(String[] args) {
//输出结果为2
System.out.println(testApply(1, e -> e + 1));
//输出结果4
System.out.println(testCompose(1, e -> e + 1, e -> e * 2));
//输出结果为3
System.out.println(testCompose(1, e -> e * 2, e -> e + 1));
//输出结果为3
System.out.println(testAndThen(1, e -> e + 1, e -> e * 2));
}
}
此外JDK同时提供了BiFunction接口,BiFunction接口和Function接口类似,不同的是BiFunction接口的apply方法接受的是两个参数。
R apply(T t,U u);
除了Function接口之外,java8常用的函数式接口还有如下几个:
该接口的抽象方法为 T get();方法无参数,返回一个T类型的对象。我们可以使用Supplier接口实现获取随机数或者获取集合的Stream对象等。
int random = getRandom(() -> new Random().nextInt(10));
System.out.println(random);
public static Integer getRandom(Supplier supplier) {
return supplier.get();
}
该接口的抽象方法为 void accept(T t);方法接受一个T类型的参数,没有返回值,我们可以使用该接口对对象进行处理,比如对List的元素迭代处理输出等。
testConsumer(stuList, list -> list.forEach(stu ->
System.out.println(JSON.toJSONString(stu))));
public static void testConsumer(T t, Consumer consumer) {
consumer.accept(t);
}
该接口的抽象方法为 boolean test(T t);表示接收T类型的参数,返回boolean类型的值。该接口会在java8集合新特性中详细讲解。
上述几个接口如果单独使用意义不明显,一般都是多个函数式接口结合使用。
该接口抽象方法为同样为 R apply(T t,U u),不同于Function是BiFunction接收两个参数,返回一个结果。
BinaryOperator继承自BiFunction,不同的是BinaryOperator限制输入的两个参数以及输出的结果都是T类型