Java 8 流 max() 函数参数类型 比较器 vs 可比较

2022-09-02 00:28:29

我写了一些简单的代码,如下所示。此类工作正常,没有任何错误。

public class Test {
    public static void main(String[] args) {
        List<Integer> intList = IntStream.of(1,2,3,4,5,6,7,8,9,10).boxed().collect(Collectors.toList());
        int value = intList.stream().max(Integer::compareTo).get();

        //int value = intList.stream().max(<Comparator<? super T> comparator type should pass here>).get();

        System.out.println("value :"+value);
    }
}

如代码注释所示,该方法应传递类型为 的参数。max()Comparator<? super Integer>

但实现接口 - 不是Integer::compareToComparableComparator

public final class Integer extends Number implements Comparable<Integer> {
    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }
}

这怎么能行呢?该方法说它需要一个参数,但它与参数一起工作。max()ComparatorComparable

我知道我误解了什么,但我现在知道了什么。有人可以解释一下吗?


答案 1
int value = intList.stream().max(Integer::compareTo).get();

上面的代码片段在逻辑上等效于以下内容:

int value = intList.stream().max((a, b) -> a.compareTo(b)).get();

这在逻辑上也等效于以下内容:

int value = intList.stream().max(new Comparator<Integer>() {
    @Override
    public int compare(Integer a, Integer b) {
        return a.compareTo(b);
    }
}).get();

Comparator是一个功能接口,可以用作 lambda 或方法引用,这就是您的代码成功编译和执行的原因。

我建议阅读Oracle关于方法参考的教程(他们使用一个比较两个对象的示例)以及§15.13上的Java语言规范。方法引用表达式,以了解其工作原理。


答案 2

我可以理解你的困惑。

我们有一个声明两个参数的 's 方法Comparator

int compare(T o1, T o2);

我们有一个 's 方法,它接受一个参数Integer

int compareTo(Integer anotherInteger)

到底如何解析为实例?Integer::compareToComparator

当方法引用指向实例方法时,解析器可以查找具有 arity 的方法(即预期的参数数)。n-1n

以下是JLS关于如何识别适用方法的摘录。我将删除有关解析 :: 标记前面的表达式的第一部分。

其次,给定一个带有参数的目标函数类型,可以识别一组可能适用的方法:n

如果方法引用表达式具有以下形式,则可能适用的方法为:ReferenceType :: [TypeArguments] Identifier

  • 要搜索的类型的成员方法,该方法可能适用于方法调用(§15.12.2.1),该方法调用命名标识符,具有arity n,具有类型参数TypeArguments,并且出现在与方法引用表达式相同的类中;加

  • 要搜索的类型的成员方法,该方法可能适用于方法调用,该方法调用命名标识符,具有 arity n-1,具有类型参数 TypeArguments,并且出现在与方法引用表达式相同的类中

考虑了两种不同的 arities,nn-1,以解释此形式引用静态方法或实例方法的可能性。

...

形式的方法引用表达式可以用不同的方式解释。如果引用实例方法,则隐式 lambda 表达式与 if 引用静态方法相比具有额外的参数。ReferenceType :: [TypeArguments] IdentifierIdentifierIdentifier

https://docs.oracle.com/javase/specs/jls/se12/html/jls-15.html#jls-15.13.1

如果我们要从该方法引用编写隐式 lambda 表达式,则第一个(隐式)参数将是调用该方法的实例,第二个(显式)参数将是要在方法中传递的参数。

(implicitParam, anotherInteger) -> implicitParam.compareTo(anotherInteger)

请注意,方法引用与 lambda 表达式不同,即使前者可以很容易地转换为后者。lambda 表达式需要解压到新方法中,而方法引用通常只需要加载相应的常量方法句柄。

Integer::compareTo实现接口 - 不是 。ComparableComparator

Integer::compareTo因为表达式不实现任何接口。但是,它可以引用/表示不同的功能类型,其中之一是 。Comparator<Integer>

Comparator<Integer> a = Integer::compareTo;
BiFunction<Integer, Integer, Integer> b = Integer::compareTo;
ToIntBiFunction<Integer, Integer> c = Integer::compareTo;

推荐