深度解读Java8-lambda表达式之方法引用

时间:2021-01-23 19:11:00

先看个例子

import java.util.ArrayList;
import java.util.Arrays;
import static java.util.Comparator.comparing;

import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * Created by kewangk on 2018/1/29.
 */
public class TestJava8 {
    public static void main(String[] args) {
        List<String> strings=Arrays.asList("hehe","","wonking","memeda");
        List<Integer> lengths=map(strings, (String s)->s.length());
        System.out.println(lengths);
        lengths.sort(comparing(Integer::intValue));
        lengths.sort((i1,i2)-> i1.compareTo(i2));
        System.out.println(lengths);
    }

    public static <T,R> List<R> map(List<T> list, Function<T,R> f){
        List<R> result=new ArrayList<R>(list.size());
        for(T t:list){
            result.add(f.apply(t));
        }
        return result;
    }

    public static List<Integer> filterOdd(List<Integer> list, Predicate<Integer> p){
        List<Integer> result=new ArrayList<>();
        for(Integer i: list){
            if(p.test(i)){
                result.add(i);
            }
        }
        return result;
    }
}

lengths.sort(comparing(Integer::intValue));

这行就用到了方法引用,初看有些晦涩。没关系,我们来从外到内层层剥开它的外衣。

首先看List.sort(Comparator<? super E> c)这个方法

sort方法需要传进一个Comparator,这是一个函数式接口,所以我们首先想到能用lambda表达式来传参

lengths.sort(( i1, i2) -> i1.compareTo(i2));

所以你像上面这样写没错,但编译器会抱怨一句

Inspection looks for Comparators defined as lambda expressions which could be expressed using methods like Comparator.comparing()

Can be replaced with Comparator.naturalOrder

这句话的意思是说,语法检查器查找定义为lambda表达式的Comparator,可以使用Comparator.comparing()方法来代替,并且还心细到给你找了一个现成的自然顺序比较器给你开箱即用(我们先忽略这个)。

不明白这句话的意思?这其实就是在鼓励你是用lambda表达式。

你就奇怪了,我不是已经用了lambda表达式嘛?干嘛还非要我换别的用法?

我们知道,lambda表达式其实就是提供了某个函数式接口的一个实现,只不过它本身不携带自己实现的哪个接口的信息,而是由编译器进行类型推断。

既然lambda表达式本身不携带实现接口的信息,那么我们需要知道(一眼就看出来的那种,弯都不带拐的)某个方法到底需要哪种类型的lambda表达式怎么办呢?

所以Java API的设计师们帮我们设计出这样一个静态方法

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

从这个方法的实现来看,我们可以看出设计师们设计此方法的更多用意:

 为了兼容老接口Comparable,去看看API可以知道,Comparable接口1.2版本开始出现

 为了实现方法引用,那么方法引用又是为了实现什么?现在你还不能理解,接着看下去

 为了更优雅的代码

 为了爱

 为了和平

 个中奥妙,真无穷无尽也

再来看看这个comparing方法做了什么

返回一个Comparator(正是sort方法需要的),传入一个Function,另一个函数式接口,来看看Function的函数描述符(再次感受一下lambda表达式的简洁优美)

Function<T,R>  T->R

将T变成R,OOP式的表达就是,将一种对象转化成另一种对象,这就是Function的全部

具体到这个例子中来,看看Function实现了从什么到什么的转化

Function<? super T, ? extends U>,这个泛型限定的是,源对象是T或T的一个父类,目标对象是U或U的一个子类。T没有更具体的限制,那么来看U,U是实现了Comparable接口的一个对象

现在清楚了,Function要做的事情就是,将T转化成一个Comparable对象,T不管它是什么类型,只要它实现了Comparable接口,这个转换就会成功

所以这个Function的作用用一句话总结来说就是,将实现了Comparable接口的两个对象转化成Comparable对象进行比较,整个比较逻辑,又是作为一个lambda表达式形式的Compatator接口进行返回

至此,最开始的那行代码已经被我们剥得只剩裤衩了。感觉是不是很美妙~~

再来看看Integer::intValue

可以肯定,这是一个lambda表达式形式的Function,那么它代表前面说的函数描述符中T->R的哪一个呢?
很显然,它代表T,之前我们分析Function的时候已经确定,R就是Comparable,但是一直没说T从哪来。

专业来讲,它是一个方法引用,即引用Integer中的intValue方法。

与这个引用等价的lambda表达式是:(Integer i)->i.intValue()

这个lambda表达式的签名不正是与Function的apply()方法签名一致吗

如果我们把这个lambda对
return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
这段代码中的keyExtractor.apply(c1)进行替换,就变成了这样的代码:
return (Comparator<T> & Serializable)(c1, c2) -> c1.compareTo(c2);
是不是很眼熟?没错,兜兜转转转了一圈又特么回来了,其实中间实际还进行了基本类型装箱拆箱的动作。

准确来说,转换过程是这样滴:
Integer->int->Integer->Comparable

总结下来:这个comparing帮我们做了下面几件事情:
将重复的方法调用进行了折叠,避免重复调用
限制可以比较的类型,通过指定Function的转换目标为Comparable,限制源类型必须实现了Comparable接口才能比较(不过这个理由貌似很牵强)

所谓大道至简,它只是简约,而不简单。也不要惧怕这大道,其背后一定是由诸多简单的规则环环相扣而来,只要一层一层分析下去,真理自会展现。