2

最近,我在维护的一个应用程序中遇到了一种奇怪的行为。源代码使用版本 1.6_33 编译,但在 1.7u21 下运行。客户决定在没有咨询的情况下更改版本,我对他们的选择没有影响。

在架构中,一些接口在一定条件下被参数化以限制它们的使用。参数化的具体类必须使用定义的具体参数之一。为方便起见,PropertyDescritor 由负责实例化 ConcreteParameter(用于附加进程)的机制使用。此行为适用于版本 6,但不适用于版本 7。

在版本 7 中,当尝试获取参数的类时,返回类型始终是Parameter类型,而在版本 6 中,类型是ConcreteParameter。一个异常发生在 java 中,如下例所示。但是为什么在这种情况下会起作用?!

我浏览了JLS 7以及java 7 兼容性,但没有找到任何关于这种行为的解释。对我来说,没有具体类型是不合逻辑的。有人可以解释一下为什么会这样吗?这不是一个错误,反射适用于 getDeclaredMethod(...) 但不适用于 getDeclaredMethods() 吗?

提前致谢。

以下示例说明了过去的工作方式:

package fr.free.naoj;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

import org.junit.Test;

public class PropertyDescriptorAndReflectionTest {

    @Test public void testPropertyDescriptorInnerWithJava7() {
        PropertyDescriptor pd;
        try {
            // Fails
            pd = new PropertyDescriptor("parameter", ConcreteClass.class);
            assertEquals(ConcreteParameter.class, pd.getPropertyType());
        } catch (Exception e) {
            e.printStackTrace();
            fail();
        }
    }

    @Test public void testReflectionOnGenericClassWithJava7() {
        try {
            // Works
            Method m = ConcreteClass.class.getDeclaredMethod("getParameter", new Class<?>[]{});
            m.setAccessible(true);
            assertEquals(ConcreteParameter.class, m.getReturnType());

            // Fails
            for (Method me : ConcreteClass.class.getDeclaredMethods()) {
                me.setAccessible(true);
                if (me.getName().equals("getParameter")) {
                    assertEquals(ConcreteParameter.class, me.getReturnType());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            fail();
        }
    }

    private class ConcreteClass extends AbstractClass<ConcreteParameter> {
        @Override public ConcreteParameter getParameter() {
            this.parameter = new ConcreteParameter();
            return this.parameter;
        }

        @Override public void setParameter(ConcreteParameter parameter) {
            this.parameter = parameter;
        }
    }

    private abstract class AbstractClass<P extends Parameter> implements Super<P> {
        protected P parameter; 
    }

    private interface Super<P extends Parameter> {
        P getParameter();

        void setParameter(P parameter);
    }

    private class ConcreteParameter implements Parameter {
        @Override public String sayHello() {
            return "hello";
        }
    }

    private interface Parameter {
        String sayHello();
    }
}
4

1 回答 1

2

让我们从头开始。

ConcreteClass.getDeclaredMethods()在 Java 6 和 Java 7 下给出 4 个结果,而不仅仅是 2 个:getParameter()returningConcreteParametersetParameter(ConcreteParameter); 还要getParameter()回归Parameter,和setParameter(Parameter)。Java 6 和 Java 7 之间唯一不同的是方法从getDeclaredMethods. 我认为您引用的 Java 7 兼容性文档中提到了这一点。

额外的方法对应于编译器为使泛型与擦除一起工作而添加的桥接方法,即编译器将在类中生成两个隐藏方法,如下所示ConcreteClass

public void setParameter(Parameter p) {
    setParameter((ConcreteParameter) p);
}

public Parameter getParameter() {
    return getParameter(); // calls the 'real' one!
}

(这不是合法的 Java 代码,因为您不允许有两个仅在返回类型上有所不同的方法,但在字节码中这很好,因为返回类型是 JVM 方法签名的一部分。)

如果您阅读 JavaDoc,Class#getDeclaredMethod我们会看到:

如果在一个类中声明了多个具有相同参数类型的方法,并且其中一个方法的返回类型比其他任何方法都更具体,则返回该方法;否则任意选择其中一种方法。

所以你看到的行为getDeclaredMethod似乎getDeclaredMethods在规范中,最具体的方法被返回getDeclaredMethod,多个方法被返回getDeclaredMethods

但是,您看到的行为似乎是错误 6788525PropertyDescriptor的复发。可能它只是采用它找到的第一个方法,并使用它想要的名称,这意味着它有时会出错。我建议您向 Oracle 提交错误报告。

于 2013-06-05T14:32:25.463 回答