java8 lambda表达式

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可以推断出我们需要的类型。

        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 表达式

猜你喜欢

转载自www.cnblogs.com/yuanbing1226/p/8995946.html