【问题标题】:Java 8 stream max() function argument type Comparator vs ComparableJava 8 流 max() 函数参数类型 Comparator vs Comparable
【发布时间】:2019-09-05 17:18:04
【问题描述】:

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

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&lt;? super Integer&gt; 类型的参数。

但是 Integer::compareTo 实现了 Comparable 接口 - 不是 Comparator

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

这如何工作? max() 方法说它需要一个 Comparator 参数,但它适用于 Comparable 参数。

我知道我误解了一些东西,但我现在知道是什么了。谁能解释一下?

【问题讨论】:

  • Integer::compareTo 不返回 Comparable - 它是以下的简短定义:“请编译器为所需的类型生成匹配的实现(在本例中为 Comparator)并将参数映射到指定的功能。”在这种情况下,该函数需要两个“参数”(this 和一个参数compareTo),并且比较器提供两个参数 -> 有效。
  • ""请编译器,生成..." ...编译器总是对礼貌和礼貌做出最好的反应:-)

标签: java java-8 java-stream


【解决方案1】:
int value = intList.stream().max(Integer::compareTo).get();

上面的sn-p代码在逻辑上等价于:

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's tutorial on Method References(他们使用比较两个对象的示例)以及§15.13. Method Reference Expressions 上的 Java 语言规范,以了解其工作原理。

【讨论】:

  • 虽然它绝对正确,但它并没有回答“这如何工作?”
【解决方案2】:

我可以理解你的困惑。

我们有一个Comparator 的方法,它声明了两个参数

int compare(T o1, T o2);

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

int compareTo(Integer anotherInteger)

Integer::compareTo 到底是如何解析为 Comparator 实例的?

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

这是 JLS 中关于如何识别适用方法的摘录。我将放弃关于解析:: 标记之前的表达式的第一部分。

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

如果方法引用表达式的形式为ReferenceType :: [TypeArguments] Identifier,那么可能适用的方法是:

  • 要搜索的类型的成员方法可能适用于方法调用(第 15.12.2.1 节),该方法调用名称为 Identifier,具有 arity n,具有类型参数 TypeArguments,并且与方法引用表达式;加

  • 要搜索的类型的成员方法可能适用于名称为Identifier,具有arity n-1,具有类型参数TypeArguments,并出现在同一类中的方法调用方法引用表达式

考虑了两个不同的参数,nn-1,以说明此表单引用静态方法或实例方法的可能性。

...

ReferenceType :: [TypeArguments] Identifier 形式的方法引用表达式可以用不同的方式解释。如果Identifier 指的是一个实例方法,那么隐式lambda 表达式有一个额外的参数,而Identifier 指的是一个静态方法。

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

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

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

请注意,方法引用与 lambda 表达式不同,尽管前者可以很容易地转换为后者。一个lambda表达式需要desugared进入一个新的方法,而一个方法引用通常只需要加载一个对应的常量方法句柄。

Integer::compareTo 实现 Comparable 接口 - 不是 Comparator

Integer::compareTo 作为表达式没有实现任何接口。但是,它可以引用/表示不同的函数类型,其中之一是Comparator&lt;Integer&gt;

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

【讨论】:

    【解决方案3】:

    Integer 通过覆盖 compareTo 来实现 Comparable

    不过,覆盖的compareTo 可以以满足和实现Comparator 接口的方式使用。

    这里的用法

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

    它被翻译成类似的东西

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

    方法引用(或 lambda 表达式)必须满足相应功能接口的单个​​抽象方法的签名,在这种情况下 (Comparator),compareTo 可以。


    这个想法是max 需要一个Comparator,它的compare 方法需要两个Integer 对象。 Integer::compareTo 可以满足这些期望,因为它还需要两个 Integer 对象。第一个是它的接收者(调用方法的实例),第二个是参数。使用新的 Java 8 语法,编译器将一种样式转换为另一种样式。

    compareTo 还根据Comparator#compare 的要求返回int。)

    【讨论】:

    • 被低估了,但我认为这是最好的解释。
    【解决方案4】:

    第一个技巧:所有实例方法实际上都有一个额外的隐式参数,即您在方法体中称为this 的参数。例如:

    public final class Integer extends Number implements Comparable<Integer> {
        public int compareTo(/* Integer this, */ Integer anotherInteger) {
            return compare(this.value, anotherInteger.value);
        }
    }
    
    Integer a = 10, b = 100;
    int compareResult            = a.compareTo(b);
    // this actually 'compiles' to Integer#compareTo(this = a, anotherInteger = b)
    

    第二招:如果参数的数量和类型(包括this),Java 编译器可以将方法引用的签名“转换”为某些功能接口满足:

    interface MyInterface {
        int foo(Integer bar, Integer baz);
    }
    
    Integer a = 100, b = 1000;
    
    int result1 =                   ((Comparator<Integer>) Integer::compareTo).compare(a, b);
    int result2 = ((BiFunction<Integer, Integer, Integer>) Integer::compareTo).apply(a, b);
    int result3 =                           ((MyInterface) Integer::compareTo).foo(a, b);
    
    // result1 == result2 == result3
    

    如您所见,class Integer 没有实现任何ComparatorBiFunction 或随机的MyInterface,但这并不能阻止您将Integer::compareTo 方法引用转换为这些接口。

    【讨论】:

      猜你喜欢
      • 2018-08-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-22
      • 1970-01-01
      • 2014-08-17
      相关资源
      最近更新 更多