我第一次尝试使用通用方法,有点困惑。我创建了一个简单的例子来证明我可能会以错误的方式解决这个问题并需要理顺。我正在使用Eclipse 3.6.1。我的印象是编译器通过推理确定了参数类型,但我不确定为什么它迫使我在泛型方法中使用强制转换。这是一个简单的例子。
class Test1
{
Test1 () {};
public String getX () {return "Test1"};
};
class Test2
{
Test2 () {};
public String getX () {return "Test2"};
};
我的主要方法:
public static void main(String args[])
{
Test1 tst1 = new Test1();
Test2 tst2 = new Test2();
System.out.println("result: " + displayTest(tst1, tst2));
}
static <T,S> boolean displayTest(T x, S y)
{
System.out.println("X: " + ((Test1) x).getX());
System.out.println("Y: " + ((Test2) y).getX());
if (((Test1) x).getX().equals(((Test2) y).getX()))
return true;
else
return false;
}
我认为编译器会知道T
在这种情况下是Test1
的实例而S
是Test2
,但在Eclipse中,getX
是不是一个有效的方法。为了使其编译,它迫使我将对象强制转换为正确的类型,这在我看来是违反泛型方法的一般原则。
显然,我没有得到这个并且我做错了什么。编译器如何知道泛型方法中的类型呢?怎么应该做这样的事情?在我试图实现这个的大型系统中,我有几种方法可以对不同类型的对象进行操作,并且我试图使它们成为通用的。即方法1调用方法2(使用泛型类型),后者又调用方法3(再次传递泛型类型)。我希望只有函数调用的开始(在这种情况下调用方法1)需要知道对象是什么类型,所有后续方法只是通用方法。
非常感谢。
答案 0 :(得分:3)
编译器不知道方法内部的类型。它们可以是任何东西。
如果您使用<T extends Y>
,其中Y
是定义getY()
的界面,则可以使用。
泛型的要点是确保编译时的安全性。它们主要是编译时的概念。例如:
public static <T> T instantiate(Class<T> clazz) throws Exception {
return clazz.newInstance();
}
这种方法可以在没有任何情况下使用,如下所示:
Foo foo = instantiate(Foo.class);
Bar bar = instantiate(Bar.class);
另一个例子,来自Collections框架。有Collections.enumeration(collection)
,这是通用的。所以:
Enumeration<String> enumeration1 =
Collections.enumeration(new ArrayList<String>(..));
Enumeration<Integer> enumeration2 =
Collections.enumeration(new ArrayList<Integer>(..));
没有演员,但你确定这些将是类型。然后,nextElement()
方法将返回String
或Integer
,而无需投射:
String s = enumeration1.nextElement();
Integer i = enumeration2.nextElement();
如果没有泛型,则需要在这些示例中使用强制转换,如果传递了错误的参数,则会出现运行时异常(很可能是ClassCastException
)。使用泛型,您可以在编译时获得异常。
编译器实际上代表你添加了这些强制转换,但只有在确定强制转换不会出错之后。
答案 1 :(得分:1)
问题是在编译时编译器不知道T
和S
的具体类型。您的主要方法使用displayTest
和Test1
的具体实例调用Test2
,但没有什么可以阻止您使用String
和Integer
来调用它
这意味着您无法在getX
上致电x
,因为您无法保证T
是Test1
的子类。
您可以使用捕获来约束T
和S
的类型:
boolean <T extends Test1, S extends Test2> displayTest(T x, S y)
这告诉编译器T
必须是Test1
(或Test1
的子类),这意味着你不需要强制转换。
答案 2 :(得分:0)
为什么编译器会知道T和S是Test1和Test2?您可以从代码中的任何位置调用displayTest,任意次数可以使用任意数量的不同对象类型。应该做什么?
另外,eclipse!=编译器。 Eclipse的代码完成不会“编译”任何东西。编译器确切地知道放在那里的类型,但仅适用于该函数的每个CALL。这就是为什么它被称为通用。它可以采用任何通用类型。
答案 3 :(得分:0)
当你说编译器通过推理确定参数类型时,它意味着与你想的不同。
由于displayMethod是一个通用方法,调用该方法的“正确”方法是通过调用它来告诉编译器在这个特定方法调用中代表什么T和S
ClassName.<Test1,Test2>displayMethod(tst1,tst2)
但是由于java很聪明,编译器可以推断你没有告诉它究竟是什么T和S,如果你这样做,T和S将成为Test1和Test2,
ClassName.displayMethod(tst1, tst2)
另外,像Cameron skinner所说,你可以通过使T扩展Test1和S扩展Test2来修复你的代码。您可以执行的另一项修复是保留<T, S>
,并将getX()
方法重命名为覆盖toString()
方法。由于toString()
是Object
类的成员,因此泛型类T and S
可以访问它,您不需要将方法限制为仅接受类型为{的参数{1}}