你能帮我理解无界通配符类型 List和原始类型 List之间的区别吗?
List<?> b; // unbounded wildcard type
List a; // raw type
除此之外,有人可以帮助我理解什么是有界类型参数列表吗?
List<E extends Number> c;
你能帮我理解无界通配符类型 List和原始类型 List之间的区别吗?
List<?> b; // unbounded wildcard type
List a; // raw type
除此之外,有人可以帮助我理解什么是有界类型参数列表吗?
List<E extends Number> c;
你真的应该看看 Effective Java,第 23 条:不要在新代码中使用原始类型。
要使用那本书中的示例,请考虑以下示例……如果您有一个不关心其中的元素类型的集合,该怎么办。例如,您想查看两个集合之间共有多少个元素。你可能会想到以下几点:
public static int numElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o : s1) {
if (s2.contains(o)) {
++result;
}
}
return result;
}
这个例子虽然有效,但由于使用了原始类型,因此不是一个好主意。原始类型根本就不是类型安全的……你最终可能会以一种非类型安全的方式修改集合并破坏你的程序。相反,请谨慎行事并使用类型安全的替代方案:
public static int numElementsInCommon(Set<?> s1, Set<?> s2) {
int result = 0;
for (Object o : s1) {
if (s2.contains(o)) {
++result;
}
}
return result;
}
不同之处在于您只能添加null
到 a Set<?>
,并且您不能对从 a 中取出的元素进行任何假设Set<?>
。如果你使用 raw Set
,你可以添加任何你想要的东西。该numElementsInCommon
方法是一个很好的示例,您甚至不需要添加任何内容,也不需要对集合中的内容进行任何假设。这就是为什么它是使用?
通配符的一个很好的候选者。
希望这可以帮助。阅读 Effective Java 中的整篇文章,它会真正变得清晰。
回答你问题的第二部分......记得我说过当你使用?
通配符时,你不能对你从集合中取出的元素做任何假设?如果您确实需要对从集合中删除的对象的接口做出假设,该怎么办。例如,假设您想跟踪一组Cool
事物。
public interface Cool {
// Reports why the object is cool
void cool();
}
那么你可能有一些这样的代码:
public static void reportCoolness(Set s) {
for (Object item : s) {
Cool coolItem = (Cool) item;
coolItem.cool();
}
}
这不是类型安全的......你需要确保你传入了一个只有Cool
对象的集合。要修复它,您可能会说:
public static void reportCoolness(Set<Cool> s) {
for (Cool coolItem : s) {
coolItem.cool();
}
}
这很棒!完全符合您的要求并且类型安全。但是如果以后你有这个怎么办:
public interface ReallyCool extends Cool {
// Reports why the object is beyond cool
void reallyCool();
}
由于所有ReallyCool
对象都是Cool
,您应该能够执行以下操作:
Set<ReallyCool> s = new HashSet<ReallyCool>();
// populate s
reportCoolness(s);
但是您不能这样做,因为泛型具有以下属性:假设B
是 的子类A
,则Set<B>
不是 的子类Set<A>
。对此的技术讨论是“泛型类型是不变的”。(与协变相反)。
要使最后一个示例工作,您需要Set<Cool>
通过(安全地)强制转换Set<ReallyCool>
. 为了避免让 api 的客户端通过这些讨厌的、不必要的代码,您可以reportCoolness
像这样使方法更加灵活:
public static void reportCoolness(Set<? extends Cool> s) {
for (Cool coolItem : s) {
coolItem.cool();
}
}
现在你的方法接受任何Set
包含有元素Cool
或的任何子类Cool
。所有这些类型都遵循Cool
api...所以我们可以安全地cool()
在任何元素上调用该方法
有道理?希望这可以帮助。
关于第一个问题,之间的区别List
和List<?>
:
两者之间的一个显着区别是,当您将通配符作为类型时, 的类型Collection
是未知的,因此该add
方法将引发编译时错误。
您仍然可以从 中获取值List<?>
,但您需要显式转换。
这两种情况都让我们将任何类型的列表放入这个变量中:
List nothing1 = new ArrayList<String>();
List nothing2 = new ArrayList();
List nothing3 = new ArrayList<>();
List nothing4 = new ArrayList<Integer>();
List<?> wildcard1 = new ArrayList<String>();
List<?> wildcard2 = new ArrayList();
List<?> wildcard3 = new ArrayList<>();
List<?> wildcard4 = new ArrayList<Integer>();
但是我们可以在这个对象中放入哪些元素呢?
我们只能将 String 放入List<String>
:
List<String> strings = new ArrayList<>();
strings.add("A new string");
我们可以将任何对象放入 List 中:
List nothing = new ArrayList<>();
nothing.add("A new string");
nothing.add(1);
nothing.add(new Object());
我们不能添加任何东西(除了null)到List<?>
! 因为我们使用泛型。Java 知道它是 List 类型,但不知道它的确切类型。并且不会让我们犯错。
结论:List<?>
,它是泛型 List,为我们提供了类型安全性。
PS 永远不要在你的代码中使用原始类型。
以下是三者的总结:
List
: 没有类型参数的列表。它是一个列表,其元素可以是任何类型——元素可以是不同类型。List<?>
:具有无界类型参数的列表。它的元素具有特定但未知的类型;元素必须都是相同的类型。List<T extends E>
: 一个类型参数叫做 的列表T
。为 for 提供的类型T
必须是扩展的类型E
,否则它不是参数的有效类型。