3年前 (2021-09-24)  Java系列 |   抢沙发  406 
文章评分 0 次,平均分 0.0

在Java程序中使用lambda表达式和函数编程

在JavaSE8之前,匿名类通常用于将功能传递给方法。这种做法混淆了源代码,使其更难理解。Java8通过引入lambdas消除了这个问题。本教程首先介绍lambda语言功能,然后详细介绍使用lambda表达式和目标类型进行函数式编程。您还将了解lambda如何与作用域、局部变量、this和super关键字以及Java异常交互。

请注意,本文中的代码示例与JDK 12兼容。

Lambdas: A primer

lambda表达式(lambda)描述可传递给构造函数或方法以供后续执行的代码块(匿名函数)。构造函数或方法接收lambda作为参数。考虑下面的例子:

() -> System.out.println("Hello")

此示例标识用于将消息输出到标准输出流的lambda。从左到右,()标识lambda的正式参数列表(示例中没有参数),->表示表达式是lambda,System.out.println(“Hello”)是要执行的代码。

lambda简化了函数接口的使用,函数接口是带注释的接口,每个接口只声明一个抽象方法(尽管它们也可以声明默认、静态和私有方法的任意组合)。例如,标准类库提供了一个java.lang.Runnable接口和一个抽象void run()方法。此功能接口的声明如下所示:

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

类库用@functionanterface注释Runnable,后者是java.lang.functionanterface注释类型的实例。FunctionInterface用于注释将在lambda上下文中使用的接口。

lambda没有显式接口类型。相反,编译器使用周围的上下文来推断在指定lambda时实例化哪个函数接口——lambda绑定到该接口。例如,假设我指定了以下代码片段,它将前面的lambda作为参数传递给java.lang.Thread类的Thread(Runnable target)构造函数:

new Thread(() -> System.out.println("Hello"));

编译器确定lambda正在传递给Thread(Runnable r),因为这是满足lambda的唯一构造函数:Runnable是函数接口,lambda的空形式参数list ()run()的空参数列表匹配,并且返回类型(void)也一致。lambda必须是可运行的。

清单1给出了一个小应用程序的源代码,该应用程序允许您使用这个示例。

清单1。LambdaDemo.java(版本1)

public class LambdaDemo
{
   public static void main(String[] args)
   {
      new Thread(() -> System.out.println("Hello")).start();
   }
}

编译清单1(javac LambdaDemo.java)并运行应用程序(java LambdaDemo)。您应该观察以下输出:

Hello

Lambdas可以极大地简化您必须编写的源代码量,还可以使源代码更易于理解。例如,如果没有lambdas,您可能会指定清单2更详细的代码,它基于实现Runnable的匿名类的实例。

清单2。LambdaDemo.java(版本2)

public class LambdaDemo
{
   public static void main(String[] args)
   {
      Runnable r = new Runnable()
                   {
                      @Override
                      public void run()
                      {
                         System.out.println("Hello");
                      }
                   };
      new Thread(r).start();
   }
}

编译此源代码后,运行应用程序。您将发现与前面所示相同的输出。

Java lambdas深入研究

为了有效地使用lambda,您必须理解lambda表达式的语法以及目标类型的概念。您还需要了解lambda如何与作用域、局部变量、thissuper关键字以及异常交互。我将在接下来的章节中介绍所有这些主题。

Lambda语法

每个lambda都符合以下语法:

( formal-parameter-list ) -> { expression-or-statements }

形式参数列表是以逗号分隔的形式参数列表,它必须在运行时匹配函数接口的单个抽象方法的参数。如果省略它们的类型,编译器将从使用lambda的上下文中推断出这些类型。考虑下面的例子:

(double a, double b) // types explicitly specified
(a, b) // types inferred by compiler

Lambdas和var

从JavaSE11开始,您可以用var替换类型名。例如,您可以指定(var a,var b

必须为多个或无形式参数指定括号。但是,在指定单个形式参数时,可以省略括号(尽管不必如此)。(这仅适用于参数名——当指定了类型时也需要括号)。

x // parentheses omitted due to single formal parameter
(double x) // parentheses required because type is also present
() // parentheses required when no formal parameters
(x, y) // parentheses required because of multiple formal parameters

形式参数列表后面是->标记,后面是表达式或语句——表达式或语句块(称为lambda体)。与基于表达式的实体不同,基于语句的实体必须放置在打开({)和关闭(})大括号字符之间:

(double radius) -> Math.PI * radius * radius
radius -> { return Math.PI * radius * radius; }
radius -> { System.out.println(radius); return Math.PI * radius * radius; }

第一个示例的基于表达式的lambda主体不必放在大括号之间。第二个示例将基于表达式的主体转换为基于语句的主体,其中必须指定return以返回表达式的值。最后一个示例演示了多个语句,并且在没有大括号的情况下无法表达。

Lambda体与分号

注意前面的示例中分号()的缺失或存在。在每种情况下,lambda主体都不会以分号结尾,因为lambda不是语句。但是,在基于语句的lambda体中,每个语句都必须以分号结尾。

清单3展示了一个演示lambda语法的简单应用程序;请注意,此清单建立在前两个代码示例的基础上。

清单3。LambdaDemo.java(版本3)

@FunctionalInterface
interface BinaryCalculator
{
   double calculate(double value1, double value2);
}
@FunctionalInterface
interface UnaryCalculator
{
   double calculate(double value);
}
public class LambdaDemo
{
   public static void main(String[] args)
   {
      System.out.printf("18 + 36.5 = %f%n", calculate((double v1, double v2) ->
                        v1 + v2, 18, 36.5));
      System.out.printf("89 / 2.9 = %f%n", calculate((v1, v2) -> v1 / v2, 89,
                        2.9));
      System.out.printf("-89 = %f%n", calculate(v -> -v, 89));
      System.out.printf("18 * 18 = %f%n", calculate((double v) -> v * v, 18));
   }
   static double calculate(BinaryCalculator calc, double v1, double v2)
   {
      return calc.calculate(v1, v2);
   }
   static double calculate(UnaryCalculator calc, double v)
   {
      return calc.calculate(v);
   }
}

清单3首先介绍了BinaryCalculatorUnaryCalculator函数接口,其calculator()方法分别对两个输入参数或单个输入参数执行计算。该清单还介绍了一个LambdaDemo类,其main()方法演示了这些函数接口。

静态双计算(BinaryCalculator calc,double v1,double v2)和静态双计算(UnaryCalculator calc,double v)方法演示了功能接口。lambda将代码作为数据传递给这些方法,这些方法作为BinaryCalculatorUnaryCalculator实例接收。

编译清单3并运行应用程序。您应该观察以下输出:

18 + 36.5 = 54.500000
89 / 2.9 = 30.689655
-89 = -89.000000
18 * 18 = 324.000000

目标类型

lambda与隐式目标类型关联,该类型标识lambda绑定到的对象的类型。目标类型必须是从上下文推断的功能接口,这将lambda限制在以下上下文中出现:

  • 变量声明
  • 分配
  • 返回语句
  • 数组初始值设定项
  • 方法或构造函数参数
  • Lambda体
  • 三元条件表达式
  • 转换表达式

清单4展示了一个演示这些目标类型上下文的应用程序。

清单4. LambdaDemo.java(版本4)

import java.io.File;
import java.io.FileFilter;
import java.nio.file.Files;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitor;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Callable;
public class LambdaDemo
{
   public static void main(String[] args) throws Exception
   {
      // Target type #1: variable declaration
      Runnable r = () -> { System.out.println("running"); };
      r.run();
      // Target type #2: assignment
      r = () -> System.out.println("running");
      r.run();
      // Target type #3: return statement (in getFilter())
      File[] files = new File(".").listFiles(getFilter("txt"));
      for (int i = 0; i < files.length; i++)
         System.out.println(files[i]);
      // Target type #4: array initializer
      FileSystem fs = FileSystems.getDefault();
      final PathMatcher matchers[] =
      {
         (path) -> path.toString().endsWith("txt"),
         (path) -> path.toString().endsWith("java")
      };
      FileVisitor<Path> visitor;
      visitor = new SimpleFileVisitor<Path>()
                {
                   @Override
                   public FileVisitResult visitFile(Path file,
                                                    BasicFileAttributes attribs)
                   {
                      Path name = file.getFileName();
                      for (int i = 0; i < matchers.length; i++)
                      {
                         if (matchers[i].matches(name))
                            System.out.printf("Found matched file: '%s'.%n",
                                              file);
                      }
                      return FileVisitResult.CONTINUE;
                   }
                };
      Files.walkFileTree(Paths.get("."), visitor);
      // Target type #5: method or constructor arguments
      new Thread(() -> System.out.println("running")).start();
      // Target type #6: lambda body (a nested lambda)
      Callable<Runnable> callable = () -> () ->
         System.out.println("called");
      callable.call().run();
      // Target type #7: ternary conditional expression
      boolean ascendingSort = false;
      Comparator<String> cmp;
      cmp = (ascendingSort) ? (s1, s2) -> s1.compareTo(s2)
                            : (s1, s2) -> s2.compareTo(s1);
      List<String> cities = Arrays.asList("Washington", "London", "Rome",
                                          "Berlin", "Jerusalem", "Ottawa",
                                          "Sydney", "Moscow");
      Collections.sort(cities, cmp);
      for (int i = 0; i < cities.size(); i++)
         System.out.println(cities.get(i));
      // Target type #8: cast expression
      String user = AccessController.doPrivileged((PrivilegedAction<String>) ()
                                           -> System.getProperty("user.name"));
      System.out.println(user);
   }
   static FileFilter getFilter(String ext)
   {
      return (pathname) -> pathname.toString().endsWith(ext);
   }
}

原文地址:https://www.infoworld.com/article/3452018/get-started-with-lambda-expressions.html

  
 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2373.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册