Lambda表达式最佳实践(1)入门与介绍

时间:2023-01-11 19:11:41

Java8引入了Lambda表达式特性,这些是通过java.util.function这个包实现的。所有的Lambda表达式都是这个包下的其中一类。

我们来看下这个包java.util.function

Lambda表达式最佳实践(1)入门与介绍

可以看到很多有@FunctionalInterFace的接口,@FunctionalInterFace代表这个接口只有一个抽象方法。从命名,我们可以猜测出一些接口的作用。

比如,Predicate就是只包含输入是一个对象,输出是true或者false布尔值的方法的这么一个接口。

@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}

Consumer就是只包含输入一个对象,返回void的方法的这么一个接口。

@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}

类似的BiPredicate就是输入是两个对象,输出是true或者false布尔值的方法的这么一个接口;IntToDoubleFunction就是包含一个输入是int类型的输出为double类型的方法的接口

我们可以尝试写一个简单的lambda表达式:

public class LambdaTest {
public static int transfer(double a, Function<Double, Integer> fn) {
return fn.apply(a);
}

public static void main(String[] args) {
System.out.println(transfer(9.9, a -> BigDecimal.valueOf(a).intValue()));
}
}

同时,tansfer函数还可以这么定义:

public class LambdaTest {
public static int transfer(double a, DoubleToIntFunction fn) {
return fn.applyAsInt(a);
}

public static void main(String[] args) {
System.out.println(transfer(9.9, a -> BigDecimal.valueOf(a).intValue()));
}
}

但是,两种定义出现在一起,同样调用的时候,就会编译错误。因为:

Ambiguous method call. Both 
transfer (double,DoubleToIntFunction)in LambdaTest 
and transfer (double, Function<Double, Integer>) in LambdaTest match

由此可见,只有一个方法的interface,其实都可以用Lambda表达式代替。

使用Lambda表达式的一些tips

对于只有一个方法的interface,使用@FunctionalInterface注解

这样可以限制这个接口只会有一个抽象方法,防止在大型项目中合作,修改接口导致lambda表达式全部失效。加上这个注解,只要对于这个接口增加新的方法导致抽象方法不止一个,就会编译错误

不要重载以FunctionalInterface的方法

例如之前举的例子:

public class LambdaTest {
public static int transfer(double a, DoubleToIntFunction fn) {
return fn.applyAsInt(a);
}

public static int transfer(double a, Function<Double, Integer> fn) {
return fn.apply(a);
}

public static void main(String[] args) {
System.out.println(transfer(9.9, a -> BigDecimal.valueOf(a).intValue()));
}
}

解决办法一是换个名字,另一个是调用时,加上强制类型转换,但不推荐这么做

不要将lambda表达式作为内部类:

public class Test implements SimpleJob {

private String value = "Origin Value";

interface Foo {
String method(String string);
}

public String test() {
Foo fooIC = new Foo() {
String value = "Inner class value";

@Override
public String method(String string) {
return this.value;
}
};
String resultIC = fooIC.method("");

Foo fooLambda = parameter -> {
String value = "Lambda value";
return this.value;
};
String resultLambda = fooLambda.method("");

return "Results: resultIC = " + resultIC +
", resultLambda = " + resultLambda;
}

public static void main(String[] args) {
System.out.println(new Test().test());
}
}

lambda表达式实际上是直接填写方法里面的内容,所以无法像内部类那样可以添加field。

这里输出是:

Results: resultIC = Inner class value, resultLambda = Origin Value

保持Lambda表达式简洁明了

避免大块代码,可以抽象为方法

例如:

Foo foo = parameter -> { String result = "Something " + parameter; 
//many lines of code
return result;
};

就最好写成:

Foo foo = parameter -> buildString(parameter);
private String buildString(String parameter) {
String result = "Something " + parameter;
//many lines of code
return result;
}

避免指定参数类型

编译器可以通过类型指针识别出参数的类型,所以不用强制指定参数类型,例如:

(String a, String b) -> a.toLowerCase() + b.toLowerCase();

可以写成:

(a, b) -> a.toLowerCase() + b.toLowerCase();

单个参数不用加括号

(a) -> a.toLowerCase();

可以写成:

a -> a.toLowerCase();

避免return和大括号

a -> {return a.toLowerCase()};

可以写成:

a -> a.toLowerCase();

使用方法指针

对于仅仅是调用类方法的lambda表达式,例如

a -> a.toLowerCase();

可以替换成:

String::toLowerCase;

应该使用“Effectively Final”的变量

lambda表达式用的外部变量,不用是final的,但应该都是Effectively Final的,就是在lambda表达式里面不会修改这个变量