A.3 容易忽略的事实

许多开发人员或许惊讶于 ArrayListArrayList 并无实质性的关联。如例 A-4 所示,可以将 Object 的子类添加到 Object 集合中。

例 A-4 List 的应用

  1. List<Object> objects = new ArrayList<Object>();
  2. objects.add("Hello");
  3. objects.add(LocalDate.now());
  4. objects.add(3);
  5. System.out.println(objects);

很好!由于 StringObject 的子类,可以将 String 引用赋给 Object 引用。读者可能认为,在声明字符串列表之后就能为其添加对象,但实际情况并非如此,如例 A-5 所示。

例 A-5 List 与对象一起使用

  1. List<String> strings = new ArrayList<>();
  2. String s = "abc";
  3. Object o = s;
  4. // strings.add(o); ➋
  5.  
  6. // List<Object> moreObjects = strings; ➌
  7. // moreObjects.add(new Date());
  8. // String s = moreObjects.get(0); ➍

❶ 合法

❷ 不合法

❸ 同样不合法,但假设其合法

❹ 损坏的集合

由于 StringObject 的子类,我们可以将 String 引用赋给 Object 引用,但无法将 Object 引用添加到 List。这似乎有些奇怪,原因在于 List 并非 List 的子类。在声明类型时,可以添加的唯一实例就是所声明的类型,使用子类或超类实例均不合法。换言之,参数化类型(parameterized type)具有不变性(invariance)。

在本例中,从注释掉的语句不难看出为何 List 不是 List 的子类。假设可以将 List 赋给 List,那么通过对象引用列表就能将非字符串的内容添加到列表中。这样一来,采用字符串列表的原始引用检索时会导致强制转换异常,编译器将无法判断转换是否有效。

不过,如果定义了一个数字列表,应该就可以为列表添加整数、浮点数与双精度浮点数。为此,我们需要在类型边界(type bound)中使用通配符。