与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;
;导致编译器查找接受字符串参数的构造函数,因为Function
的apply()
方法需要单个(在此上下文中)字符串参数。执行函数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
我是不喜欢lambda,阅读性太差,但大家都用,这东西大佬考虑就行了