zoukankan      html  css  js  c++  java
  • 用不用lambda,这是一个问题

    Sun在2009年开启了代号为“dolphin”的工程,计划在JDK1.7中加入lambda表达式、虚拟机模块化支持、动态语言支持等新特性。但是由于深陷商业竞争,市值严重缩水,Sun无法在预计的时间内完成JDK的迭代。

    2010年,Oracle收购Sun,执行新计划,放缓了更新的节奏,将lambada等功能顺延至1.8版本。2013年,JDK1.8版本正式发布,这是Java语言历史上浓墨重彩的一笔,而lambda(λ)则是其中最重要的特性。

    什么是lambda表达式?

    众所周知,Java是一门强大的面向对象的语言,在Java中,除了8种基本的数据类型,其他一切皆为对象, 而数据和对数据的操作,是依赖于对象的属性和方法。面向对象的三大核心:封装、继承、多态都是对数据的抽象,而lambda提供了一种对行为抽象的编程模型。

    Java中将方法作为参数进行传递的方式被称为lambda表达式。

    lambda表达式是Java对于函数式编程的温和转变,面向对象编程和函数式编程不是互相对立的,结合使用能够更加有效地帮助我们管理程序的复杂性。

    为什么需要lambada?

    我认为有以下两点:将外部迭代转换为内部迭代使得效率更高、通过将函数作为参数使得编码更加优雅,更易读。

    随着摩尔定律的失效,个人电脑与专业服务器中开始配置多核处理器,为了使得程序的运行效率更高,开发者需要能够将任务分发到多个核心去执行,而Java Collection Framework默认的外部迭代方式将程序绑定在单个核心上运行,极大地降低了运算速度。

    有时候为了达到某一目的,我们需要重复很多样板代码,这些代码会侵入业务逻辑,降低了可读性。

    一个例子

    假设现在有这么一个例子:

    一个列表中拥有多个元素,元素类型为Integer,现在我要将其中每个元素通过转换为Point(java.awt.Point),最后得出所有Point中距离原点最远的那个。

    传统的写法是:

    1.    public static void main(String[] args) {

    2.        List<Integer> intList = Arrays.asList(21, 82, 33, 54);

    3.        List<Point> pointList = new ArrayList<>();

    4.        for (Integer i : intList) {

    5.            pointList.add(new Point(i % 3, i)); // 转换一

    6.        }

    7.        double max = Double.MIN_VALUE;

    8.        for (Point p : pointList) {

    9.            max = Math.max(p.distance(0, 0), max); // 转换二

    10.        }

    这样写似乎没有什么不对,是的,它非常正确。但是依然存在着一些问题。

    首先,我们考虑一下这两个转换之间的关系。点Integer类型的A需要通过转换一变为Point类型,再由转换二将其变为Double类型,这没有问题。但是代码中的逻辑是当转换一全部完成后,再开始转换二。这似乎没有必要,并且程序将任务绑定在了一个核心上。假如转换任务是一系列复杂的计算,那么串行会导致效率低下。

    此外,我们的目的是intList -> Double(距离最大值),但是为了这一目的,在堆上为中间载体pointList分配了空间,假如这个List非常大,无疑会增加初始化以及GC消耗。而且,这段代码显得有点臃肿。

    匿名内部类 和 lambda

    我们首先使用匿名内部类对其进行改造,内部类是一种语法糖,修改之后虽然比之前简短了一些,但是仍然十分丑陋冗长,并且难以快速理解。

    1.        List<Integer> intList = Arrays.asList(21, 82, 33, 54);

    2.        final double[] max = {Double.MIN_VALUE};

    3.        intList.forEach(new Consumer<Integer>() {

    4.            @Override

    5.            public void accept(Integer i) {

    6.                max[0] = Math.max(new Point(i % 3, i).distance(0, 0), max[0]);

    7.            }

    8.        });

    那么,我们应该如何对其进行改造呢?首先我们来看看哪些东西其实是不需要的。因为List的泛型是Integer,所以编译器可以从上下文推断出来intList.forEach所要操作的元素类型是Integer,其次Consumer是一个interface,准确地来说他是一个函数式接口,我们重写了它的accept方法,它只是用来接收元素,所以我们可以摒弃这种啰嗦的写法,利用编译器自身根据上下文的类型推断,来书写更为精简的lambda表达式:

    1.        List<Integer> intList = Arrays.asList(21, 82, 33, 54);

    2.        final double[] max = {Double.MIN_VALUE};

    3.        intList.forEach(i -> {

    4.            max[0] = Math.max(new Point(i % 3, i).distance(0, 0), max[0]);

    5.        });

    Stream

    Stream是JDK1.8中新增的重要API,它可以帮助开发者操作对集合进行复杂的操作,并且能够自动依赖Fork/Join框架来实现自动并行化。

    在传统方式中,我们可以将数据想象成一种“小球”,而集合则是存放它的“瓶子”。那么上面的例子应该是这样:

    如果我们使用流呢?流提供了一个可选的有序值序列,而且无需为这些值提供任何存储。我们将其想象成一条河道,其中是流动的数据,我们可以在河道上设立“滤网”,通过这些滤网在数据“流动”的时候就对其进行转化。

    此时转化一/二都是对流进行操作,简洁了许多,代码如下:

    1.        List<Integer> intList = Arrays.asList(21, 82, 33, 54);

    2.        OptionalDouble max = intList.stream()

    3.                            .map(i -> new Point(i % 3, i)) //操作一

    4.                            .mapToDouble(p -> p.distance(0, 0)) //操作二

    5.                            .max();

    如果我们想要并行化处理呢?只要将上述代码中的 stream换成 parallelStream,便可将intList中的元素转换为并行流再进行操作。

    写在最后

    Java中的lambda表达式是对函数式编程的支持,在处理集合等问题上有着诸多的优势,而且能够可见地提高代码的质量,理想并且简洁地解决并发问题,但是这并不代表函数式编程(FP)就优于面向对象编程(OOP)。

    可以说,早期的Java是纯粹的OOP语言,因此很多不需要使用对象来传递消息的地方也被强行要求使用对象,这是被人诟病的因素之一,但是随着发展,现在的Java程序大量使用反射、lambda等技术,已经不是那个单纯OOP语言了。

    具体使用哪种编程模式,需要关注问题本身,正如《人月神话》中广为流传的真理——没有银弹。

    -END-

    纯洁的微笑

    一个有故事的程序员

  • 相关阅读:
    LeetCode偶尔一题 —— 617. 合并二叉树
    《剑指offer》 —— 链表中倒数第k个节点
    《剑指offer》 —— 青蛙跳台阶问题
    《剑指offer》—— 二维数组中的查找
    《剑指offer》—— 替换空格
    《剑指offer》—— 合并两个排序的链表
    《剑指offer》—— 礼物的最大价值
    生成Nuget 源代码包来重用你的Asp.net MVC代码
    Pro ASP.Net Core MVC 6th 第四章
    Pro ASP.NET Core MVC 6th 第三章
  • 原文地址:https://www.cnblogs.com/bigben0123/p/10310197.html
Copyright © 2011-2022 走看看