2008年的今天,科比领取了属于他的MVP奖杯,又是想念老大的一天┭┮﹏┭┮
人年纪大了,一个有关的字眼都会激起心中那思念的层层涟漪~
source
在具体理解Java中的方法引用之前,我们首先来回顾一下之前已经所学的一些东西,并由由这些东西来看一下方法引用出现的意义是什么,以及方法引用在程序中是如何使用的。
假设我们现在定义了一个函数式接口(为了方便和后面的内容连接,这里以函数式接口为例)如下所示,它其中只有一个抽象方法,实现输出所接收的字符串参数:
@FunctionalInterface
public interface Printable {
public abstract void print(String s);
}
那么如何使用定义的接口呢?到目前来说,函数式接口的使用三种有三种方式:
创建接口的实现类,通过调用实现类的对象使用
public class PrintableImpl implements Printable{
@Override
public void print(String s) {
System.out.println(s);
}
}
通过匿名内部类直接创建对象使用
public class PrintDemo {
public static void main(String[] args) {
String str = "hello world...";
new Printable() {
@Override
public void print(String s) {
System.out.println(s);
}
}.print(str);
}
}
通过Lambda表达式使用
public class PrintDemo {
public static void main(String[] args) {
String str = "hello world...";
show(str, s-> System.out.println(s));
}
public static void show(String str, Printable p){
p.print(str);
}
}
Lambda表达式的使用已经在很大程度上简化了代码的书写,并且很好的体现了函数式编程的思想。Lambda表达式传递的代码就是一种实现逻辑:拿什么参数做什么操作,如上面的案例中只有将传入的s输出,并没有其他的处理逻辑。这样看起来好像已经很好了,代码相当的简洁。如果中间的实现逻辑较复杂,而且我们需要在程度的不同地方多次使用相同的处理逻辑,那么就要多次重复的写相同处理逻辑的Lambda表达式,不免显得冗余。
方法引用的出现可以有效的解决上述问题,当对象和方法都已经存在时,可以直接使用方法引用来优化Lambda表达式。如上所示的Lambda表达式:
show(str, s-> System.out.println(s));
System.out对象和println()
已经存在,因此按照方法引用的思想,Lambda表达式可写为:
show(str, System.out::println);
以show(str, System.out::println);
为例说明,::
称为引用运算符,而它所在的表达式就被称为方法引用。如果Lambda表达式要表达的函数方案已经存在于某个方法的实现中,可以直接使用::
来引用该方法代替Lambda表达式。就上面对于同一种操作的两种实现方法来看:
System.out.println()
进行输出println()
来代替Lambda表达式,它的写法更加简洁,体现了复用的思想Java8之后方法引用有四种使用方式:
使用方式 | 方法引用写法 | Lambda表达式写法 |
---|---|---|
静态方法引用 | ClassName::staticMethod | (args) -> ClassName.staticMethod(args) |
类的实例方法引用 | object::instanceMethod | (args) -> object.instanceMethodName(args) |
对象的实例方法引用 | ClassName::instanceMethod | (args) -> ClassName.instanceMethod(args) |
构造方法引用 | ClassName::new | (args) -> new ClassName(args) |
我们继续使用如下的函数式接口:
@FunctionalInterface
public interface Printable {
public abstract void print(String s);
}
并创建StringConvert类来实现字符串的大小写转换:
public class StringConvert {
public void StringToUppercase(String s){
System.out.println(s.toUpperCase());
}
public static void StringToLowercase(String s){
System.out.println(s.toLowerCase());
}
}
我们实现一个方法实现将一个字符串传递给Printable接口,然后调用其中的print()
:
public static void showString(String str, Printable p){
p.print(str);
}
那么如何来使用StringConvert类中的成员方法呢?同样我们可以使用Lambda表达式进行使用:
public class StringConvertDemo {
public static void main(String[] args) {
String str = "HELLO world...";
// 通过Lambda表达式使用成员方法
showString(str, (s)->{
StringConvert st = new StringConvert();
st.StringToUppercase(s); // HELLO WORLD...
});
}
public static void showString(String str, Printable p){
p.print(str);
}
}
根据方法引用使用的两大前提
可知,同样可以使用方法引用。对象名我们使用匿名对象,方法名就是StringToUppercase
。因此,使用方法引用可写作:
public class StringConvertDemo {
public static void main(String[] args) {
String str = "HELLO world...";
// 通过对象名引用成员方法
showString(str, new StringConvert()::StringToUppercase); // HELLO WORLD...
System.out.println("------------");
}
public static void showString(String str, Printable p){
p.print(str);
}
}
通过new StringConvert()::StringToUppercase
来实现接口的实现逻辑,而且在不同的地方调用都可以使用它,而不必重写Lambda中的逻辑。
如上所示,在StringConvert类中我们还定义了一个静态方法StringToLowercase()
来实现字符串转为小写形式。有了方法引用,我们就可以直接通过类名引用类中的静态方法:
public class StringConvertDemo {
public static void main(String[] args) {
String str = "HELLO world...";
// 通过类名直接引用静态方法
showString(str, StringConvert::StringToLowercase); // hello world...
}
public static void showString(String str, Printable p){
p.print(str);
}
}
首先创建一个Person类作为父类,类中只有一个成员方法,用于接受一个字符串然后输出一句话
public class Person {
public void say(String s){
System.out.println("Say hello to " + s);
}
}
然后定义一个子类Student,类中重写了父类中的say()
。同时创建成员方法showInfo()
来继续使用前面的printable
接口,用来输出传入的字符串:
package MethodReference;
public class Student extends Person{
private String name;
private int age;
public Student() {
}
public Student(String name) {
this.name = name;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public void say(String s) {
System.out.println("Show love to " + s);
}
public void showInfo(String s, Printable p){
p.print(s);
}
}
那么如何使用父类中的say()
呢?最直接的方法就是直接创建父类对象,通过调用对象的say()
实现:
public void method(String s){
showInfo(s, (str)->{
Person p = new Person();
p.say(str); // Say hello to kobe
});
}
通过前面所学的对于super关键字的使用可知,我们可以在子类中可以使用super.methodName
的方法来直接显式使用父类中方法。因此,上面的代码可写作:
public void method(String s){
showInfo(s, str-> super.say(str)); // Say hello to kobe
}
浅理解Java中的继承
如果使用方法引用,代码可继续简化写作:
public void method(String s){
showInfo(s, super::say); // // Say hello to kobe
}
同样是使用上面的Person类和Student类,如何使用本类中的成员方法呢?一种是通过类名直接引用,另一种是使用this.methodName
显式引用
public void method1(String s){
showInfo(s, str->say(str)); //Show love to kobe
showInfo(s, str->this.say(str)); // Show love to kobe
}
而如果使用方法引用可直接写作:
public void method1(String s){
showInfo(s, this::say); // Show love to kobe
}
上面的Student类中定义了一个无参构造和两个带参构造,那么如何使用方法引用来调用构造方法创建对象呢?首先新建一个函数式接口,接收字符串返回Person对象
@FunctionalInterface
public interface Studentbuilder {
Student building(String s);
}
在Student类中新建方法showInfo1()
,方法体中通过Studentbuilder
接口创建Student对象,然后输出对象的名字。
public void showInfo1(String s, Studentbuilder p){
Student stu = p.building(s);
System.out.println(stu.getName());
}
使用new 关键字可以在Lambda表达式中直接创建对象:
public void method2(String s){
showInfo1(s, str-> new Student(str)); // kobe
}
而使用方法引用后,上面的代码可写作:
public void method2(String s){
showInfo1(s, Student::new); // kobe
}
完整Student类实现和测试代码:
public class Student extends Person{
private String name;
private int age;
public Student() {
}
public Student(String name) {
this.name = name;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public void say(String s) {
System.out.println("Show love to " + s);
}
public void showInfo(String s, Printable p){
p.print(s);
}
public void showInfo1(String s, Studentbuilder p){
Student stu = p.building(s);
System.out.println(stu.getName());
}
// 通过super关键字应用父类的方法
public void method(String s){
showInfo(s, (str)->{
Person p = new Person();
p.say(str); // Say hello to kobe
});
System.out.println("------------");
showInfo(s, str-> super.say(str)); // Say hello to kobe
System.out.println("------------");
showInfo(s, super::say); // // Say hello to kobe
System.out.println("------------");
}
// 通过this关键字使用本类成员方法
public void method1(String s){
showInfo(s, str->say(str)); //Show love to kobe
System.out.println("------------");
showInfo(s, str->this.say(str)); // Show love to kobe
System.out.println("------------");
showInfo(s, this::say); // Show love to kobe
System.out.println("------------");
}
// 使用类名引用new创建对象
public void method2(String s){
showInfo1(s, str-> new Student(str)); // kobe
System.out.println("------------");
showInfo1(s, Student::new); // kobe
}
public static void main(String[] args) {
new Student().method("kobe");
new Student().method1("kobe");
new Student().method2("kobe");
}
}
首先创建一个函数式接口Arraybuilder,接口中的抽象方法实现根据传递的size创建int型数组。
@FunctionalInterface
public interface Arraybuilder {
int[] building(int size);
}
如果使用Lambda表达式,代码可写作:
public class ArrayDemo {
public static void main(String[] args) {
int[] arr = build(5, (len) -> new int[len]);
System.out.println(arr.length); // 5
}
public static int[] build(int size, Arraybuilder ab){
return ab.building(size);
}
}
而使用方法引用可简化写作:
public class ArrayDemo {
public static void main(String[] args) {
int[] arr2 = build(10, int[]::new);
System.out.println(arr2.length); // 10
}
public static int[] build(int size, Arraybuilder ab){
return ab.building(size);
}
}
其他类型的数组创建同理。
不管是通过方法引用简化什么样的Lambda表达式,只有当Lambda表达式中只调用一个方法,方法可能是对象的实例方法、类的静态方法、构造方法等等,而不做其他的操作时,才能把Lambda表达式重写为方法引用。
如:s->s.length() == 0
是一个只有一行逻辑代码的Lambda表达式,但是它不能使用方法引用重写。因此它执行了两步逻辑,首先是求得s的长度,然后判断长度是否为0.
方法引用使用运算符::连接类(或对象)与方法名称(或new)
实现在特定场景下lambda表达式的简化表示,使用时要注意方法引用的使用场景及各种方法引用的特性。使用方法引用的好处是能够更进一步简化代码编写,使代码更简洁。但同样的需要注意的是,代码越简化,代码的理解难度也就随之增加,故需要根据具体的场景具体考虑是否使用方法引用。