2年前 (2021-12-13)  Java系列 |   1 条评论  506 
文章评分 0 次,平均分 0.0
[收起] 文章目录

使用方法引用简化Java中的函数式编程

与lambdas一起,JavaSE8将方法引用引入Java语言。本文简要概述Java中的方法引用,然后通过Java代码示例开始使用它们。在本文结束时,你将了解如何使用方法引用引用类的静态方法、绑定和未绑定的非静态方法以及构造函数,以及如何使用它们引用超类和当前类类型中的实例方法。文还将了解为什么许多Java开发人员采用lambda表达式和方法引用作为匿名类的更干净、更简单的替代方案。

方法引用

有时,lambda表达式只调用现有方法。例如,下面的代码片段使用lambda调用系统。lambda的单个参数上的System.out's void println(s)方法--s的类型尚未知:

(s) -> System.out.println(s)

lambda以其形式参数列表和代码体的形式呈现其系统。System.out.println(s)表达式将s的值打印到标准输出流。它没有显式的接口类型。相反,编译器从周围的上下文推断要实例化哪个函数接口。例如,考虑下面的代码片段:

Consumer<String> consumer = (s) -> System.out.println(s);

编译器分析前面的声明并确定java.util.function.Consumer预定义功能接口的void accept(T)方法与lambda的形式参数列表((s))匹配。它还确定accept()void返回类型与println()void返回类型匹配。因此,lambda绑定到消费者。

更具体地说,lambda绑定到Consumer<String>。编译器生成代码,以便调用Consumer<String>void accept(String s)方法,将传递给s的字符串参数传递给System.out's void println(String s)方法。此调用如下所示:

consumer.accept("Hello"); // Pass "Hello" to lambda body. Print Hello to standard output.

要保存击键,可以使用方法引用替换lambda,方法引用是对现有方法的紧凑引用。例如,以下代码片段替换(String s) -> System.out.println(s),其中::表示该系统。正在引用out的void println(String s) 方法:

Consumer<String> consumer2 = System.out::println; // The method reference is shorter.
consumer2.accept("Hello"); // Pass "Hello" to lambda body. Print Hello to standard output.

没有必要为前面的方法引用指定一个正式的参数列表,因为编译器可以根据Consumer<String>这个参数化类型的java来推断这个列表。String实际类型参数替换void accept(T t)中的T,也是lambda主体系统中单个参数的System.out.println()方法调用。

方法引用深度

方法引用是从现有方法创建lambda的语法快捷方式。方法引用引用现有类或对象的方法,而不是提供实现体。与lambda一样,方法引用需要目标类型。

您可以使用方法引用来引用类的静态方法、绑定和未绑定的非静态方法以及构造函数。您还可以使用方法引用来引用超类和当前类类型中的实例方法。我将向您介绍这些方法引用类别中的每一个,并在一个小演示中演示如何使用它们。

对静态方法的引用

静态方法引用是指特定类中的静态方法。它的语法是className::staticMethodName,其中className标识类,staticMethodName标识静态方法。例如Integer::bitCount。清单1演示了一个静态方法引用。

import java.util.Arrays;
import java.util.function.Consumer;
public class MRDemo
{
   public static void main(String[] args)
   {
      int[] array = { 10, 2, 19, 5, 17 };
      Consumer<int[]> consumer = Arrays::sort;
      consumer.accept(array);
      for (int i = 0; i < array.length; i++)
         System.out.println(array[i]);
      System.out.println();
      int[] array2 = { 19, 5, 14, 3, 21, 4 };
      Consumer<int[]> consumer2 = (a) -> Arrays.sort(a);
      consumer2.accept(array2);
      for (int i = 0; i < array2.length; i++)
         System.out.println(array2[i]);
   }
}

清单1的main()方法通过java.util.Arrays对整数数组进行排序。java.util.Arrays类的静态void sort(int[] a)方法,该方法出现在静态方法引用和等效的lambda表达式上下文中。对数组排序后,for循环将排序后的数组内容打印到标准输出流。

在使用方法引用或lambda之前,它必须绑定到函数接口。我正在使用预定义的消费者功能接口,它满足方法参考/lambda要求。排序操作通过将要排序的数组传递给使用者的accept()方法开始。

您将看到以下输出:

2
5
10
17
19
3
4
5
14
19
21

对绑定的非静态方法的引用

绑定的非静态方法引用是指绑定到接收方对象的非静态方法。其语法为objectName::instanceMethodName,其中objectName标识接收方,instanceMethodName标识实例方法。例如s::trim。清单2演示了一个绑定的非静态方法引用。

import java.util.function.Supplier;
public class MRDemo
{
   public static void main(String[] args)
   {
      String s = "The quick brown fox jumped over the lazy dog";
      print(s::length);
      print(() -> s.length());
      print(new Supplier<Integer>()
      {
         @Override
         public Integer get()
         {
            return s.length(); // closes over s
         }
      });
   }
   public static void print(Supplier<Integer> supplier)
   {
      System.out.println(supplier.get());
   }
}

清单2的main()方法将一个字符串赋给字符串变量s,然后调用print()类方法,并使用函数获取该字符串的长度作为该方法的参数。print()在方法引用(s::length--length()绑定到s)、等效lambda和等效匿名类上下文中调用。

我已经定义了print()来使用java.util.function.Supplier 预定义的函数接口,其get()方法返回结果supplier。在这种情况下,传递给print()的supplier实例实现其get()方法以返回s.length();输出此长度。

s::length引入了一个在s上闭合的闭包。在lambda示例中可以更清楚地看到这一点。因为lambda没有参数,所以s的值只能从封闭范围中获得。因此,lambda主体是一个在s上闭合的闭包。匿名类示例使这一点更加清楚。

编译清单2并运行应用程序。您将看到以下输出:

44
44
44

对未绑定非静态方法的引用

未绑定的非静态方法引用是指未绑定到接收方对象的非静态方法。它的语法是className::instanceMethodName,其中className标识声明实例方法的类,instanceMethodName标识实例方法。例如String::toLowerCase

String::toLowerCase是一个未绑定的非静态方法引用,用于标识String类的非静态String toLowerCase()方法。但是,由于非静态方法仍然需要receiver对象(在本例中为String对象,用于通过方法引用调用toLowerCase()),因此receiver对象由虚拟机创建。将在此对象上调用toLowerCase()String::toLowerCase指定一个方法,该方法接受单个字符串参数,即接收方对象,并返回字符串结果。String::toLowerCase()等价于lambda (String s) -> { return s.toLowerCase(); }

清单3演示了这个未绑定的非静态方法引用。

import java.util.function.Function;
public class MRDemo
{
   public static void main(String[] args)
   {
      print(String::toLowerCase, "STRING TO LOWERCASE");
      print(s -> s.toLowerCase(), "STRING TO LOWERCASE");
      print(new Function<String, String>()
      {
         @Override
         public String apply(String s) // receives argument in parameter s;
         {                             // doesn't need to close over s
            return s.toLowerCase();
         }
      }, "STRING TO LOWERCASE");
   }
   public static void print(Function<String, String> function, String s)
   {
      System.out.println(function.apply(s));
   }
}

清单3的main()方法调用print()类方法,其功能是将字符串转换为小写,并将要转换的字符串作为方法的参数。print()在方法引用(String::toLowerCase,其中toLowerCase()未绑定到用户指定的对象)和等效的lambda和匿名类上下文中调用。

我已经定义了print()来使用java.util.function.Function预定义的函数接口,它表示接受一个参数并生成结果的函数。在本例中,传递给print()的函数实例实现其R apply(T)方法以返回s.toLowerCase();输出此字符串。

尽管String::toLowerCase的字符串部分使其看起来像是引用了一个类,但只引用了该类的一个实例。匿名类示例使这一点更加明显。注意,在匿名类示例中,lambda接收一个参数;它不会在参数s上关闭(即,它不是一个闭包)。

编译清单3并运行应用程序。您将看到以下输出:

string to lowercase
string to lowercase
string to lowercase

对构造函数的引用

您可以使用方法引用引用构造函数,而无需实例化命名类。这种方法引用称为构造函数引用。它的语法是className::new。类名必须支持对象创建;它不能命名抽象类或接口。关键字new为引用的构造函数命名。以下是一些例子:

  • Character::new: equivalent to lambda (Character ch) -> new Character(ch)
  • Long::new: equivalent to lambda (long value) -> new Long(value) or (String s) -> new Long(s)
  • ArrayList<City>::new: equivalent to lambda () -> new ArrayList<City>()
  • float[]::new: equivalent to lambda (int size) -> new float[size]

上一个构造函数引用示例指定了数组类型而不是类类型,但原理相同。该示例演示了对数组类型的“构造函数”的数组构造函数引用。

要创建构造函数引用,请指定不带构造函数的new。当一个类,如java.lang.Long声明多个构造函数,编译器将函数接口的类型与所有构造函数进行比较,并选择最佳匹配。清单4演示了一个构造函数引用。

import java.util.function.Supplier;
public class MRDemo
{
   public static void main(String[] args)
   {
      Supplier<MRDemo> supplier = MRDemo::new;
      System.out.println(supplier.get());
   }
}

清单4的MRDemo::new构造函数引用相当于lambda()->new MRDemo()。表达supplier.get()执行这个lambda,它调用MRDemo的默认无参数构造函数并返回传递给系统的MRDemo对象System.out.println()。此方法将对象转换为字符串,并打印该字符串。

现在假设您有一个类,它有一个无参数构造函数和一个接受参数的构造函数,并且您想调用接受参数的构造函数。您可以通过选择不同的函数接口来完成此任务,如清单5所示的预定义函数接口。

import java.util.function.Function;
public class MRDemo
{
   private String name;
   MRDemo()
   {
      name = "";
   }
   MRDemo(String name)
   {
      this.name = name;
      System.out.printf("MRDemo(String name) called with %s%n", name);
   }
   public static void main(String[] args)
   {
      Function<String, MRDemo> function = MRDemo::new;
      System.out.println(function.apply("some name"));
   }
}

Function<String, MRDemo> function = MRDemo::new;;导致编译器查找接受字符串参数的构造函数,因为Functionapply()方法需要单个(在此上下文中)字符串参数。执行函数apply(“some name”)会将“some name”传递给MRDemo(String name)

编译清单5并运行应用程序。您将观察到类似于以下内容的输出:

MRDemo(String name) called with some name
MRDemo@2f92e0f4

超类和当前类类型中对实例方法的引用

Java的super关键字只能在实例上下文中使用,它引用超类中的重写方法。您可能偶尔需要创建一个引用超类方法的方法引用。可以通过以下语法执行此操作:

className.super::instanceMethod

Java的this关键字只能在实例上下文中使用,它调用构造函数或引用当前类中的实例方法。您可能偶尔需要创建引用当前类中方法的方法引用。可以通过以下语法执行此操作:

this::instanceMethod

清单6展示了一个演示这两种语法的应用程序。

import java.util.function.Consumer;
class Superclass
{
   void print(String msg)
   {
      System.out.printf("Superclass print(): %s%n", msg);
   }
}
class Subclass extends Superclass
{
   Subclass()
   {
      Consumer<String> consumer = Subclass.super::print;
      consumer.accept("Subclass.super::print");
      consumer = this::print;
      consumer.accept("this::print");
   }
   @Override
   void print(String msg)
   {
      System.out.printf("Subclass print(): %s%n", msg);
   }
}
public class MRDemo
{
   public static void main(String[] args)
   {
      new Subclass();
   }
}

清单6介绍了超类和子类,其中子类扩展了超类。子类引入构造函数并重写超类的void print(String msg)方法。我还介绍了一个MRDemo类,其main()方法通过实例化子类来驱动应用程序。

子类的构造函数将超类的print()方法的方法引用分配给Consumer<String>变量(void print(String)匹配void accept(String)的返回类型和参数列表),并调用该方法。接下来,构造函数将子类的print()方法的方法引用分配给该变量并调用该方法。

编译清单6并运行应用程序。您将看到以下输出:

Superclass print(): Subclass.super::print
Subclass print(): this::print

总结

lambda和方法引用帮助我们以简洁的方式表达功能,这使得源代码更容易理解。在本文中,我向你展示了如何使用方法引用来引用类的静态方法、绑定和未绑定的非静态方法以及构造函数,以及如何使用它们来引用超类和当前类类型中的实例方法。继续练习lambdas和方法引用,养成使用这些特性而不是更详细的匿名类的习惯。

原文地址:https://www.infoworld.com/article/3453296/get-started-with-method-references-in-java.html

 

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

关于

发表评论

表情 格式
  1. 我是不喜欢lambda,阅读性太差,但大家都用,这东西大佬考虑就行了

    冬马 评论达人 LV.1 2年前 (2021-12-15) [0] [0]

登录

忘记密码 ?

切换登录

注册