Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparator two compare Methods with Reflection

I was trying to fix a Problem with an unknown Comparator (no Source access). So I've wrote some Reflection Code to see what Types that Comparator accepts.

Surprisingly, Reflection tells me, there are two compare-Methods, one with the real type and one with Object:

Comparator<Integer> comp = new Comparator<Integer>()
{

    @Override
    public int compare(Integer o1, Integer o2)
    {
        return 0;
    }
};

Method[] methods = comp.getClass().getMethods();
for (Method method : methods)
{
    if(method.getName().equals("compare")){
        System.out.println(method);
    }
}

Output:

public int de.hinneLinks.stackoverflow.MyClass$1.compare(java.lang.Integer,java.lang.Integer)
public int de.hinneLinks.stackoverflow.MyClass$1.compare(java.lang.Object,java.lang.Object)

Where is that second compare Method coming from?

But its not usable, why?:

comp.compare(1, 2); //Compiles
comp.compare((Object)1,(Object)2); //Does not Compile

I can however call those Methods with Reflection, if i call both Methods with new Object(), i'll get two different Exceptions:

compare(java.lang.Object,java.lang.Object)
java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.Integer

compare(java.lang.Integer,java.lang.Integer)
java.lang.IllegalArgumentException: argument type mismatch

If define my Comparator with Object, then there is only one Method.

like image 950
hinneLinks Avatar asked Jan 07 '23 05:01

hinneLinks


2 Answers

The other method (compare(Object obj1, Object2) is a bridge method generated by the compiler to preserve binary compatibility after type erasure:

When compiling a class or interface that extends a parameterized class or implements a parameterized interface, the compiler may need to create a synthetic method, called a bridge method, as part of the type erasure process. You normally don't need to worry about bridge methods, but you might be puzzled if one appears in a stack trace.

You can add a check on the method to see if it's a bridge method:

for (Method method : methods) {
    if (method.getName().equals("compare") && !method.isBridge()) {
        System.out.println(method);
    }
}
like image 183
M A Avatar answered Jan 11 '23 22:01

M A


This is due to Type Erasure, which was a design decision (preserving backwards-compatibility) in the implementation of Generics in Java.

During the type erasure process, the Java compiler erases all type parameters and replaces each with its first bound if the type parameter is bounded, or Object if the type parameter is unbounded.

This allows your class to be accessed from pre-Java-5 JVMs, where Comparator<Integer> isn't visible but Comparator is (where it supplies compare(Object, Object)). The implementation of compare(Object, Object) simply casts each argument to an Integer and calls compare(Integer, Integer), which is why you get your exceptions.

like image 37
Jeff Bowman Avatar answered Jan 12 '23 00:01

Jeff Bowman