JAVA方法method编程_浅谈Java 8中的方法引用(Method References)

本人接触Java 8的时间不长,对Java 8的一些新特性略有所知。Java 8引入了一些新的编程概念,比如经常用到的 lambda表达式、Stream、Optional以及Function等,让人耳目一新。这些功能其实上手并不是很难,根据别人的代码抄过来改一下,并不要知道内部的实现原理,也可以很熟练地用好这些功能。但是当我深究其中一些细节时,会发现有一些知识的盲区。下面我就来谈一下Java 8中的Method References这个概念。

1. 什么是方法引用(Method References)

方法引用(Method References)是一个与Lambda表达式、函数式接口(Functional Inferface)紧密关联的概念。如我们所知,函数式接口(Functional Inferface)是一种有且仅有一个抽象方法的接口。而Lambda表达式是Java 8引入的对于函数式接口更加简洁的实现方式。方法引用(Method References)则是另一种对函数式接口的实现方式。下面是Java 8中的一个函数式接口(Functional Inferface)的一般性定义,它可以拥有一个或多个default 方法和 static方法,但只能拥有一个抽象方法(这里abstract关键字可以被省略)。

/***这就是一个Functional Interface,无论加不加注解@FunctionalInterface,这都是一个Functional Interface。*/

public interfaceA {public abstract void method1(inta);public default void method2(intb) {//Do something

};public static void method3(intc) {//Do something

};

}

如果有其他的方法需要以 Interface A的实例作为入参时,在Lambda表达式出现之前,我们一般会使用匿名内部类的方式来处理。如下所示:

public classB {

//类B的某一个方法的入参需要传入接口A的一个实例public voidmethod1(A a) {

a.method1(1);

a.method2(2);

}public static voidmain(String args[]){

B b= newB();//使用匿名内部类实现函数式接口A的唯一抽象方法,并传入实例

b.method1(newA() {

@Overridepublic void method1(inta) {//Do something

}

});

}

}

Lambda表达式出现以后,我们开始使用下面这种方式:

public classB {

//类B的某一个方法的入参需要传入接口A的一个实例public voidmethod1(A a) {

a.method1(1);

a.method2(2);

}public static voidmain(String args[]){

B b= newB();//使用Lambda表达式实现函数式接口的唯一抽象方法

b.method1( (a)->{ /*Do something*/});

}

}

使用方法引用(Method References),可以将上面的代码转换为如下代码:

public classB {//类B的某一个方法的入参需要传入接口A的一个实例

public voidmethod1(A a) {

a.method1(1);

a.method2(2);

}//类B的两个静态方法

public static void method2(inta) {//Do something

}public static int method3(inta) {//Do something

return 1;

}public static voidmain(String args[]){

B b= newB();//使用匿名内部类实现函数式接口的唯一抽象方法

b.method1((a)->{/*Do something*/});

b.method1(B::method2);

b.method1(B::method3);//由于接口中的方法返回类型是void,此处会丢弃B::method3的返回值

}

}

这种用类名加两个冒号的写法,就是方法引用(Method References)。有点类似于C语言的函数指针,将一个方法作为另一个方法的入参。在面向对象语言中,这是一种将方法对象化的方式。在我们已经有现成的方法时,不需要再去实现一遍这个方法,只需要把现有的方法视为一个对象,将它的引用作为入参,比Lambda表达式还要方便快捷。

2. 方法引用的种类

官方的文档给出了4中类型的方法引用:

KindExample

Reference to a static method

ContainingClass::staticMethodName

Reference to an instance method of a particular object

containingObject::instanceMethodName

Reference to an instance method of an arbitrary object of a particular type

ContainingType::methodName

Reference to a constructor

ClassName::new

表1. 方法引用类型表

Reference to a static method就是我们上面代码中所示,将一个static method作为方法引用。

Reference to an instance method of a particular object也很好理解,当我们需要使用某个方法时,发现它不是static method,这时我们需要先生成一个拥有这个方法的对象实例,然后通过实例的引用标识符去调用这个方法:

public classB {//类B的某一个方法的入参需要传入接口A的一个实例

public voidmethod1(A a) {

a.method1(1);

a.method2(2);

}//类B的两个静态方法

public static void method2(inta) {//Do something

}public static int method3(inta) {//Do something

return 1;

}

//类B的两个成员方法public void method4(inta) {//Do something

}public int method5(inta) {//Do something

return 1;

}public static voidmain(String args[]){

B b= newB();//使用匿名内部类实现函数式接口的唯一抽象方法

b.method1((a)->{/*Do something*/});

b.method1(B::method2);

b.method1(B::method3);//由于接口中的方法返回类型是void,此处会丢弃B::method3的int返回值

B anotherB=newB();

b.method1(anotherB::method4);

b.method1(anotherB::method5);//同上,由于接口中的方法返回类型是void,此处会丢弃anotherB::method5的int返回值

}

}

大家应该注意到了,这里有一个比较特殊的处理,虽然接口A中的方法method1的返回类型为void,但仍然可以传入一个返回类型为int的方法引用。如果接口A的返回类型为int,方法引用的返回参数可以是byte,但不能是long。这里大家如果感兴趣可以继续深入的研究。

Reference to an instance method of an arbitrary object of a particular type,这个相对复杂一点,官方文档也一笔带过了,这里我们再深入一点。首先我们先分析官方文档中的例子:

The following is an example of a reference to an instance method of an arbitrary object of a particular type:

String[] stringArray = { "Barbara", "James", "Mary", "John",

"Patricia", "Robert", "Michael", "Linda" };

Arrays.sort(stringArray, String::compareToIgnoreCase);

The equivalent lambda expression for the method reference String::compareToIgnoreCase would have the formal parameter list (String a, String b), where a and bare arbitrary names used to better describe this example. The method reference would invoke the method a.compareToIgnoreCase(b).

这里需要先知道Arrays.sort和Comparator的代码大概做了什么:

public classArrays {//..........

public static void sort(T[] a, Comparator super T>c) {if (c == null) {

sort(a);

}else{if(LegacyMergeSort.userRequested)

legacyMergeSort(a, c);elseTimSort.sort(a,0, a.length, c, null, 0, 0);

}

}//.............

}

这里相当于将String::compareToIgnoreCase作为方法引用去实现Comparator接口,那么Comparator接口中有什么呢?其实就是下面代码中描述的:

@FunctionalInterfacepublic interface Comparator{intcompare(T o1, T o2);

}

而String中的compareToIgnoreCase却只有一个入参,与Comparator中的compare方法参数列表不一致:

Class String{//......

public intcompareToIgnoreCase(String str) {return CASE_INSENSITIVE_ORDER.compare(this, str);

}//.......

}

这时在使用Arrays.sort(stringArray, String::compareToIgnoreCase);时 sort方法中是不知道第二个参数传过来的是String::compareToIgnoreCase的,依然会使用类似c.compare(stringArray[i],stringArray[j])的语句去比较字符串。但这时实际执行的是stringArray[i].compareToIgnoreCase(stringArray[j])。这是官方的一个例子,下面我们来自己写一个更为普遍一些的例子。

如下所示,接口A中的method1有两个入参,第一个入参为class B的引用,在main方法中我们向B.method1中传递了一个方法引用B::method2。根据前面的表1,我们已经知道,如果method2是B中的静态方法,我们可以使用B::method2,否则我们只能先new一个B的实例,比如 B b=new B(); 然后使用b::method2。这里method2不是一个静态方法,但是我们仍然使用了B::method2,为什么呢?这就是方法引用的“Reference to an instance method of an arbitrary object of a particular type”。这时在B.method1中,并不知道参数a具体是通过什么方式实现的,有可能是用匿名内部类,有可能是lambda表达式,有可能是其他类的静态方法等等。所以在B.method1中只能使用接口A声明的方式去调用。但是实际上,Java 8在这里进行了处理,对方法引用进行了转换,接口方法中的第一个参数,映射为实例的引用,第二个参数才是这个实例的方法中的参数。方法引用--这一概念最好的体现就在于此,将B::method2赋值给接口A的一个引用,即使参数个数不同也可以赋值。

@FunctionalInterfacepublic interfaceA {public abstract int method1(B b,inta);

}public classB {public static voidmethod1(A a) {

B b=new B();

a.method1(b,1);

/* | |

-------/ |

/ /

↓ ↓

b.method2(1) */

}public int method2(int a) { return 1;}public static voidmain(String args[]){

B.method1(B::method2);

}

}

再来说下Reference to a constructor,这种方法引用的特殊之处在于使用ClassName::new来表示构造函数。当然,官方文档中已经解释的很好了,我这里仅做一下概括。如下所示,一般的文章会把B.method1(B::new)等同于lambda表达式B.method1( ()->{return new B()} ), 但在下面的例子中,上述转换无法编译。因为接口A中的抽象方法method1的返回值为void。但这时仍可以使用B::new,也可以正常打印出1024。

@FunctionalInterfacepublic interfaceA {public abstract void method1(inta);

}public classB {intvalue;public static voidmethod1(A a) {

a.method1(1024);

}public B(intvalue) {this.value=value;

System.out.println(value);

}public static voidmain(String args[]){

B.method1(B::new);

B.method1( ()->{return new B()} ) //compile error

}

}

应用举例

在Java8中引入了java.util.function,其中最典型的且使用最广的是Interface Function。

@FunctionalInterfacepublic interface Function{/*** Applies this function to the given argument.

*

*@paramt the function argument

*@returnthe function result*/R apply(T t);//......

}

程序员可以根据需要implement interface Function,即实现其中的apply方法。其实这是方法对象化的另一种实现方式,即将一般的单个参数的函数抽象成一个Function接口,在其他某个地方程序会调用 apply,对这个函数进行执行。这个函数可以在运行时根据情况调用不同的实例,展现了程序的多态性。如下代码展示了三种实现Function接口的方式。可以直接实现这个接口,也可以用lambda表达式,也可以用方法引用。

public classB {public static void method1(Integer input,Functiona) {

Boolean result=a.apply(input);

System.out.println(result);

}public static voidmain(String args[]){

B.method1(new Integer(1024),new Function(){

@OverridepublicBoolean apply(Integer t) {return t==0?false:true;

}

});//implements a new class

B.method1(new Integer(1024), (x)->{return x==0?false:true;});//lambda

B.method1(new Integer(1024),B::method2);//method reference

}public staticBoolean method2(Integer x) {return x==0?false:true;

}

}

再介绍一个进阶用法,将Reference to an instance method of an arbitrary object of a particular type与Function结合。我们来看下面的例子,其实StringBuffer的toString()方法是没有任何入参的,但是可以作为方法引用对Function a 进行赋值。这是代码中是使用 a.apply(input) 这种方式,但实际上是在执行input.toString()。

importjava.util.function.Function;public classB {public static void method1(StringBuffer input,Functiona) {

System.out.println(a.apply(input)); // 等于input.toString()

}public static voidmain(String args[]){

StringBuffer buf=newStringBuffer();

buf.append("Reference to an instance method of an arbitrary object of a particular type");

buf.append(" + Function");

B.method1(buf, StringBuffer::toString);

}

}

结语

方法引用(Method References)的上述4种使用场景十分灵活,与lambda表达式、函数式接口(Functional Inferface)共同组成了Java对于方法对象化的实现。在此基础上又扩展出了Java 8的Function包中的众多类,而Function包又对Stream包等一系列包提供了强大的支持。Java8 的编程因此变得更为灵活。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值