
一句话介绍:
方法引用(Method Reference)是在 Lambda 表达式的基础上引申出来的一个功能。
先不铺展概念,从一个示例开始说起。
List<Integer> list = Arrays.asList(1, 2, 3);
list.forEach(num -> System.out.println(num));
上面是一个很普通的 Lambda 表达式:遍历打印列表的元素。
相比 JDK8 版本以前的 for  循环或 Iterator 迭代器方式,这种 Lambda 表达式的写法已经是一种很精简且易读的改进。但有没有更精简的改进?
答案是有!下面就有请方法引用出场:
list.forEach(System.out::println);
没用过这种方式的小伙伴,可能会纳闷:这是什么鬼?为什么编译器竟然不报错?该怎么理解?
这其实就是一种方法引用。中间的两个冒号“::”,就是 Java 语言中方法引用的特有标志,出现它,就说明使用到了方法引用。
因为 Foreach() 方法的形参是 Consume<T> 对象,所以,上面方法引用的方式等同于如下表达:
Consumer<Integer> consumer = System.out::print;
list.forEach(consumer);
有木有很神奇?System.out::print  语句的左值可以是一个 Consumer对象。从编译器的角度来理解,等号右侧的语句是一种方法引用,那么编译器会认为该语句引用的是 Consumer接口的 accept(T t) 抽象方法。
下面来细细拆分一下输出语句:System.out.println(); 。
System 是一个可不变类,包含了多个域变量和静态方法,之所以能使用 System.out 这种形式,就因为 out 是它的一个静态变量,且是一个  PrintStream  对象:
/**
 * The "standard" output stream. This stream is already
 * open and ready to accept output data. Typically this stream
 * corresponds to display output or another output destination
 * specified by the host environment or user.
 * <p>
 * For simple stand-alone Java applications, a typical way to write
 * a line of output data is:
 * <blockquote><pre>
 *     System.out.println(data)
 * </pre></blockquote>
 * <p>
 * See the <code>println</code> methods in class <code>PrintStream</code>.
 *
 * @see     java.io.PrintStream#println()
 * @see     java.io.PrintStream#println(boolean)
 * @see     java.io.PrintStream#println(char)
 * @see     java.io.PrintStream#println(char[])
 * @see     java.io.PrintStream#println(double)
 * @see     java.io.PrintStream#println(float)
 * @see     java.io.PrintStream#println(int)
 * @see     java.io.PrintStream#println(long)
 * @see     java.io.PrintStream#println(java.lang.Object)
 * @see     java.io.PrintStream#println(java.lang.String)
*/
public final static PrintStream out = null;
而 println(xxx) 是 PrintStream 类里一个普通方法。println(xxx) 方法有多个重载,不同点在入参的类型,可以使 int、float、double、char、char[]、boolean、long 等。
public void println(T x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}
前面啰嗦那么多,重点来了!
println(xxx)  方法的特点是只有一个入参,没有出参。这个和 Consumer<T>  函数式接口的 accept(T t)  是不是很像?这也是方法引用的精髓:
只要一个已存在的方法,其入参类型、入参个数和函数式接口的抽象方法相同(不考虑两者的返回值),就可以使用该方法(如本例中的 println(xxx)),来指代函数式接口的抽象方法(如本例中的  accept(T t) 方法),等于是该抽象方法的一种实现,也不需要继承该函数式接口。
直接用已存的类名 + 两个冒号 + 方法名即可:类名::方法名。注意,这里的方法名是不带括号的。
这个比 Lambda 表达式还省事,Lambda 表达式是在不继承接口的基础上,直接用形如  () -> {} 的方式变相实现了抽象方法,方法引用是直接用已存的方法来指代该抽象方法!
总结一下,方法引用解决了什么问题?
它解决了代码功能复用的问题,使得表达式更为紧凑,可读性更强,借助已有方法来达到传统方式下需多行代码才能达到的目的。
方法引用的语法很简单。
使用一对冒号 :: 来完成,分为左右两个部分,左侧为类名或对象名,右侧为方法名或 new 关键字。有以下四种类型:
## 方法引用的几种类型:
1、构造器引用,形式为 类名::new
2、静态方法引用,形式为 类名::方法名
3、类特定对象的方法引用,形式为 类对象::方法名
4、类的任意对象引用,形式为 类名::方法名
看个非常简单的示例,对应了上面的四种引用类型。
public class Animal {
    private String name;
    public Animal() {
    }
    public Animal(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public static Animal getInstance(Supplier<Animal> supplier) {
        return supplier.get();
    }
    public void guard(Animal animal) {
        System.out.println(this.getName() + " guard " + animal.getName());
    }
    public void sleep() {
        System.out.println(this.getName() + " sleep.");
    }
    public static void bodyCheck(Animal animal) {
        System.out.println("body check " + animal.getName());
    }
}
定义了一个简单的 Animal 类,包含了静态方法、普通方法、有参构造函数等。
接下来,我们看下基于这个  Animal 类,四种方法引用类型的使用:
public static void main(String[] args) {
    List<Animal> animalList = new ArrayList<Animal>() {{
        add(new Animal("sheep"));
        add(new Animal("cow"));
    }};
    System.out.println("---- 构造器引用 ----");
    Animal pig = Animal.getInstance(Animal::new);
    pig.sleep();
    System.out.println("\n---- 静态对象的引用 ----");
    animalList.forEach(Animal::bodyCheck);
    System.out.println("\n---- 类特定对象的引用 ----");
    Animal dog = new Animal("dog");
    animalList.forEach(dog::guard);
    System.out.println("\n---- 类的任意对象的引用 ----");
    animalList.forEach(Animal::sleep);
}
如果上面的代码你都理解了,那方法引用你也已经基本掌握了。
下面,针对方法引用的这几种类型,各自再详细解释。
语法很简单:类名::方法名,使用方式如下:
// 示例 1
Supplier<List<Integer>> supplier1 = ArrayList::new;
List<Integer> list = supplier1.get();
// 示例 2
Supplier<Animal> supplier2 = Animal::new;
Animal animal = supplier2.get();
之所以能赋值给 Supplier 接口,是因为其抽象方法 get() 没有入参,与类的无参构造函数一致。
@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
这里还需要注意一点,自定义的类必须有“无参构造函数”,否则编译器会报错。
我们都知道,当创建一个类后,如果不显式声明构造函数,编译器会默认加一个无参构造函数。但如果有显式声明一个或多个有参构造函数,则编译器不再默认追加无参构造函数。如下:
public class Animal {
    private String name;
    public Animal(String name) {
        this.name = name;
    }
}
上面代码中的 Animal 类只有一个构造函数  Animal(String name),不再有无参构造函数。这种方式下使用构造器引用就会报错:
Supplier<Animal> supplier = Animal::new; // 编译报错:Cannot resolve constructor 'Animal'
语法为 类名::静态方法名。
还是以上面的 Animal 类为例,为了更好展示静态方法引用,相比上面的示例,我们适当做一下调整:
public class Animal {
    private String name;
    private Integer weight;
    public Animal() {
    }
    public Animal(String name, Integer weight) {
        this.name = name;
        this.weight = weight;
    }
    public String getName() {
        return name;
    }
    public Integer getWeight() {
        return weight;
    }
    ...
    public static void bodyCheck(Animal animal) {
        System.out.println("body check " + animal.getName());
    }
    public static Integer compareByName(Animal one, Animal another) {
        return one.getName().compareTo(another.getName());
    }
    public Integer compareByWeight(Animal one, Animal another) {
        return one.getWeight() - another.getWeight();
    }
}
Animal 类有两个成员变量 name 和 weight,它有多个方法,其中包括两个静态方法  compareByName() 和  compareByWeight()。
给定一个 Animal  对象列表,如果我们想根据名称排序,可以怎么做?你想到了几种方式?
Collections.sort(List<T> list) 方法这种方式,需要 Animal 类实现 Coparable<T> 接口,给出  compareTo(T t)  抽象方法的具体实现,如下所示:
public class Animal implements Comparable {
    ...
    @Override
    public int compareTo(Object o) {
        Animal another = (Animal)o;
        return this.getName().compareTo(another.toString());
    }
}
// 调用
Collections.sort(animalList);
这种方式在 JDK7 版本及以前使用的比较多。
Collections.sort(List<T> list, Comparator<? super T> c) 方法在集合类  Collections<T>  中,还有一个 sort(List<T> list) 的重载方法  sort(List list, Comparator<? super T> c)。
使用该方法,Animal 类就无需再实现 Comparable<T> 接口,在 JDK7 版本及以前,使用匿名内部类来调用此方法即可。
相比第一种方式,结构上轻便了很多,代码实现如下:
Collections.sort(animalList, new Comparator<Animal>() {
    @Override
    public int compare(Animal o1, Animal o2) {
        return o1.getName().compareTo(o2.getName());
    }
});
和第二种类似,只不过随着 JDK8 版本中 Lambda 表达式的出现,可替换以往的匿名内部类,代码实现上做到更简洁:
// Lambda 表达式的实现
Collections.sort(animalList, (a, b) -> a.getName().compareTo(b.getName()));
在第一种方式中,Animal 类还要实现 Comparable<T> 接口,然后做  compare()  抽象方法的具体实现。
整个实现上是过于笨重的,太形式化。
有了方法引用,就可以大大减轻这种不必要的形式化。因为 Animal 类中已经有了类似的比较方法,即静态方法  compareByName()。
直接用这个方法代替 compare() 方法不就行啦,如下:
Collections.sort(animalList, Animal::compareByName);
是不是很简单!没有接口实现,也没有匿名内部类,以一种优雅的方式达到了相同的目的,这也是方法引用的魅力之处。
我个人理解,方法引用的出现,就是为了去优化冗余且过于形式化的代码,直接用短平快的方式解决。
List 接口的 sort() 默认方法除了 Collections 集合类,List 接口中,也提供了列表的排序方法。
// 匿名内部类实现
animalList.sort(new Comparator<Animal>() {
    @Override
    public int compare(Animal o1, Animal o2) {
        return o1.getName().compareTo(o2.getName());
    }
});
// Lambda 表达式实现
animalList.sort((a, b) -> a.getName().compareTo(b.getName()));
// 静态方法引用的实现
animalList.sort(Animal::compareByName);
Stream() 流排序Stream() 流是 JDK8 中新引入的功能,排序代码如下:
// 方式 1:Lambda 表达式实现
animalList = animalList
    .stream()
    .sorted((a, b) -> a.getName().compareTo(b.getName()))
    .collect(Collectors.toList());
// 方式 2:静态方法引用
animalList = animalList
    .stream()
    .sorted(Animal::compareByName)
    .collect(Collectors.toList());
在前一章节的第五种方式中,我们可以替换为类特定对象的引用。
语法:类对象::普通方法名。
在上面的 Animal 类中,有一个普通方法:
public Integer compareByWeight(Animal one, Animal another) {
    return one.getWeight() - another.getWeight();
}
compareByWeight() 就是一个普通的实例方法,但它的定义依然与 Comparable 接口的 compare()  抽象方法定义是一致的。所以也可以使用在方法引用中。
怎么使用呢?方式如下:
Animal dog = new Animal("dog", 40);
animalList.sort(dog::compareByWeight);
类特定对象的引用、静态方法引用,两者在使用上没有区别,都达到一样的目的,只是方式不同,一个是类 + 静态方法名,一个是类对象 + 普通方法名。
语法:类名::普通方法名。
从语法上看,与前面 2.3.2 小节的静态方法引用类似,都是类名 + 方法名的方式,只不过一个是普通方法,一个是静态方法,但这是不是意味着两者在含义上也是类似的呢?
答案是否定的。
对于 2.3.2 章节的静态方法引用,以及 2.3.3 章节的类特定对象的引用,它们的重点都是在引出方法,只不过引出的方式不同。
public class Animal {
    private String name;
    private Integer weight;
    public Animal(String name, Integer weight) {
        this.name = name;
        this.weight = weight;
    }
    ...
    public static Integer compareByName(Animal one, Animal another) {
        return one.getName().compareTo(another.getName());
    }
    public Integer compareByWeight(Animal one, Animal another) {
        return one.getWeight() - another.getWeight();
    }
}
// 静态方法引用
animalList.sort(Animal::compareByName);
// 类的特定对象的引用
Animal dog = new Animal("dog", 40);
animalList.sort(dog::compareByWeight);
就像上面的代码中,“类的特定对象的引用”示例中,换个 Animal 对象,依然能达到同样的效果:
Animal cat = new Animal("cat", 15);
animalList.sort(cat::compareByWeight);
好了,现在回到本小节的主题:类的任意对象的引用。
我们可以怎么用呢?
在继续讲之前,我们先回头再观察下前面面代码中的 compareByWeight(xx, xxx) 方法。有没有发现它的两个参数有点儿冗余?另外,如果是两个参数,这个方法放在任何一个类中都可以使用,完全可以把它抽到一个工具类中使用,没必要放在这个类中。如果要放在该类中,可以换一种方式,传递一个参数即可:
public Integer compareByWeight(Animal another) {
    return this.getWeight() - another.getWeight();
}
调用代码如下:
animalList.sort(Animal::compareByWeight);
这里很多人都会疑惑,方法引用的前提,不都是入参个数都要一样吗?但 compareByWeight(Animal another) 方法只有一个参数,而 sort()  方法的形参  Comparator<T> 对应的抽象方法  compare(T o1, T o2) 是两个参数:
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}
@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}
这就是“类的任意对象的引用”这种类型的特殊之处。
方法引用会默认将第一个入参作为当前类的一个调用对象,其余参数继续作为方法的入参。
在本例中,compare(T o1, T o2) 方法是需要接入两个 Animal 对象的,但第一个对象 o1 可以作为当前 Animal 类的一个对象,剩下的 o2 继续作为引用方法  compareByWeight()  的参数,即:
o1.compareByWeight(o2)
这也是为何称为“类的任意对象的引用”。
为加深理解,我们再举一个例子。
前面的 Animal 类中,有一个 sleep()  普通方法和 bodyCheck(xx)  静态方法:
public class Animal {
    private String name;
    ...
    public void sleep() {
        System.out.println(this.getName() + " sleep.");
    }
    public static void bodyCheck(Animal animal) {
        System.out.println("body check " + animal.getName());
    }
}
Animal::sleep  构成了“类的任意对象的引用”,Animal::bodyCheck  构成了“静态方法引用”,它们都可以用在如下表达式中:
animalList.forEach(Animal::sleep);
animalList.forEach(Animal::bodyCheck);
sleep() 方法虽然没有入参,但依然可以用在 forEach() 方法中,因为  Consumer<T>  接口的 accept(T t) 抽象方法有一个入参,而该入参就可以作为 Animal 类的一个对象,来调用 sleep() 方法。
如上所述,方法引用有多种类型,在实际使用过程中,可灵活运用。
说到底,跟 Lambda 表达式一样,它还是一种语法糖,为我们的开发工作提效。为达到同样的目标,相比传统实现方式,这种语法糖减轻了代码量,使用更轻便,不再拘泥于特定场景下囿于面向对象语言规则而产生的笨重表达,是对它们的一种轻量级替代。
你,现在掌握了吗?