Type Inference in Method Reference

我的未来我决定 提交于 2019-12-10 10:38:29

问题


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());
    }
}

回答1:


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()));



回答2:


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.




回答3:


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);


来源:https://stackoverflow.com/questions/39073799/type-inference-in-method-reference

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!