Java泛型:将泛型类型定义为仅返回类型

73

我正在查看一些关于GWT的GXT代码,我遇到了一个在Java教程中找不到其他示例的泛型用法。如果您想查看所有代码,则类名为com.extjs.gxt.ui.client.data.BaseModelData。以下是重要部分:

private RpcMap map;

public <X> X get(String property) {
  if (allowNestedValues && NestedModelUtil.isNestedProperty(property)) {
    return (X)NestedModelUtil.getNestedValue(this, property);
  }
  return map == null ? null : (X) map.get(property);
}

X 在此类或继承体系中的任何地方都没有被定义,在eclipse中使用“转到声明”时,它只会跳转到公共方法签名中的 <X>

我尝试使用以下两个示例调用此方法以查看发生了什么:

public Date getExpiredate() {
    return  get("expiredate");
}

public String getSubject() {
    return  get("subject");
}

它们被编译并且没有错误或警告。我认为至少要进行强制类型转换才能使其工作。

这是否意味着泛型允许使用一个神奇的返回值,可以是任何东西,并且只会在运行时崩溃?这似乎与泛型的预期相反。有人可以向我解释一下,可能给我一些更好的解释说明文档的链接吗?我已经阅读了Sun的23页泛型PDF文档,每个返回值的示例都在类级别上定义,或者在传递的参数之一中定义。


感谢迄今为止的回答!如果我有足够的声望,我会给你们所有人点赞(特别是saua,非常有帮助)。非常惊讶于反应时间。我仍然希望能看到一些参考资料,以便更详细地解释这个问题,但我认为我现在比以前更好地理解了它。 - Jason Tholstrup
请更新链接:http://extjs.com/deploy/gxtdocs/com/extjs/gxt/ui/client/data/BaseModelData.html - 已损坏 - Mr_and_Mrs_D
随意更新它。我已经好几年没用gxt了。 - Jason Tholstrup
一些 FXMLLoader 的方法也会使用它。这特别有帮助,因为类型转换总是由用户完成。 如果 ObjectInputStream::readObject 也能添加这种行为就太好了。 - Mordechai
6个回答

59
该方法返回您期望的类型(<X> 在该方法中被定义且绝对不受限制)。
这是非常危险的,因为没有任何规定确保返回类型实际上与返回的值匹配。
唯一的优点是,您无需对可以返回任何类型的通用查找方法的返回值进行转换。
我建议:谨慎使用此类结构,因为您几乎失去了所有类型安全性,只有在每次调用get()时不必编写显式转换。
是的:这几乎是黑魔法,在运行时会崩溃并破坏泛型应该达到的整个思想。

6
如果类型还用于限定方法中的泛型类型,这个结构更安全 - 这意味着您不需要显式转换。那么,在运行时就不会出现错误的可能性。 - Bill Michell
3
对于 GWT 而言,这可能是有意而为之的设计;GWT 编译成 JavaScript,而我们都知道 JavaScript 具有动态、弱类型的特性。 - Nicole

43

类型声明在方法中。这就是 "<X>" 的含义。该类型仅在该方法范围内有效,并且与特定调用相关。你的测试代码之所以能够编译,是因为编译器尝试确定类型,并且只有在无法确定时才会发出警告。在某些情况下,你必须明确说明。

例如,Collections.emptySet() 的声明如下:

public static final <T> Set<T> emptySet()

在这种情况下,编译器可以猜测:
Set<String> s = Collections.emptySet();

但如果它不能,你必须输入:
Collections.<String>emptySet();

附注:最近的Java版本(8+)在类型推断方面变得更加智能,像最后一个示例这样的代码不再需要显式类型(取决于它们被使用的位置)。 - Joachim Sauer

5

我正在尝试使用GXT类解决同样的问题。具体来说,我正在尝试调用一个带有以下签名的方法:

class Model {
    public <X> X get(String property) { ... }
}

要从您的代码中调用上述方法并将X转换为字符串,我执行以下操作:

public String myMethod(Data data) {
    Model model = new Model(data);
    return model.<String>get("status");
}

上面的代码将调用get方法并告诉它应该将X返回的类型作为字符串返回。 在方法与当前类相同的情况下,我发现我必须使用“this.”来调用它。例如:
this.<String>get("status");

正如其他人所说,GXT团队的做法相当粗糙且危险。


2

有趣的注释,来自 RpcMap (GXT API 1.2)

get方法的头部:

public java.lang.Object get(java.lang.Object key)

使用未实例化的通用参数 <X> 具有相同的效果,只是您不必在所有地方都使用“Object”一词。我同意其他帖子的看法,这很随意且有点危险。


2

当编译 BaseModelData 时,会产生未经检查的警告,因为它是不安全的。如果像这样使用,您的代码将在运行时抛出 ClassCastException,即使本身没有任何警告。

public String getExpireDate() {
  return  get("expiredate");
}

1
好的,我试着解释一下:只有在运行时类型不是字符串类型时,它才会抛出异常。如果是字符串类型,那就没问题了。你只是将强制转换移到了获取方法中。 - Jason Tholstrup
1
没错。如果你在编译代码时没有警告,泛型保证你永远不会收到ClassCastException;而忽略这些警告,BaseModelData就放弃了这个保证。 - erickson

0

是的,这很危险。通常,您会像这样保护此代码:

<X> getProperty(String name, Class<X> clazz) {
   X foo = (X) whatever(name);
   assert clazz.isAssignableFrom(foo);
   return foo;
}

String getString(String name) {
  return getProperty(name, String.class);
}

int getInt(String name) {
  return getProperty(name, Integer.class);
}

2
为什么这样做更好呢?它仍然可能在运行时崩溃,只是以不同的方式。 - David Roussel
3
通过插入两个连续的运行时炸弹来实现防止崩溃!首先,转换将因异常而崩溃......然后,如果这还不够,断言(可能无法运行)将使整个程序崩溃!更加复杂...没有额外的好处...-1。 - Newtopian
3
public <T> T get(int i, Class<T> type) { Object val = get(i); if (type.isInstance(val)) { return type.cast(val); } return null; }这段代码的作用是:根据传入的索引值和指定类型,获取泛型类型的元素。如果获取到的元素是指定类型的实例,则返回该元素,否则返回null。 - Tatarize

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接