【问题标题】:Does the method parameters with different generics make the methods have different signature?具有不同泛型的方法参数是否使方法具有不同的签名?
【发布时间】:2011-02-25 22:35:47
【问题描述】:

我有以下工厂类。它有两个方法,它们接受 Class 实例并返回相应的对象。它们具有相同的方法名称,并且两个方法都以 Class 作为参数,但具有不同的泛型类,也返回不同的类型。编译器是否认为这两种方法是重复的?当我在 Eclipse 中打开 java 文件时,它会报告如下错误:

描述资源路径位置类型 方法lookupHome(Class) 与EJBHomeFactory EJBHomeFactory.java 类型中的另一个方法具有相同的擦除lookupHome(Class)

但是,javac 似乎不会报告任何错误。

import javax.ejb.EJBHome;
import javax.ejb.EJBLocalHome;

public class EJBHomeFactory {
    public <T extends EJBHome> T lookupHome(Class<T> homeClass) throws PayrollException {
        return lookupRemoteHome(homeClass);
    }

    public <T extends EJBLocalHome> T lookupHome(Class<T> homeClass) throws PayrollException {
        return lookupLocalHome(homeClass);
    }

    /* ... define other methods ... */
}

更新 1:这是使代码通过的 ant 脚本,我不确定它是如何工作的,但它不会引发任何错误。编译器好像是Eclipse JDT Compiler,我用普通的javac试过了,编译不出来。

<target name="compile" depends="init" description="Compile Java classes">
    <property name="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter"/>
    <mkdir dir="${build.classes.dir}"/>
    <javac destdir="${build.classes.dir}"
            srcdir="${build.src.dir};${devsrc.dir}"
            deprecation="${build.deprecation}"
            debug="${build.debug}"
            source="${build.source}"
            target="${build.target}"
            nowarn="${suppress.warning}"
            bootclasspath="${bootclasspath}" >
        <classpath>
            <path refid="build.class.path.id"/>
        </classpath> 
    </javac>
</target>

更新 2:我只是创建了另一个示例,有两个基类和两个子类,一个工厂类接受 Class 并根据参数的类型生成实例。该代码无法通过 javac 编译,并且在 Eclipse 中,IDE 抱怨同样的擦除问题。这是代码: 两个空基类:

public class BaseClassFirst {
}

public class BaseClassSecond {
}

两个子类:

public class SubClassFirst extends BaseClassFirst {
    private int someValue = 0;

    public SubClassFirst() {
        System.out.println(getClass().getName());
    }

    public SubClassFirst(int someValue) {
        this.someValue = someValue;
        System.out.println(getClass().getName() + ": " + this.someValue);
    }
}

public class SubClassSecond extends BaseClassSecond {
    private int someValue = 0;

    public SubClassSecond() {
        System.out.println(getClass().getName());
    }

    public SubClassSecond(int someValue) {
        this.someValue = someValue;
        System.out.println(getClass().getName() + ": " + this.someValue);
    }
}

因子类: 导入 java.lang.reflect.Method;

public class ClassFactory {

    private static ClassFactory instance = null;

    private ClassFactory() {
        System.out.println("Welcome to ClassFactory!");
    }

    public static synchronized ClassFactory getInstance() {
        if (instance == null) {
            instance = new ClassFactory();
        }
        return instance;
    }

    public <T extends BaseClassFirst> T createClass(Class<T> firstClazz) {
        if (firstClazz.equals(SubClassFirst.class)) {
            try {
                return firstClazz.newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public <T extends BaseClassSecond> T createClass(Class<T> secondClazz) {
        if (secondClazz.equals(SubClassSecond.class)) {
            try {
                return secondClazz.newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public static void main(String[] args) {
        ClassFactory factory = ClassFactory.getInstance();
        SubClassFirst first = factory.createClass(SubClassFirst.class);
        SubClassSecond second = factory.createClass(SubClassSecond.class);
        for (Method method : ClassFactory.class.getDeclaredMethods()) {
            System.out.println(method);
        }
    }
}

【问题讨论】:

    标签: java generics overloading


    【解决方案1】:

    当方法可以在编译时解析时,这有效,因为返回类型是签名的一部分。擦除后有两种方法

    public EJBHome lookupHome(Class homeClass) throws PayrollException;
    public EJBLocalHome lookupHome(Class homeClass) throws PayrollException;
    

    你不能在没有泛型的情况下定义这些,但由于返回类型是签名的一部分,这些是不同的方法。

    你可以打电话

    lookupHome(EJBHome.class);
    lookupHome(EJBLocalHome.class);
    

    但不是

    Class c= EJBHome.class;
    lookupHome(c); // Ambiguous method call.
    

    编辑:尝试以下操作。

    for (Method method : EJBHomeFactory.class.getDeclaredMethods()) {
        System.out.println(method);
    }
    

    你应该会看到类似的东西

    public javax.ejb.EJBHome EJBHomeFactory.lookupHome(java.lang.Class)
    public javax.ejb.EJBLocalHome EJBHomeFactorylookupHome(java.lang.Class)
    

    同样,如果你使用 javap -c。

    调用虚拟 #8; //方法lookupHome:(Ljava/lang/Class;)Ljavax/ejb/EJBHome;

    调用虚拟 #10; //方法lookupHome:(Ljava/lang/Class;)Ljavax/ejb/EJBLocalHome;

    编辑如果你想要一个返回类型是签名一部分的例子..

    class A {
       public static Byte getNum() { return 0; }
    }
    
    class B {
      public static void main(String ... args) {
         int i = A.getNum();
         System.out.println(i);
      }
    }
    

    编译运行,没有报错,现在把A中getNum的签名改成

    class A {
       public static Integer getNum() { return 0; }
    }
    

    只编译类A。如果返回类型不是签名的一部分,这对B没有影响,但是如果你运行B而不重新编译它,你会得到

    Exception in thread "main" java.lang.NoSuchMethodError: A.getNum()Ljava/lang/Byte;
    at B.main(B.java:10)
    

    如您所见,返回类型Ljava/lang/Byte; 是返回类型的内部名称和签名的一部分。

    【讨论】:

    • 彼得,感谢您的回答。这是否意味着编译器是否抛出错误取决于这两个方法的调用方式?或者在极端情况下,如果这两个方法从来没有被调用过,编译器会在这两个方法上抛出错误?
    • 返回类型不是签名的一部分。
    • @jprete,正如我所演示的,返回类型是字节码级别签名的一部分。你会如何支持你的说法?
    • 编译器只会抛出由于编译器错误而崩溃的错误,它通常会报告代码中的错误。如果它认为调用不明确,它将报告错误。如果这两个方法从未被调用过,如果它会通过调用进行编译,则不会报告错误。
    • Java 语言规范明确声明返回类型不是方法签名的一部分。我无法确定规范是否使两种引用的方法合法。我的直觉说不,但我不认为规范明确禁止这种情况,因为它们在编译时可以区分,就像两个具有相同名称的方法一样。不允许出现与此类似的情况,但不一定是这种情况。
    【解决方案2】:

    返回类型不是签名的一部分。

    【讨论】:

    【解决方案3】:

    对于泛型,这两种方法确实有不同的参数类型,所以编译器应该能够将它们分开。检查当前的语言规范,应该允许这两种方法。我听说在 Java 7 中不允许使用它们,这在理论上是非常糟糕的。

    要回答您的问题,是的,它们确实具有不同的签名,具体取决于参数类型。

    但实际上,无论如何您都应该避免这种混淆。有两个不同的方法名。

    public <T extends EJBHome> T lookupHome(Class<T> homeClass) 
    
    public <T extends EJBLocalHome> T lookupLocalHome(Class<T> localHomeClass) 
    

    永远不需要重载。如有疑问,请使用不同的名称来中断重载。

    【讨论】:

    • 你是对的,使用不同的名称会更好。但是,我不是该现有代码的作者,我只是不知道为什么 Eclipse 总是抱怨同样的擦除。我尝试使用javac编译,也失败了。
    【解决方案4】:

    这些是重复的方法。类型擦除意味着Class&lt;T&gt; 在运行时减少为Class,这意味着您有两个方法,每个方法都有签名lookupHome(Class homeClass)。我的理解是编译器不应该编译这段代码;如果你有一个可以编译它的javac 版本,那就有问题了!

    最好的办法是重命名这两种方法,使它们的名称都比lookupHome 更具体。

    编辑: 在研究了语言规范一段时间后,我认为声明很可能是合法的,尽管我不是 100% 确定。我仍然认为在擦除后有两个具有相同签名的方法是个坏主意。

    【讨论】:

    • 示例代码确实在我们的项目中编译。该项目是使用十几个 ant 脚本编译的,我还没有深入研究它是如何工作的。但似乎编译器是 org.eclipse.jdt.core.JDTCompilerAdapter
    • 它编译它并且它工作,因为它们确实有不同的签名,看看javap -c的字节码
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多