Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

getMethods() returns method I haven't defined when implementing a generic interface

An simple interface:

interface Foo {
    void myMethod(String arg);
}

class FooImpl implements Foo {
    void myMethod(String arg){}

    public static void main(String[] args) {
    Class cls = FooImpl.class;
    try {
        for (Method method : cls.getMethods()) {
        System.out.print(method.getName() + "\t");
        for(Class paramCls : method.getParameterTypes()){
            System.out.print(paramCls.getName() + ",");
        }
        System.out.println();
        }
    } catch (SecurityException e) {
        // TODO Auto-generated catch block
    }
    }
}

The output would be:

myMethod java.lang.String,
...//Other Method

Only one myMethod is printed.

But if I change the interface to a generic one:

interface Foo<T> {
    void myMethod(T arg);    
}   

class FooImpl implements Foo<String> {
    void myMethod(String arg){}
}

Then strangely the output will be:

myMethod java.lang.Object,
myMethod java.lang.String,
...//Other Method

Why after changing the interface to a generic one will lead to one more Method with a parameter type Object?

like image 397
StarPinkER Avatar asked Mar 07 '13 10:03

StarPinkER


2 Answers

The first method is a bridge method, created by the compiler. If you test your methods for 'isBridge()', you can filter out the 'wrong' methods (also filters out some strange results you can get with covariance returns).

Following code will not print the myMethod java.lang.Object:

import java.lang.reflect.Method;


public class FooImpl implements Foo<String> {
    public void myMethod(String arg) {
    }

    public static void main(String[] args) throws Exception {
        Class cls = FooImpl.class;
        for (Method method : cls.getMethods()) {
            if (!method.isBridge()) {
                System.out.print(method.getName() + "\t");

                for (Class paramCls : method.getParameterTypes()) {

                    System.out.print(paramCls.getName() + ",");

                }
            }
            System.out.println();
        }
    }
}

interface Foo<T> {
    public void myMethod(T arg);
}
like image 183
Fortega Avatar answered Oct 24 '22 20:10

Fortega


try {
        for (Method method : cls.getMethods()) {
    //these methods are called bridge methods       
            if(!method.isBridge()){
                System.out.print(method.getName() + "\t");
                for(Class paramCls : method.getParameterTypes()){
                    System.out.print(paramCls.getName() + ",");
                }
                System.out.println();
            }
        }
    } catch (SecurityException e) {
        // TODO Auto-generated catch block
    }

UPDATE: Bridge methods

Quoting the blog:

Bridge methods in Java are synthetic methods, which are necessary to implement some of Java language features. The best known samples are covariant return type and a case in generics when erasure of base method's arguments differs from the actual method being invoked.

UPDATE CODE FROM above blog:

public class SampleTwo {
    public static class A<T> {
        public T getT(T args) {
            return args;
        }
    }

    public static class B extends A<String> {
        public String getT(String args) {
            return args;
        }
    }
}

On compiling the code will look like:

public SampleThree$B();
...
public java.lang.String getT(java.lang.String);
Code:
0:   aload_1
1:   areturn

public java.lang.Object getT(java.lang.Object);
Code:
0:   aload_0
1:   aload_1
2:   checkcast       #2; //class java/lang/String
5:   invokevirtual   #3; //Method getT:(Ljava/lang/String;)Ljava/lang/String;
8:   areturn
}

the bridge method, which overrides method from base class "A", not just calling one with string argument (#3), but also performs type cast to "java.lang.String" (#2). It means, that if you will execute following code, ignoring compiler's "unchecked" warning, the result will be ClassCastException thrown from the bridge method:

A a = new B();
a.getT(new Object()));
like image 41
Narendra Pathai Avatar answered Oct 24 '22 20:10

Narendra Pathai