泛型和原始类型

3

我写了一些使用泛型的代码,但遇到了以下情况,我无法理解:

我有一个接口IpRange和以下类:

public class Scope<IpRange<T extends IP>> {
     List<IpRange<T>> rangesList;
     public List<IpRange<T>> getRangesList() {return rangesList;}
}

现在,如果我从某个测试类中编写以下内容:
Scope<Ipv4> myScope = new Scope<Ipv4>(); 
scope.getRangesList().get(0)

我正在获取IpRange类型的对象,但如果我使用原始类型并执行以下操作:
Scope myScope = new Scope();
scope.getRangesList().get(0)

我得到了Object对象,除非我明确将其转换为Range,否则我无法使用ipRange方法。

如果是List<T>,我就理解了,因为我使用了原始类型,编译器无法知道列表项的实际类型,但在这种情况下,它将始终是IpRange类型,那么为什么我没有得到Object?

问题在于,当我创建范围时,我不一定知道实际的范围类型。考虑这个构造函数:public Scope(String rangeStringList); 就我所知,字符串可以是"16.59.60.80"或"fe80::10d9:159:f:fffa%"。但我知道的是,我向编译器传递了一些IpRange对象,并且我希望能够使用此接口,无论这是ipv4还是ipv6。由于编译器可以确定这是ipRange,即使我使用了原始类型,我想知道为什么Java选择以这种方式进行。


7
泛型只是语法糖。所有那些东西只是为了在编译之前进行类型检查。在编译期间,它会被转换为类型转换。由于您在第二个示例中没有指定类型,编译器不会为您进行转换。 - styfle
1
@axelrod 我猜答案是“因为Java的设计者选择这样做”。设计选择是原始类型会完全忽略像你这样的通用约束,而不是使找到正确擦除的算法更聪明。 - millimoose
2
基本上,原始类型只是为了向后兼容而存在。编写声明的正确方法应该是像 Scope<? extends IpRange<?>> myScope = new Scope<Ipv4>(); 这样。 (绝对没有理由实例化原始类型。) - millimoose
在这种情况下,最好使用继承,这样您就可以得到一个IpRange对象,它是Ipv4RangeIpv6Range。编辑:实际上,您可能根本不需要泛型。泛型(在Java中)的目的是在您获取结果时消除强制转换,而由于您没有使用它进行强制转换,因此所有这些都有点无意义... - Jeff
1
没错,但你仍然不需要泛型。你只需要 public class Scope {public List<IpRange<IP>> getRangesList() {return rangesList;} 因为你在编译时不使用 T 的值。编辑:显然还要从字段中适当地移除泛型... - Jeff
显示剩余3条评论
3个回答

2
人们指出,在使用原始类型时,所有通用类型信息都被剥离,并暗示这与向后兼容性有关。我想如果没有解释可能不够令人满意,因此我将尝试解释一下,说明在像你的代码中遇到这样的问题的可能性。
首先,想象一下你编写的代码是旧库的一部分,你正在通过添加通用类型来升级该库。也许这是一个流行的库,许多人使用了旧代码。
某人可能已经使用了你的库中的类来做类似以下的事情:
private void someMethod(Scope scope, Object object) {
  scope.getRangesList().add(object);
}

现在看这个,我们知道Object可能不是IpRange类型,但这是一个私有方法,所以让我们假设类型检查是由调用someMethod的任何方法有效执行的。这可能不是好的代码,但没有泛型它可以编译并且可能正常工作。
想象一下,写这篇文章的人升级了你的库的新版本以获取一些新功能或不相关的错误修复,同时他们现在可以使用更多的类型安全性来使用你的泛型类。然而,他们可能不想使用它,因为像上面那样使用原始类型的代码太多了。
你实际上建议的是,即使“scope”是原始类型,从getRangesList()返回的List必须始终是List<IpRange<? extends IP>>类型,所以编译器应该注意到这一点。
如果是这种情况,添加一个Object到列表中的旧代码将不再能够编译而需要进行编辑。这是一种后向兼容性被破坏的方式,而不是忽略所有可用的原始类型的泛型类型信息。

1

是的,如果您使用原始类型,则在该方法的其余部分中所有泛型都会“关闭”,并且所有泛型类型都会变成原始类型,即使它们本来不受原始类型缺少的泛型参数的影响。


0
如果您使用原始类型,所有泛型类型信息都将从类中剥离,包括在实例上调用的静态方法。
这样做的原因是为了与Java 1.4向后兼容。

你能详细说明一下,如果返回值是我所描述的IpRange,它将如何损害向后兼容性吗? - axelrod
@axelrod 是的 - 1.4 版本的 API 返回了一个 Object 类型 - 这只是保持一致。1.4 版本的代码仅使用我们现在称之为原始类型。需要记住的是,在运行时,没有泛型 - 返回类型实际上是 Object。泛型只存在于编译时,在运行时会进行隐式转换为泛型类型。 - Bohemian

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