具有不同泛型的方法参数是否使方法具有不同的签名?

时间:2011-02-24 18:56:02

标签: java generics overloading

我有以下工厂类。它有两个方法,它们接受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);
    }
}

因素类: import 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);
        }
    }
}

4 个答案:

答案 0 :(得分:3)

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

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。

invokevirtual#8; //方法lookupHome:(Ljava / lang / Class;)Ljavax / ejb / EJBHome;

invokevirtual#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;是返回类型的唯一名称,也是签名的一部分。

答案 1 :(得分:1)

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

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:1)

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

要回答你的问题,是的,他们确实有不同的签名,基于参数类型。

但实际上,你应该避免这种混乱。有两种不同的方法名称。

public <T extends EJBHome> T lookupHome(Class<T> homeClass) 

public <T extends EJBLocalHome> T lookupLocalHome(Class<T> localHomeClass) 

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