Java 8 stream max() function argument type Comparator vs Comparable

前端 未结 4 1836
陌清茗
陌清茗 2021-01-01 16:41

I wrote some simple code like below. This class works fine without any errors.

public class Test {
    public static void main(String[] args) {
        List&         


        
相关标签:
4条回答
  • 2021-01-01 16:54

    Integer implements Comparable by overriding compareTo.

    That overriden compareTo, however, can be used in a way that satisfies and implements the Comparator interface.

    In its usage here

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

    it's translated to something like

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

    A method reference (or lambda expression) must satisfy the signature of the corresponding functional interface's single abstract method and, in this case (Comparator), compareTo does.


    The idea is that max expects a Comparator and its compare method expects two Integer objects. Integer::compareTo can satisfy those expectations because it also expects two Integer objects. The first is its receiver (the instance on which the method is to be called) and the second is the argument. With the new Java 8 syntax, the compiler translates one style to the other.

    (compareTo also returns an int as required by Comparator#compare.)

    0 讨论(0)
  • 2021-01-01 17:02
    int value = intList.stream().max(Integer::compareTo).get();
    

    The above snippet of code is logically equivalent to the following:

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

    Which is also logically equivalent to the following:

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

    Comparator is a functional interface and can be used as a lambda or method reference, which is why your code compiles and executes successfully.

    I recommend reading Oracle's tutorial on Method References (they use an example where two objects are compared) as well as the Java Language Specification on §15.13. Method Reference Expressions to understand why this works.

    0 讨论(0)
  • 2021-01-01 17:02

    First trick: all instance methods actually take 1 additional implicit argument, the one you refer to as this in method body. E.g.:

    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)
    

    Second trick: Java compiler can "transform" the signature of a method reference to some functional interface, if the number and types of arguments (including this) satisfy:

    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
    

    As you can see class Integer implements none of Comparator, BiFunction or a random MyInterface, but that doesn't stop you from casting the Integer::compareTo method reference as those interfaces.

    0 讨论(0)
  • 2021-01-01 17:09

    I can relate to your confusion.

    We've got a Comparator's method which declares two parameters

    int compare(T o1, T o2);
    

    and we've got an Integer's method which takes one parameter

    int compareTo(Integer anotherInteger)
    

    How on earth does Integer::compareTo get resolved to a Comparator instance?

    When a method reference points to an instance method, the parser can look for methods with arity n-1 (n is the expected number of parameters).

    Here's an excerpt from the JLS on how applicable methods are identified. I will drop the first part about parsing the expression preceding the :: token.

    Second, given a targeted function type with n parameters, a set of potentially applicable methods is identified:

    If the method reference expression has the form ReferenceType :: [TypeArguments] Identifier, then the potentially applicable methods are:

    • the member methods of the type to search that would be potentially applicable (§15.12.2.1) for a method invocation which names Identifier, has arity n, has type arguments TypeArguments, and appears in the same class as the method reference expression; plus

    • the member methods of the type to search that would be potentially applicable for a method invocation which names Identifier, has arity n-1, has type arguments TypeArguments, and appears in the same class as the method reference expression.

    Two different arities, n and n-1, are considered, to account for the possibility that this form refers to either a static method or an instance method.

    ...

    A method reference expression of the form ReferenceType :: [TypeArguments] Identifier can be interpreted in different ways. If Identifier refers to an instance method, then the implicit lambda expression has an extra parameter compared to if Identifier refers to a static method.

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

    If we were to write an implicit lambda expression from that method reference, the first (implicit) parameter would be an instance to call the method on, the second (explicit) parameter would be an argument to pass in the method.

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

    Note that a method reference differs from a lambda expression, even though the former can be easily transformed into the latter. A lambda expression needs to be desugared into a new method, while a method reference usually requires only loading a corresponding constant method handle.

    Integer::compareTo implements Comparable interface - not Comparator.

    Integer::compareTo as an expression doesn't implement any interface. However, it can refer to/represent different functional types, one of which is Comparator<Integer>.

    Comparator<Integer> a = Integer::compareTo;
    BiFunction<Integer, Integer, Integer> b = Integer::compareTo;
    ToIntBiFunction<Integer, Integer> c = Integer::compareTo;
    
    0 讨论(0)
提交回复
热议问题