简体   繁体   中英

Type Inference in Method Reference

I recently put my hands on Java 8 and tried using Method References. I was trying different kinds of Method references and got stuck in the type "Reference to an Instance Method of an Arbitrary Object of a Particular Type".

String[] arr = {"First", "Second", "Third", "Fourth"};

Arrays.sort(arr, String::compareToIgnoreCase);

This works perfectly well. But when I try to refer a method of a user defined class through its type :

Demo2[] arr = {a, b};

Arrays.sort(arr, Demo2::compare);

This displays compile-time error as "Non-static method cannot be referenced from a static context".

Here's the Demo2 class :

public class Demo2 implements Comparator<Demo2> {
    Integer i;

    Demo2(Integer i1){
        i = i1;
    }

    public Integer getI() {
        return i;
    }

    @Override
    public int compare(Demo2 o1, Demo2 o2) {
        return o1.getI().compareTo(o2.getI());
    }
}

As greg-449 pointed to you, your code has a bug.

By making a method reference like YourObjet::yourMethod you make a static reference to the method of the instance. So the method will be called for each object and thus the signature needs to be different than the earlier

A code that will compile will be of the following form :

Demo2[] demos = {a, b};
Arrays.sort(demos, Demo2::compareTo);

public class Demo2 {
    Integer i;

    Demo2(Integer i1){
        i = i1;
    }

    public Integer getI() {
        return i;
    }

    public int compareTo(Demo2 other) {
        return this.getI().compareTo(other.getI());
    }
}

But as RealSkeptic pointed out, this is not the correct way to implement and objet comparison. You should give the Arrays.sort method a comparator instead :

 Arrays.sort(demos, (obj1, obj2) -> obj1.getI().compareTo(obj2.getI()));

The Comparator interface required for Arrays.sort(T[],Comparator<T>) has a method that accepts two object references of the same type T , and returns an integer.

There is a bit of "magic" in method references. What Java does is wrap the method in such a way that it will fit the interface requirement.

The interface, of course, doesn't require a static method. But the wrapping can create a method that calls a static method, as in the Tutorial 's first example:

public static int compareByAge(Person a, Person b) {
    return a.birthday.compareTo(b.birthday);
}

It wraps it in such a way that you get something similar to

new Comparator<Person>() {
    @Override
    public int compare(Person a, Person b) {
        return Person.compareByAge(a,b);
    }
}

Which satisfies the interface.

But in the example in the section "Reference to an Instance Method of an Arbitrary Object of a Particular Type", it wraps it differently. It needs a method that receives two strings, but it has a method that only receives one. This is how String::compareToIgnoreCase is defined:

public int compareToIgnoreCase(String str)

But in this case, it's an instance method. What Java does now is, because this method belongs to an object of type String , and accepts an object of type String it is easy to build a "wrap" around it that makes it into a method that accepts two objects, much like the lambda expression:

(String a, String b) -> {
    return a.compareToIgnoreCase( b );
}

Or, if we imagine a formal wrapping as a Comparator :

new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.compareToIgnoreCase(b);
    }
}

So, the fact that it's an instance method that belongs to type T , accepts type T and returns int allows Java to wrap it appropriately so it fits the Comparator interface.

But int compare(Demo2 o1, Demo2 o2) doesn't fit that pattern. It accepts two parameters. If a method accepts two parameters, it must be a static method to fit the wrapping rules - there is no way to pass the "this" object into the Comparator interface. So it tries to wrap it as a static method, and fails, as it is not a static method.

Bottom line: you got the error, because for this particular type of method reference, you need an instance method with only one parameter of the same type as the class.

As @Holger mentions in a comment, if you have a new class you are building, you shouldn't put a comparison method in it specifically for this sort of sorting task. If the class has a natural ordering, make it Comparable and use Arrays.sort(Object[]) . If it doesn't, and you need to sort it sometimes based on any of its attributes, use a lambda expression or Comparator.comparing(Demo2::getI) which makes better use of an existing getter for the specific purpose of comparison.

As a convention, Comparator<T> is implemented with a lambda expression and it looks very odd to implement it in a class.

    Demo2[] array = new Demo2[2];
    array[0] = new Demo2(12);
    array[1] = new Demo2(32);

    Comparator<Demo2> demo2Comparator = (e1,e2)->e1.getI().compareTo(e2.getI());
    Arrays.sort(array, demo2Comparator);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM