Java 方法引用
1、前言
方法引用是java8的新特性之一, 可以直接引用已有Java类或对象的方法或构造器。方法引用与lambda表达式结合使用,可以进一步简化代码。
2、方法引用的使用场景
先来看一个普通的使用场景:随机生成10个整数然后取它们绝对值并一一打印出来
new Random().ints(10)
.map(i->Math.abs(i))
.forEach(i -> System.out.println(i));
map
方法接受的是一个函数式接口IntUnaryOperator
,那么上面代码中的i->Math.abs(i)
实际上是:
new IntUnaryOperator() {
@Override
public int applyAsInt(int operand) {
return Math.abs(operand);
}
}
从上面来看IntUnaryOperator
就是代理了Math.abs(int i)
,参数列表、返回值都相同,而且没有掺杂其它额外的逻辑。这一点非常重要,不掺杂其它逻辑才能相互代替。那么就可以通过方法引用来简化Lambda 表达式。上面的式子就可以简化为:
new Random().ints(10)
.map(Math::abs)
.forEach(System.out::println);
3、方法引用
方法引用正确的演变过程
不单纯的Lambda不能使用方法引用。
4、不同类型的方法引用
有四种方法引用:
种类 | 语法 | 例子 |
---|---|---|
静态方法引用 | ContainingClass::staticMethodName | Math::abs |
特定对象的实例方法引用 | containingObject::instanceMethodName | this::equals |
实例方法的任意一个特定类型的对象引用 |
ContainingClass::staticMethodName |
String::concat |
构造器引用 | ClassName::new |
HashSet::new |
类::静态方法
如果我们打算把一个类中的静态方法作为Lambda体,可以这样用:
@FunctionalInterface
interface TestInterface {
String handleString(String a, String b);
}
class TestClass {
public static String concatString(String a, String b) {
return a + b;
}
}
public class Test {
public static void main(String[] args) {
TestInterface testInterface = TestClass::concatString;
String result = testInterface.handleString("abc", "def");
System.out.println(result);
//相当于以下效果,直接把类的静态方法写在Lambda体里
TestInterface testInterface2 = (a, b) -> TestClass.concatString(a, b);
String result2 = testInterface2.handleString("123", "456");
System.out.println(result2);
}
}
我们可以把一个实例的非静态方法作为Lambda体,比如这样:
@FunctionalInterface
interface TestInterface {
String handleString(String a, String b);
}
class TestClass {
public String concatString(String a, String b) {
return a + b;
}
}
public class Test {
public static void main(String[] args) {
TestClass testClass = new TestClass();
TestInterface testInterface = testClass::concatString;
String result = testInterface.handleString("abc", "def");
System.out.println(result);
//相当于以下效果,直接把类的静态方法写在Lambda体里
TestInterface testInterface2 = (a, b) -> testClass.concatString(a, b);
String result2 = testInterface2.handleString("123", "456");
System.out.println(result2);
}
}
类::实例方法
这种模式并不是要直接调用类的实例方法,这样显然连编译都过不去。
这种模式实际上是 对象::实例方法模式的一种变形,当一个对象调用方法时,方法的某个参数是函数式接口,而且函数式接口的方法参数列表的第一个参数就是调用者对象所属的类时,可以引用调用者类中定义的,不包含函数式接口第一个参数的方法,并用类::实例方法这种形式来表达,比如这样:
@FunctionalInterface
interface TestInterface<T> {
String handleString(T a, String b);
}
class TestClass {
String oneString;
public String concatString(String a) {
return this.oneString + a;
}
public String startHandleString(TestInterface<TestClass> testInterface, String str) {
String result = testInterface.handleString(this, str);
return result;
}
}
public class Test {
public static void main(String[] args) {
TestClass testClass = new TestClass();
testClass.oneString = "abc";
String result = testClass.startHandleString(TestClass::concatString, "123");
System.out.println(result);
//相当于以下效果
TestClass testClass2 = new TestClass();
testClass2.oneString = "abc";
TestInterface theOne2 = (a, b) -> testClass2.concatString(b);
String result2 = theOne2.handleString(theOne2, "123");
System.out.println(result2);
}
}
对这种模式的理解大概是这样的:
当一个对象调用一个方法,方法的参数中包含一个函数式接口,该函数式接口的第一个参数类型是这个对象的类,那么这个函数式接口可用方法引用代替,并且替换用的方法可以不包含函数式接口的第一个参数(调用对象的类)。
构造器引用,Class::new
这种模式被称为构造方法引用,或构造器引用。
构造方法也是方法,构造方法引用实际上表示一个函数式接口中的唯一方法引用了一个类的构造方法,引用的是那个参数相同的构造方法。
下面举个例子:
@FunctionalInterface
interface TestInterface {
TestClass getTestClass(String a);
}
class TestClass {
String oneString;
public TestClass() {
oneString = "default";
}
public TestClass(String a) {
oneString = a;
}
}
public class Test {
public static void main(String[] args) {
TestInterface testInterface = TestClass::new;
TestClass testClass = testInterface.getTestClass("abc");
System.out.println(testClass.oneString);
//相当于以下效果
TestInterface testInterface2 = (a) -> new TestClass("abc");
TestClass testClass2 = testInterface2.getTestClass("123");
System.out.println(testClass2.oneString);
}
}
注意:
1,函数式接口的方法getTargetClass(String a)有一个String类型的参数,所以当使用构造方法引用时,引用的是有一个String参数的那个构造,也就是这个:
public TargetClass(String a) {
oneString = a;
}
2、本例输出的结果是:
abc
abc
注意到第二行输出的不是123,而是abc,因为Lambda表达式的原因,当我们执行:
imTheOne2.getTargetClass("123");
这行代码时,调用的实际上是Lambda体:
new TargetClass("abc");
所以输出的结果和字符串"123"没关系。
写在最后
附上官方文档中的一个例子:
import java.util.function.BiFunction;
public class MethodReferencesExamples {
public static <T> T mergeThings(T a, T b, BiFunction<T, T, T> merger) {
return merger.apply(a, b);
}
public static String appendStrings(String a, String b) {
return a + b;
}
public String appendStrings2(String a, String b) {
return a + b;
}
public static void main(String[] args) {
MethodReferencesExamples myApp = new MethodReferencesExamples();
// Calling the method mergeThings with a lambda expression
System.out.println(MethodReferencesExamples.
mergeThings("Hello ", "World!", (a, b) -> a + b));
// Reference to a static method
System.out.println(MethodReferencesExamples.
mergeThings("Hello ", "World!", MethodReferencesExamples::appendStrings));
// Reference to an instance method of a particular object
System.out.println(MethodReferencesExamples.
mergeThings("Hello ", "World!", myApp::appendStrings2));
// Reference to an instance method of an arbitrary object of a
// particular type
System.out.println(MethodReferencesExamples.
mergeThings("Hello ", "World!", String::concat));
}
}