文档章节

Java SE 8: Lambda表达式

天蓝1122
 天蓝1122
发布于 2015/08/28 14:54
字数 2175
阅读 20
收藏 0

转自 http://www.infoq.com/cn/articles/Java-se-8-lambda

Java SE 8在6月13的版本中已经完全了全部的功能。在这些新的功能中,lambda表达式是推动该版本发布的最重要新特性。因为Java第一次尝试引入函数式编程的相关内容。社区对于lambda表达式也期待已久。Lambda表达式的相关内容在JSR 335中定义,本文的内容基于最新的规范和JDK 8 Build b94。 开发环境使用的是Eclipse

Lambda表达式

要理解lambda表达式,首先要了解的是函数式接口(functional interface)。简单来说,函数式接口是只包含一个抽象方法的接口。比如Java标准库中的java.lang.Runnablejava.util.Comparator都是典型的函数式接口。对于函数式接口,除了可以使用Java中标准的方法来创建实现对象之外,还可以使用lambda表达式来创建实现对象。这可以在很大程度上简化代码的实现。在使用lambda表达式时,只需要提供形式参数和方法体。由于函数式接口只有一个抽象方法,所以通过lambda表达式声明的方法体就肯定是这个唯一的抽象方法的实现,而且形式参数的类型可以根据方法的类型声明进行自动推断。

以Runnable接口为例来进行说明,传统的创建一个线程并运行的方式如下所示:

public void runThread() {
    new Thread(new Runnable() {
        public void run() {
            System.out.println("Run!");
        }
    }).start();
}

在上面的代码中,首先需要创建一个匿名内部类实现Runnable接口,还需要实现接口中的run方法。如果使用lambda表达式来完成同样的功能,得到的代码非常简洁,如下面所示:

public void runThreadUseLambda() {
    new Thread(() -> {
        System.out.println("Run!");
    }).start();
}

相对于传统的方式,lambda表达式在两个方面进行了简化:首先是Runnable接口的声明,这可以通过对上下文环境进行推断来得出;其次是对run方法的实现,因为函数式接口中只包含一个需要实现的方法。

Lambda表达式的声明方式比较简单,由形式参数和方法体两部分组成,中间通过“->”分隔。形式参数不需要包含类型声明,可以进行自动推断。当然在某些情况下,形式参数的类型声明是不可少的。方法体则可以是简单的表达式或代码块。

比如把一个整数列表按照降序排列可以用下面的代码来简洁实现:

Collections.sort(list, (x, y) -> y - x);

Lambda表达式“(x, y) -> y - x“实现了java.util.Comparator接口。

在Java SE 8之前的标准库中包含的函数式接口并不多。Java SE 8增加了java.util.function包,里面都是可以在开发中使用的函数式接口。开发人员也可以创建新的函数式接口。最好在接口上使用注解@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。

下面的代码使用函数式接口java.util.function.Function实现的对列表进行map操作的方法。从代码中可以看到,如果尽可能的使用函数式接口,则代码使用起来会非常简洁。

public class CollectionUtils {
    public static  List map(List input, Function processor) {
        ArrayList result = new ArrayList();
        for (T obj : input) {
            result.add(processor.apply(obj));
        }
        return result;
    }
    
    public static void main(String[] args) {
        List input = Arrays.asList(new String[] {"apple", "orange", "pear"});
        List lengths = CollectionUtils.map(input, (String v) -> v.length());
        List uppercases = CollectionUtils.map(input, (String v) -> v.toUpperCase());
    }
}

方法和构造方法引用

方法引用可以在不调用某个方法的情况下引用一个方法。构造方法引用可以在不创建对象的情况下引用一个构造方法。方法引用是另外一种实现函数式接口的方法。在某些情况下,方法引用可以进一步简化代码。比如下面的代码中,第一个forEach方法调用使用的是lambda表达式,第二个使用的是方法引用。两者作用相同,不过使用方法引用的做法更加简洁。

List input = Arrays.asList(new String[] {"apple", "orange", "pear"});
input.forEach((v) -> System.out.println(v));
input.forEach(System.out::println);

构造方法可以通过名称“new”来进行引用,如下面的代码所示:

List dateValues = Arrays.asList(new Long[] {0L, 1000L});
List dates = CollectionUtils.map(dateValues, Date::new);

接口的默认方法

Java开发中所推荐的实践是面向接口而不是实现来编程。接口作为不同组件之间的契约,使得接口的实现可以不断地演化。不过接口本身的演化则比较困难。当接口发生变化时,该接口的所有实现类都需要做出相应的修改。如果在新版本中对接口进行了修改,会导致早期版本的代码无法运行。Java对于接口更新的限制过于严格。在代码演化的过程中,一般所遵循的原则是不删除或修改已有的功能,而是添加新的功能作为替代。已有代码可以继续使用原有的功能,而新的代码则可以使用新的功能。但是这种更新方式对于接口是不适用的,因为往一个接口中添加新的方法也会导致已有代码无法运行。

接口的默认方法的主要目标之一是解决接口的演化问题。当往一个接口中添加新的方法时,可以提供该方法的默认实现。对于已有的接口使用者来说,代码可以继续运行。新的代码则可以使用该方法,也可以覆写默认的实现。

考虑下面的一个简单的进行货币转换的接口。该接口的实现方式可能是调用第三方提供的服务来完成实际的转换操作。

public interface CurrencyConverter {
    BigDecimal convert(Currency from, Currency to, BigDecimal amount);
}

该接口在开发出来之后,在应用中得到了使用。在后续的版本更新中,第三方服务提供了新的批量处理的功能,允许在一次请求中同时转换多个数值。最直接的做法是在原有的接口中添加一个新的方法来支持批量处理,不过这样会造成已有的代码无法运行。而默认方法则可以很好的解决这个问题。使用默认方法的新接口如下所示。

public interface CurrencyConverter {
    BigDecimal convert(Currency from, Currency to, BigDecimal amount);

    default List convert(Currency from, Currency to, List amounts) {
        List result = new ArrayList();
            for (BigDecimal amount : amounts) {
                result.add(convert(from, to, amount));
            }
            return result;
    }
}

新添加的方法使用default关键词来修饰,并可以有自己的方法体。

默认方法的另外一个作用是实现行为的多继承。Java语言只允许类之间的单继承关系,但是一个类可以实现多个接口。在默认方法引入之后,接口中不仅可以包含变量和方法声明,还可以包含方法体,也就是行为。通过实现多个接口,一个Java类实际上可以获得来自不同接口的行为。这种功能类似于JavaScript等其他语言中可见的“混入类”(mixin)。实际上,Java中一直存在“常量接口(Constant Interface)”的用法。常量接口中只包含常量的声明。通过实现这样的接口,就可以直接引用这些常量。通过默认方法,可以创建出类似的帮助接口,即接口中包含的都是通过默认方法实现的帮助方法。比如创建一个StringUtils接口包含各种与字符串操作相关的默认方法。通过继承该接口就可以直接使用这些方法。

Java SE 8标准库已经使用默认方法来对集合类中的接口进行更新。比如java.util.Collection接口中新增的默认方法removeIf可以删除集合中满足某些条件的元素。还有java.lang.Iterable接口中新增的默认方法forEach可以遍历集合中的元素,并执行一些操作。这些新增的默认方法大多使用了java.util.function包中的函数式接口,因此可以使用lambda表达式来非常简洁的进行操作。

Lambda表达式是Java SE 8在提高开发人员生产效率上的一个重大改进。通过语法上的改进,可以减少开发人员需要编写和维护的代码数量。

本文转载自:

下一篇: mysql配置
天蓝1122
粉丝 5
博文 57
码字总数 34492
作品 1
佛山
高级程序员
私信 提问
Oracle提交Java 7 和Java 8规格

Oracle提交Java 7 和Java 8规格 发表于:2010-11-18 03:21 PM 即将来临的Java版本致力于减少冗余代码,增加多核性能,并且催生出动态脚本语言 周二一名Oracle官员详细介绍了Oracle提交的版本...

xyxzfj
2010/11/19
663
0
Lambda 表达式有何用处?如何使用?(针对Java8)

什么是Lambda? 我们知道,对于一个Java变量,我们可以赋给其一个“值”。 如果你想把“一块代码”赋给一个Java变量,应该怎么做呢? 比如,我想把右边那块代码,赋给一个叫做aBlockOfCode的J...

亭子happy
2018/06/06
328
2
Java 8 新特性,Lambda,方法引用,Stream,Optional

在C++、Python等语言里都有Lambda表达式,Java 8也新增了这一特性。 在java给变量赋值是这样的: 如果你想把“一块代码”赋给一个Java变量,应该怎么做呢? 比如,我想把右边那块代码,赋给一...

saulc
2018/07/10
390
1
02、Java的lambda表达式和JavaScript的箭头函数

[toc] 前言 在JDK8和ES6的语言发展中,在Java的lambda表达式和JavaScript的箭头函数这两者有着千丝万缕的联系;本次试图通过这篇文章弄懂上面的两个“语法糖”。 简介 Lambda 表达式来源于 ...

weir_will
2018/06/14
0
0
从java1到java9每个版本都有什么新特性?

每次出新版本,大家大概都会这么问,“Java X会有什么特性呢?” 。在下面的内容里,我总结了至今为止的Java主要发行版中各自引入的新特性,这样做的目的是为了突出各个新特性是在哪个发行版...

Java红茶
2017/11/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

从零基础到拿到网易Java实习offer,我做对了哪些事

作为一个非科班小白,我在读研期间基本是自学Java,从一开始几乎零基础,只有一点点数据结构和Java方面的基础,到最终获得网易游戏的Java实习offer,我大概用了半年左右的时间。本文将会讲到...

Java技术江湖
昨天
4
0
程序性能checklist

程序性能checklist

Moks角木
昨天
6
0
VUE 计算属性

本文转载于:专业的前端网站▶VUE 计算属性 1、示例代码 <!DOCTYPE html><html lang="zh"> <head> <meta charset="UTF-8" /> <title>vue示例</title> </hea......

前端老手
昨天
5
0
快速搭建LNMT平台和环境部署 Tomcat详解

Tomcat部署的基本概念 1. CATALINA_HOME与CATALINA_BASE分别指什么?     CATALINA_HOME指的是Tomcat的安装目录     bin:\\Tomcat一些脚本存放目录,比如启动脚本startup.bat/start...

网络小虾米
昨天
6
0
float浮动

float浮动 float浮动概念及原理: 文档流:文档流是文档中可显示对象在排列时所占用的位置。 加浮动的元素,会脱离文档流,会沿父容器靠左或靠右排列,如果之前已经有浮动的元素,会挨着浮动...

studywin
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部