Java SE 8新特性导览:使用Lambda Expression进行函数式编程
“ Java SE 8新功能浏览 ”系列的这篇文章将深入了解Lambda表达式 。 我将向您展示Lambda表达式的几种不同用法。 它们都具有功能接口的共同实现。 我将解释编译器如何从代码中推断信息,例如特定类型的变量以及后台实际发生的情况。
在上一篇文章“ Java SE 8新功能之旅:Java开发世界中的重大变化 ”中,我谈到了在本系列文章中我们将要探索的内容。 首先介绍Java SE 8的 主要功能 ,然后介绍在Microsoft Windows和Apple Mac OS X平台上JDK8的安装过程 ,并提供重要的建议和注意事项。
最后,我们经历了一个由Lambda表达式支持的控制台应用程序的开发,以确保我们可能已经安装了Java SE 8。
源代码托管在我的Github帐户上:从此处克隆。
Lambda表达是什么?
Java SE 8最著名的新功能也许叫做Project Lambda,它是将Java引入函数式编程领域的一项努力。
用计算机科学术语;
Lambda是一个匿名函数。 即,没有名称的功能。
在Java中;
所有函数都是类的成员,被称为方法。 要创建方法,您需要定义其所属的类。
Java SE 8中的lambda表达式使您可以使用非常简洁的语法定义一个类和单个方法,以实现具有单个抽象方法的接口。
让我们弄清楚这个想法。
Lambda Expressions使开发人员可以简化和缩短其代码。 使它更具可读性和可维护性。 这将导致删除更多详细的类声明 。
让我们看一些代码片段。
- 实现接口:在Java SE 8之前,如果要创建线程,首先要定义一个实现可运行接口的类。 这是一个具有名为Run的抽象方法的接口,该抽象方法不接受任何参数。 您可以在自己的代码文件中定义类。 由MyRunnable.java命名的文件。 就像我在这里所做的那样,您可以将类命名为MyRunnable。 然后,您将实现单个抽象方法。
public class MyRunnable implements Runnable { @Override public void run() { System.out.println("I am running"); } public static void main(String[] args) { MyRunnable r1 = new MyRunnable(); new Thread(r1).start(); } }
在此示例中,我的实现将文字字符串输出到控制台。 然后,您将获取该对象,并将其传递给线程类的实例。 我将可运行对象实例化为名为r1的对象。 将其传递给线程的构造函数并调用线程的start方法。 我的代码现在将在自己的线程和内存空间中运行。
- 实现内部类:您可以对此代码进行一些改进,而不是将类声明为单独的文件,而可以将其声明为使用类本地的一次性使用类,即内部类 。
public static void main(String[] args) { Runnable r1 = new Runnable() { @Override public void run() { System.out.println("I am running"); } }; new Thread(r1).start(); }
所以现在,我再次创建一个名为r1的对象,但直接调用该接口的构造方法。 再一次,实现它是单个抽象方法。 然后,我将对象传递给线程的构造函数。
- 实现一个匿名类:您可以通过将类声明为匿名类来命名,因为它从未命名,因此可以使其更加简洁。 我正在实例化可运行的接口,并将其立即传递给线程构造函数。 我仍在实现run方法,并且仍在调用线程的start方法。
public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("I am running"); } }).start(); }
- 使用lambda表达式:在Java SE 8中,您可以重构此代码以显着减少它并使它更具可读性。 lambda版本可能看起来像这样。
public static void main(String[] args) { Runnable r1 = () -> System.out.println("I am running"); new Thread(r1).start(); }
我正在声明具有可运行类型的对象,但现在我使用一行代码来声明单个抽象方法实现,然后再次将对象传递给线程的构造函数。 您仍在实现可运行的接口并调用它的run方法,但使用的代码却少得多。 此外,它可以进行以下改进:
public static void main(String[] args) { new Thread(() -> System.out.println("I am running")).start(); }
这是有关Lambda项目的早期规格文档中的重要报价。
Lambda表达式只能出现在将它们分配给类型为功能接口的变量的位置。
引用布莱恩·格茨让我们分解一下以了解发生了什么。
有哪些功能接口?
功能接口是仅具有一个自定义抽象方法的接口。 即,不是从对象类继承的对象。 Java有许多这样的接口,例如Runnable,Comparable,Callable,TimerTask等。
在Java 8之前,它们被称为Single Abstract Method或SAM接口 。 在Java 8中,我们现在将它们称为功能接口 。
Lambda表达式语法:
这个lambda表达式返回了runnable接口的实现。 它有两部分,由称为箭头标记或Lambda运算符的新语法分开。 lambda表达式的第一部分,在箭头标记之前,是您要实现的方法的签名。
在此示例中,这是一个无参数方法,因此仅用括号表示。 但是,如果我要实现一个接受参数的方法,则只需给出参数名称。 我不必声明它们的类型。
因为接口只有一个抽象方法,所以数据类型是已知的。 lambda表达式的目标之一就是消除不必要的语法。 表达式的第二部分,在箭头标记之后,是单个方法主体的实现。
如果仅是一行代码(如本例所示),则您不需要任何其他内容。 要使用多个语句实现方法主体, 请将它们括在花括号中 。
Runnable r = ( ) -> { System.out.println("Hello!"); System.out.println("Lambda!"); };
Lambda目标:
Lambda表达式可以减少您需要编写的代码量以及必须创建和维护的自定义类的数量。
如果您要实现一次使用的接口,那么创建另一个代码文件或另一个命名类并不总是很有意义。 Lambda表达式可以定义一次匿名实现,以供一次性使用,并显着简化代码。
定义和实例化功能接口
为了开始学习Lambda表达式,我将创建一个全新的功能接口。 一个具有单个抽象方法的接口,然后我将使用Lambda表达式实现该接口。
您可以使用托管在github上的源代码项目“ JavaSE8-Features”来导航项目代码。
-
没有任何参数的方法,Lambda实现
在我的源代码中,我实际上将接口放入其自己的以lambda.interfaces结尾的子包中。 我将其命名为HelloInterface接口。为了实现带有lambda表达式的接口,它必须具有一个抽象方法。 我将声明一个返回void的公共方法,并将其命名为doGreeting 。 它不会接受任何参数,这是使接口可用于Lambda表达式所需要做的全部工作。 如果需要,可以使用新的注释,该注释已添加到Java SE 8中,称为功能接口 。
/** * * @author mohamed_taman */ @FunctionalInterface public interface HelloInterface { void doGreeting(); }
现在,我准备在lambda.impl包下创建一个新类UseHelloInterface , 该类将实例化我的功能接口( HelloInterface ),如下所示:
/** * @author mohamed_taman */ public class UseHelloInterface { public static void main(String[] args) { HelloInterface hello = ()-> out.println("Hello from Lambda expression"); hello.doGreeting(); } }
运行文件并检查结果,它应该运行并输出以下内容。
------------------------------------------------------------------------------------ --- exec-maven-plugin:1.2.1:exec (default-cli) @ Java8Features --- Hello from Lambda expression ------------------------------------------------------------------------------------
因此,当您使用不接受任何参数的单个抽象方法时,代码就是这样。 让我们看一下带有参数的外观。
-
具有任何参数的方法,Lambda实现
在lambda.interfaces下 。 我将创建一个新接口,并将其命名为CalculatorInterface 。 然后,我将声明一个返回void的公共方法,并将其命名为doCalculate ,它将接收两个整数参数value1和value2 。
/** * @author mohamed_taman */ @FunctionalInterface public interface CalculatorInterface { public void doCalculate(int value1, int value2); }
现在,我准备在lambda.impl包下创建一个新类Use CalculatorInterface ,它将实例化我的功能接口( CalculatorInterface ),如下所示:
public static void main(String[] args) { CalculatorInterface calc = (v1, v2) -> { int result = v1 * v2; out.println("The calculation result is: "+ result); }; calc.doCalculate(10, 5); }
请注意doCalculate()参数,它们在接口中分别命名为value1和value2,但是您可以在此处为它们命名。 我将其命名为v1和v2。 我不需要在参数名称前插入int; 该信息是已知的,因为编译器可以从功能接口方法签名中推断出此信息。运行文件并检查结果,它应该运行并输出以下内容。
------------------------------------------------------------------------------------ --- exec-maven-plugin:1.2.1:exec (default-cli) @ Java8Features --- The calculation result is: 50 ------------------------------------------------------------------------------------ BUILD SUCCESS
始终牢记以下规则:
同样,您必须遵循该规则,即接口只能具有一个抽象方法 。 然后,可以使用lambda表达式实现该接口及其单一抽象方法。
-
将内置功能接口与lambda结合使用
前面已经介绍了如何使用lambda表达式实现您自己创建的接口。现在,我将展示具有内置接口的lambda表达式。 属于Java运行时的接口。 我将使用两个示例。 我正在一个名为lambda.builtin的程序包中工作,这是练习文件的一部分。 我将从这堂课开始。 UseThreading 。 在此类中,我实现了Runnable接口。 这个接口是Java多线程体系结构的一部分,我的重点是代码的编写方式,而不是操作方式。 我将展示如何使用lambda表达式替换这些内部类。 我将注释掉声明两个对象的代码。 然后,我将重新声明它们并使用lambdas进行实现。 因此,让我们开始吧。
public static void main(String[] args) { //Old version // Runnable thrd1 = new Runnable(){ // @Override // public void run() { // out.println("Hello Thread 1."); // } //}; /* ***************************************** * Using lambda expression inner classes * ***************************************** */ Runnable thrd1 = () -> out.println("Hello Thread 1."); new Thread(thrd1).start(); // Old Version /* new Thread(new Runnable() { @Override public void run() { out.println("Hello Thread 2."); } }).start(); */ /* ****************************************** * Using lambda expression anonymous class * ****************************************** */ new Thread(() -> out.println("Hello Thread 2.")).start(); }
让我们看另一个例子。 我将使用比较器 。 比较器是Java中的另一个功能接口,具有单个抽象方法。 此方法是比较方法。打开文件UseComparator类,并检查代码的注释位,这是实际代码,然后将其重构为lambda表达式。
public static void main(String[] args) { List<string> values = new ArrayList(); values.add("AAA"); values.add("bbb"); values.add("CCC"); values.add("ddd"); values.add("EEE"); //Case sensitive sort operation sort(values); out.println("Simple sort:"); print(values); // Case insensetive sort operation with anonymous class /* Collections.sort(values, new Comparator<string>() { @Override public int compare(String o1, String o2) { return o1.compareToIgnoreCase(o2); } }); */ // Case insensetive sort operation with Lambda sort(values,(o1, o2) -> o1.compareToIgnoreCase(o2)); out.println("Sort with Comparator"); print(values); }
和以前一样,它不会为您提供任何性能优势 。 基本功能完全相同。 无论您声明自己的类 ,使用内部或匿名内部类还是lambda表达式 ,完全取决于您。
在本系列的下一篇文章中,我们将探讨和代码如何使用lambda表达式, 过滤与谓词接口的集合 , 遍历与方法引用 的集合 ,在接口实现的默认方法,并最终实现在接口的静态方法 遍历集合 。