JAVA反射机制入门(二)--getMethods()系列方法特例详解

昨天在研究getDeclaredMethod(String name,Class<?>... parameterTypes)API时,发现这么一段话:

The name parameter is a String that specifies the simple name of the desired method, and the parameterTypes parameter is an array of Class objects that identify the method's formal parameter types, in declared order. If more than one method with the same parameter types is declared in a class, and one of these methods has a return type that is [color=blue]more specific than any of the others, that method is returned; otherwise one of the methods is chosen arbitrarily[/color]. If the name is "<init>"or "<clinit>" a NoSuchMethodException is raised.

就此产生了疑问,如果指定了方法的名字和参数的类型,为什么还可能会得到多个方法,java中不是不允许同名同参的overloaded吗?接着在getMethod()的API中找到了答案:

Note that there may be more than one matching method in a class because while the Java language forbids a class to declare multiple methods with the same signature but different return types, the Java virtual machine does not. This increased flexibility in the virtual machine can be used to implement various language features. For example, covariant returns can be implemented with bridge methods ; the bridge method and the method being overridden would have the same signature but different return types.

java语言本身规定同名同参即使返回值不同,不能构成重载,但是JVM不这么认为,它认为返回值的不同可以构成重载。

这里介绍两个概念:

一.convariant return type:

当导出类中的'overriding'方法的返回类型是基类的'overrided'方法的返回类型的导出类,即如下关系:

package classTest;




class A {
    A(){System.out.println("A");}
}

class B extends A {
    B(){System.out.println("B");}
}

// Classes demonstrating method overriding:

class C<T extends A> {
    T a;
    public void setFoo(T t){
        this.a = t;
    }
    public T getFoo() {
        return a;
    }
}

class D extends C<B> {
    public void setFoo(B b){
        super.a = b;
    }

    public B getFoo() {
        return super.getFoo();
    }
}

你可能会认为D中的getFoo()会overrideC中的getFoo(),但是在正常情况下,JAVA语言认为这个构成了override,但是JVM会认为这个仅构成了overload。也就是说对于JVM这个相当于在D中包含了两个getFoo(),仅靠返回类型来区分。因此当执行下面代码时:

        D dd = new D();
        Class d = dd.getClass();
        Class<?>[] p = null;
        Method m = d.getMethod("getFoo");
        System.out.println(m.getReturnType().getName());

getMehod(“getFoo”)由于方法名和参数完全相同会得到两个getFoo,因此只能根据更精确的返回类型(上面提示的more specific)来找到那个方法。

这里需要说明一下对于泛型只是针对编译器的,JVM是不支持泛型的。JVM中的类都是raw type的,即没有类型参数的。编译器会将所有的类型进行擦除,擦除规则:如果仅仅是<?>,将所有的<?>对应的类型替换为java.lang.Object;如果有<T extends SuperClass>,则将所有的<T>替换为SuperClass;对于方法泛型方法调用处,会将Object换成具体对应的类。如上面的类C,经过编译器擦除后为:

class classTest.C extends java.lang.Object{
    //域
     classTest.A a;
    //构造器
     C( );
    //方法
    public void setFoo(classTest.A);
    public classTest.A getFoo( );
}

可以看到所有的T被classTest.A替换


二.bridge methods

对D做进一步分析,得到编译对D的翻译结果如下:

class classTest.D extends classTest.C{
    //域
    //构造器
     D( );
    //方法
    public void setFoo(classTest.B);
    public volatile void setFoo(classTest.A);
    public classTest.B getFoo( );
    public volatile classTest.A getFoo( );

可以看到增加了一个public volatile的方法。这个方法是我们没有定义的,是由编译器增加的,这个就是bridge method。当你调用dd.getFoo()时,实际调用的是这个增加的方法,而在这个方法中调用D中的getFoo方法:

public volatile classTest.A getFoo( ){

      return this.getFoo();

}

因此,从外部看来仿佛就是D中的getFoo()重写了C中的getFoo()。

如果不信,可以在D中增加一个方法如下:

public void setFoo(A a){
        this.setFoo((B)a);
    }

编译器会提示有冲突。

Name clash: The method setFoo(A) of type D has the same erasure as setFoo(T) of type C<T> but does not override it

从JDK1.5开始,在一个方法覆盖另一个方法时可以指定一个更严格的返回类型,它的机制也是同样使用的桥方法。子类在覆盖父类的方法可以指定更为严格的返回类型,如例中C中返回的是A,D中返回的是B,编译器增加桥方法来间接的完成override。




还是重申一点对于D中和C的重名方法事实上在JVM中是overload的方法,是依靠返回值区分的。编译器动态增加了一个‘桥方法’对C中的getFoo()进行override,而在该函数内部间接调用了D的getFoo()方法。而用户实际调用的是这个‘桥方法’,因此看起来D的getFoo()对C的getFoo()进行了override。

你可能感兴趣的:(java,jvm,C++,c,C#)