lambda表达式的几种形式
Runnable runnable = () -> System.out.println("Hello World");
Runnable runnable1 = () -> {
System.out.println("Hello");
System.out.println("World");
};
ActionListener listener = event -> System.out.println("button clicked");
BinaryOperator<Long> add = (x, y) -> x + y;
BinaryOperator<Long> add1 = (Long x, Long y) -> x + y;
函数式编程而不是命令式编程
java语言从第一个版本开始就支持命令式编程和面向对象编程,在java8中添加了功能强大的函数式编程语法,它同常更为简洁,更具表达力,而且更容易并行化。所以我们有必要学习函数式编程,尽管学习新的编程思想要花费很多时间但是相对于回报来说,这点投入是值得的。我们来看一下命令式和函数式编程的语法差异。
public int externalCountStudentFromShannxi(List<Student> students){
int count = 0;
for(Student student : students) {
if(student.isFrom(Constants.Provinces.SHANNXI)) {
count++;
}
}
return count;
}
这样的代码大家都很熟悉,先定义一个临时变量 count, 然后循环学生列表,找出来自陕西的学生,给count加1,最后返回count. 命令式格式为我们提供了完全的控制权,这有时是件好事。而另一方面,您需要执行所有工作。在许多情况下,可以减少工作量来提高效率。
public long innerIterator(List<Student> students) {
return students
.stream()
.filter(student -> student.isFrom(Constants.Provinces.SHANNXI))
.count();
}
而函数式编程更像是在叙述一件事,把学生列表转换为流,过滤,然后计数,我们只需告诉程序想要干什么,程序就能执行出相应的结果。这样的编码省略了很多中间变量,对于效率来说有一定的提省。
java中的高阶函数
高阶函数是指接受另外一个函数作为参数,或者返回一个函数的函数。根据方法签名,如果参数或者返回值里边有函数接口,那么这个函数就是高阶函数。来看下面一个示例。统计一个字符串中每个字符出现的次数。两种实现的效果显而易见。
public Map<String, Long> accountNumber(String str){
Objects.requireNonNull(str, "String cannot be empty");
Map<String, Long> result = new HashMap<String, Long>();
List<String> list = Arrays.asList(str.split(""));
for(String string : list) {
if(!result.containsKey(string)) {
result.put(string, 0l);
continue;
}
result.put(string, result.get(string) + 1);
}
return result;
}
public Map<String, Long> accountNumberWithLambda(String str) {
Objects.requireNonNull(str, "String cannot be empty");
return Arrays.asList(str.split("")).stream().collect(groupingBy(string -> string, counting()));
}
编写可读性高的代码
虽然上例中用lambda方式实现很简单,真正的逻辑也只有一行,但给我的感觉是为了用lambda而用lambda.我们可以选择更优雅的方式来优化代码。让代码简介而不生硬,在编程过程中,我们很容易忽略表达能力和可读性的价值。Java8通过约定来鼓励这些品质,建议我们对齐函数组合的垂直方向上的各点。
public Map<String, Long> accountNumberWithLambda(String str) {
Objects.requireNonNull(str, "String cannot be empty");
return Arrays
.asList(str.split(""))
.stream()
.collect(groupingBy(string -> string, counting()));
}
完美的lambda表达式只有一行
为什么说完美的lambda表达式只有一行呢,让我们通过代码来体会以下,我们需要打印来自陕西的且学号为偶数的学生的英语成绩。
public void printScore(List<Student> students) {
System.out.println(students.stream()
.filter(student -> {
boolean isEven = Integer.parseInt(student.getId()) % 2 == 0;
boolean isFromShannxi = student.isFrom(Constants.Provinces.SHANNXI);
return isEven && isFromShannxi;
})
.collect(Collectors.toList())
.toString()
);
}
这样的代码尽管使用了函数式编程的风格,但还是摆脱不了命令式编程的思维,代码看起来有点不论不类的感觉,而且中间变量也没少定义,且代码意图很难看明白。其中的原因就是我们的lambda表达式没有最简化,函数组合的表达能力很大程度上依赖于每个 lambda 表达式的简洁性。如果您的 lambda 表达式包含多行(甚至两行可能都太多)。很多时候我们可以使用方法引用来优化我们的lambda表达式,让我们对上面的代码进行优化。如下所示:
System.out.println(
students.stream()
.filter(student -> student.isFrom(Constants.Provinces.SHANNXI))
.filter(student -> (Integer.parseInt(student.getId()) % 2 == 0))
.collect(Collectors.toList())
);
System.out.println(
students.stream()
.filter(student -> student.isFrom(Constants.Provinces.SHANNXI) && (Integer.parseInt(student.getId()) % 2 == 0))
.collect(Collectors.toList())
);
级联lambda表达式
如果看到
x -> y -> x > y
这样的代码,不要惊慌,其实也没什么,都是lambda表达式,接下来我们来分析其中的语法,设想有两个列表,numbers1和numbers2.假设我们想从第一个列表中仅提取大于50的数,然后从第二个列表中提取大于50的数并乘以2.
List<Integer> result1 = list1.stream()
.filter(e -> e > 50)
.collect(Collectors.toList());
List<Integer> result2 = list2.stream()
.filter(e -> e > 50)
.map(e -> e*2 )
.collect(Collectors.toList());
代码的实现很简单,但是仍然有冗余,对于检查数字是否大于50的表达式,我们使用了两次,我们可以通过一个Predicate函数接口来表示返回布尔值的lambda表达式,从而删除重复代码。
Predicate<Integer> predicate = e -> e > 50;
但是这样会产生一个问题,假如我想要一个大于25的,或者大于75的值,我们这个lambda又要重新编写了,看似优化,其实代码量更多了。因此我们需要将比较的参数传入lambda表达式,JDK为我们提供了这样一个表达式Function,它接收T,返回R,因此,我们的代码可以改写成以下。
Function<Integer, Predicate<Integer>> function = (Integer num) ->{
Predicate<Integer> isLargeThan = (Integer candidate) -> {
return candidate > num;
} ;
return isLargeThan;
};
List<Integer> result = list2.stream()
.filter(function.apply(50))
.map(e -> e*2 )
.collect(Collectors.toList());
这样我们只需改apply()方法的参数,就可以重用这段逻辑。但是这个的代码看起来仍然很乱,接下来我们就开始删除多余的逻辑。首先删除类型声明,在没有类型混淆的情况下,java可以推断出我们需要的类型。
扫描二维码关注公众号,回复: 113825 查看本文章
Function<Integer, Predicate<Integer>> function = (Integer num) ->{
Predicate<Integer> isLargeThan = (Integer candidate) -> {
return candidate > num;
} ;
return isLargeThan;
};
Function<Integer, Predicate<Integer>> function1 = num ->{
Predicate<Integer> isLargeThan = candidate -> {
return candidate > num;
} ;
return isLargeThan;
};
有改进但是改进不大,仅删除了两个单词和两个括号。我们知道当lambda表达式的主体只有一行代码时,花括号和return是多余的。让我们继续删除。
Function<Integer, Predicate<Integer>> function2 = num ->{
return candidate -> candidate > num;
};
现在所剩的外层lambda表达式主体让然只有一行,因此外层lambda表达式的花括号和return可以继续删除。
Function<Integer, Predicate<Integer>> function3 = num -> candidate -> candidate > num;
结束语
最终我们看到了上边的级联lambda表达式。它并不是什么新鲜的事物。让我们在思考以下,如果代码中这样的表达式太多的话,估计就是写代码的开发人员,第二天都会忘了这个表达式想要干什么,因此这不是我们想要的结果。当代码中过多的出现级联lambda表达式的时候,就要考虑这样的设计是否是必须的。文中出现的代码可在GitHub上下载。
相关主题
java Stream系列
java 学习指南
关于Lambda FAQ
Java 8:Lambdas,第1部分
级联 lambda 表达式