无界通配符类型 List<?> 和原始类型 List 有什么区别?

IT小君   2021-10-27T02:21:16

你能帮我理解无界通配符类型 List原始类型 List之间的区别吗?

List<?> b;    // unbounded wildcard type
List a;       // raw type


除此之外,有人可以帮助我理解什么是有界类型参数列表吗?

List<E extends Number> c;
点击广告,支持我们为你提供更好的服务
评论(4)
IT小君

以下是三者的总结:

  • List: 没有类型参数的列表。它是一个列表,其元素可以是任何类型——元素可以是不同类型

  • List<?>:具有无界类型参数的列表。它的元素具有特定但未知的类型;元素必须都是相同的类型

  • List<T extends E>: 一个类型参数叫做 的列表T为 for 提供的类型T必须是扩展的类型E,否则它不是参数的有效类型。

2021-10-27T02:21:16   回复
IT小君

你真的应该看看 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所有这些类型都遵循Coolapi...所以我们可以安全地cool()在任何元素上调用该方法

有道理?希望这可以帮助。

2021-10-27T02:21:17   回复
IT小君

关于第一个问题,之间的区别ListList<?>

两者之间的一个显着区别是,当您将通配符作为类型时, 的类型Collection是未知的,因此该add方法将引发编译时错误。

您仍然可以从 中获取值List<?>,但您需要显式转换。

2021-10-27T02:21:17   回复
IT小君

这两种情况都让我们将任何类型的列表放入这个变量中:

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 永远不要在你的代码中使用原始类型。

2021-10-27T02:21:17   回复