什么是方法引用?
方法引用是Java8的新特性,是传递Lambda表达式的替代方案,没学过Lambda表达式的,建议先看看博主的另一篇博客Lambda表达式
为什么要使用方法引用?
在回答这个问题之前我们先看个例子。
public class Main {
public static void main(String[] args){
Stream<String> integerStream = Stream.of("1,2,3,4,5,6".split(","));
integerStream.map(x -> Integer.valueOf(x)).filter(x -> x > 3).forEach(x -> System.out.print(x));
}
}
在上面的代码中,我们一共使用了三次Lambda表达式,第一个表达式将x转化为Integer类型并返回,而第二个和第三个表达式则没有对x做任何操作,只是将x作为输出语句的实参传递到输出语句中。这种不对参数进行任何操作,仅作为传输媒介的Lambda表达式,就叫传递Lambda表达式。
这类表达式可以正常运行,只是其语法对于当前这个任务而来说太过复杂。我们就开始思考,如何简化这类表达式语法?
针对这个问题,Java8给出了新的解决方案——方法引用
如何使用方法引用
非构造器方法语法:
(1)方法的所有者对象::方法名(当参数作为被调用方法的实参时)
(2)参数所属类名::方法名(参数作为目标,即被调用方法是参数自身的方法)
非当前类的成员、静态方法的调用
以例子中的代码的代码为例,print方法属于System.out方法创建的PrintStream对象,所以该方法引用为System.out::print,而parseInt方法属于Integer的字节码对象,所以其方法引用为Integer::parseInt。
使用方法引用调用print和parseInt方法:
public class Main {
public static void main(String[] args){
Stream<String> integerStream = Stream.of("1,2,3,4,5,6".split(","));
integerStream.map(Integer::valueOf).filter(x -> x > 3).forEach(System.out::print);
}
}
可以很明显的看到,使用方法引用后,代码逻辑相对之前简单了很多。
上面是将参数作为非当前类的成员、静态方法的实参传递到该方法时的引用语法。
我们再对代码做个扩展,要求在流中过滤小于3的元素后,再次将Integer元素转为int类型,我们知道Integer的成员方法intValue可以实现该需求,此时可以使用方法引用的第二种语法,我们知道流中参数都是Integer对象,所以方法引用为Integer::intValue。
public class Main {
public static void main(String[] args){
Stream<String> integerStream = Stream.of("1,2,3,4,5,6".split(","));
integerStream.map(Integer::valueOf).filter(x -> x > 3).map(Integer::intValue).forEach(System.out::print);
}
}
当前类成员、静态方法的调用
方法引用调用本类中的成员及静态方法与调用其他类类似,
以当前类为例,在类中添加一个myPrint的成员方法,一个myParseInt的静态方法。
myPrint属于Main对象所有,所以是其方法引用为new Main()::myPrint,而myParseInt属于Main字节码对象所有,所以是其方法引用为Main::myParseInt。
使用方法引用调用myPrint和myParseInt。
public class Main {
public static void main(String[] args){
Stream<String> integerStream = Stream.of("1,2,3,4,5,6".split(","));
integerStream.map(Main::myParseInt).filter(x -> x > 3).forEach(new Main()::myPrint);
}
public void myPrint(Integer msg){
System.out.print(msg);
}
public static int myParseInt(String numStr){
return Integer.valueOf(numStr).intValue();
}
}
这里解释一下,为什么我不直接用this而是使用new关键字重新实例化一个对象。
因为在类加载时对象的实例化是一定晚于静态方法加载的,所以this关键字不能在被static修饰的方法中使用。
那么在什么情况下可以使用this进行方法引用呢?
将以上代码重构,将对流的操作提取到另一个类StreamUtil中。
public class Main {
public static void main(String[] args){
Stream<String> integerStream = Stream.of("1,2,3,4,5,6".split(","));
new StreamUtil().myStream(integerStream);
}
}
class StreamUtil{
void myStream(Stream stream){
stream.map(StreamUtil::myParseInt).filter(x -> ((Integer) x).intValue() > 3).forEach(this::myPrint);
}
public void myPrint(Object msg){
System.out.print((Integer) msg);
}
public static Integer myParseInt(Object numStr){
return Integer.valueOf((String)numStr);
}
}
在子类中调用当前类父类方法与在子类中调用成员方法相似,只是将this换为super。
类构造函数的调用
语法:
类名::new
将非当前类的成员、静态方法的调用中的代码做个调整,不在进行输出,而是将其保存在ArrayList集合中。
public class Main {
public static void main(String[] args){
Stream<String> integerStream = Stream.of("1,2,3,4,5,6".split(","));
ArrayList<Integer> collect = integerStream.map(Integer::valueOf).filter(x -> x > 3).collect(Collectors.toCollection(ArrayList::new));
System.out.println(collect);
}
模糊性与方法引用
至此,使用方法引用的使用就正式介绍完毕,但使用方法引用时应该避免调用指向性不明确的方法,例如Integer的toString方法。
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
public static String toString(int i, int radix) {
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
radix = 10;
/* Use the faster version */
if (radix == 10) {
return toString(i);
}
char buf[] = new char[33];
boolean negative = (i < 0);
int charPos = 32;
if (!negative) {
i = -i;
}
while (i <= -radix) {
buf[charPos--] = digits[-(i % radix)];
i = i / radix;
}
buf[charPos] = digits[-i];
if (negative) {
buf[--charPos] = '-';
}
return new String(buf, charPos, (33 - charPos));
}
public String toString() {
return toString(value);
}
以上三个方法都是Integer中的toString方法,第一个和第二个是Integer中自定义的两个静态方法,第三个是重写的Object中的toString方法。
所以此时Integer中存在三个toString方法,如果此时通过方法调用,例如使用Integer::toString进行方法调用,此时能被调用到的方法有两个,一个是Integer类中一个参数的静态方法,另一个是Integer对象的成员toString方法,此时JVM无法推断出到底调用的哪个方法。这就是方法引用的模糊性。