Java8特性之Lambda 表达式

207 阅读6分钟

本系列引入Java 8特性和示例。如lambda表达式、Java流、功能接口和日期时间API更改。

  • Lambda Expression

Lambda表达式,如果你熟悉其他流行编程语言(比如Scala,C#),对Lambda就不感到陌生。在Java编程语言中,Lambda表达式(或函数)只是一个匿名函数。一个没有名称且未绑定到标识符的函数。它们被精确地写在需要的地方,通常作为其他函数的参数。Lambda表达式最重要的特性是它们在其外观的上下文中执行。因此,相同的lambda表达式可以在其他上下文中以不同的方式执行(例如,逻辑是相同的,但结果会根据传递给函数的不同参数而不同)。

函数式编程相对于面向对象编程(OOP)的好处,大多数OOP语言都是围绕对象和实例发展的,并且只把它们当作自己的第一类公民。另一个重要的实体即功能退居其次。尤其是在java中,函数不能存在于对象之外。在java中,函数本身没有任何意义,除非它与某个对象或实例相关。但是在函数式编程中,您可以定义函数,为它们提供引用变量并将其作为方法参数传递等等。 JavaScript是一个很好的例子,您可以在其中传递回调方法,例如 到Ajax调用。 这是非常有用的功能,并且从一开始就在Java中缺少。 现在使用Java 8,我们也可以使用这些lambda表达式。

Lambda表达式提供的功能:

  • 允许将功能视为方法参数,或将代码视为数据。
  • 可以不属于任何类而创建的函数。
  • lambda表达式可以像对象一样传递并按需执行。

lambda表达式的基本语法为:

lambda operator -> body

  • 无参数

() -> System.out.println("无参数");

  • 一个参数

(p) -> System.out.println(“参数: " + p);

如果可以从上下文推断出该变量的类型,则不必使用括号

  • 多个参数

(p1, p2) -> System.out.println(“多个参数: " + p1 + ", " + p2);


  • Lambda Expression的协作规则


  1. lambda表达式可以有零个、一个或多个参数。
  2. 可以显式声明参数的类型,也可以从上下文推断参数的类型。
  3. 多个参数用强制括号括起来,并用逗号分隔。空括号用于表示一组空参数。
  4. 当有单个参数时,如果它的类型是推断出来的,则不强制使用括号。例如a ->返回a*a。
  5. Lambda表达式的主体可以包含零个,一个或多个语句。
  6. 如果lambda表达式的主体具有单语句,则不强制使用大括号,且匿名函数的返回类型与主体表达式相同。当主体中有多个语句时,必须用大括号括起来。

以上是Lambda Expression的简单概述,下面会深入介绍Lambda Expression 但在这之前先了解下 functional interfaces。因为这是重要的。

  • Java 8 functional interface

Lambda表达式就像函数一样,它们也像函数一样接受参数。Lambda表达式基本上表示函数接口的实例

单一抽象方法接口(SAM接口)不是一个新概念。 在Java中,我们已经有许多此类SAM接口的示例。 功能接口是仅包含一个抽象方法的接口。 他们只能展示一种功能。 从Java 8开始,lambda表达式可用于表示功能接口的实例。 功能接口可以具有许多默认方法,通过使用新的注释(即@FunctionalInterface)标记这些接口来实施单一职责规则。 Runnable,ActionListener和Comparable是功能接口的一些示例。在Java 8之前,我们必须创建匿名内部类对象或实现这些接口。

例如,Runnable接口的新定义

@FunctionalInterfacepublic interface Runnable {       public abstract void run();}

如果想在任何函数接口中添加新方法,编译器将不允许您这样做,并将抛出编译时错误。怎么和Lambda结合呢?我们知道Lambda表达式是不带名称的匿名函数,它们(大多数)作为参数传递给其他函数。在java中,方法参数始终具有类型,并且在确定方法重载甚至是简单方法调用的情况下,都会寻找此类型信息以确定需要调用哪个方法。因此,基本上每个lambda表达式也必须都可以转换为某种类型才能被接受为方法参数。 转换lambda表达式的类型始终是函数接口类型。让我们通过一个例子来理解它。

列如我们创建个线程,那么最简单的代码将是:

public class FgMainTest {    public static void main(String[] args) {        new Thread(new Runnable() {            @Override            public void run() {                System.out.println("创建一个线程");            }        }).start();    }}

如果我们使用lambda表达式执行此任务,则代码为:

public class FgMainLambdaTest {    public static void main(String[] args) {        new Thread(()->{            System.out.println("lambda: 创建一个线程");        }).start();    }}

Runnable是一个具有单个方法run()的函数接口。因此,当您将lambda表达式传递给Thread类的构造函数时,编译器将尝试将该表达式转换为等效的可运行代码,如第一个代码示例所示。如果编译器成功,那么一切运行良好,如果编译器不能将表达式转换为等效的实现代码,它将会报错。在上面的例子中,lambda表达式被转换为Runnable类型。

附上Thread构造函数:

public Thread(Runnable target) {    this(null, target, "Thread-" + nextThreadNum(), 0);}
  • lambda 表达式例子

我列出了一些代码例子,你可以阅读和分析一个lambda表达式如何在日常编程中使用。

  • 遍历一个列表并执行一些操作

import java.util.ArrayList;import java.util.List;public class FgMainLambdaTest_example_1 {    public static void main(String[] args) {        // 创建List集合        // {1, 2, 3, 4}        List<Integer> arrL = new ArrayList<Integer>();        arrL.add(1);        arrL.add(2);        arrL.add(3);        arrL.add(4);        // 用lambda expression 打印集合arrL中所有元素        arrL.forEach(n -> System.out.println(n));        arrL.forEach(n -> {            if (n % 2 == 0) System.out.println(n);        });    }}


  • 排序

import java.util.Arrays;import java.util.List;public class FgMainLambdaTest_example_2 {    public static void main(String[] ar) {        List<Department> departmentList = Arrays.asList(                new Department("研发部"),                new Department("测试部"),                new Department("运营部"),                new Department("框架部")        );        System.out.println("排序前: " + Arrays.toString(departmentList.toArray()));        departmentList.sort(Department::nameCompare);        System.out.println("排序后 " + Arrays.toString(departmentList.toArray()));/*        输出结果:     排序前: [研发部, 测试部, 运营部, 框架部]      排序后 [框架部, 测试部, 研发部, 运营部] */    }}class Department {    String name;    Department(String name) {        this.name = name;    }    public static int nameCompare(Department a1, Department a2) {        /**         * java string compareTo()方法按字母顺序比较给定字符串和当前字符串。它返回正数,负数或0。         * 它根据字符串中每个字符的Unicode值来比较字符串。         * 如果第一个字符串在词法上大于第二个字符串,则返回正数(字符值之差)。         * 如果第一个字符串在词法上小于第二个字符串,则返回负数;如果第一个字符串在词法上等于第二个字符串,则返回0。         */        return a1.name.compareTo(a2.name);    }    public String toString() {        return name;    }}