在进行实例化列表时,是什么类型的?

我在多个不同的地方看到过实例化列表或ArrayList的人:

List l = new ArrayList(); 

什么类型?? 这是否意味着它可以包含任何类型? 如果是这样,为什么要使用它而不仅仅是和ArrayList?

这是否意味着它可以包含任何类型?

不。这意味着您的l变量可以引用以任何类型参数化的列表。 所以它实际上是一个限制 :你不会被允许向l添加任何对象,因为你不知道它接受哪些项目。 举一个具体的例子, l可以是List ,也可以是List

正如Marko正确指出的那样,它对List类型的未知限制。

Java文档说:

使用通配符(?)指定无界通配符类型,例如List 。 这称为未知类型的列表。 有两种情况,无界通配符是一种有用的方法:

  • 如果您正在编写可以使用Object类中提供的function实现的方法。
  • 当代码使用generics类中不依赖于类型参数的方法时。 例如,List.size或List.clear。 实际上,经常使用Class因为Class中的大多数方法都不依赖于T.

让我把它作为一个很长的床时间故事; 读它睡着了:)


让我们从这一点开始 – 要调用generics方法, 必须提供其类型参数。 (除非以“原​​始”方式调用该方法,即以擦除forms调用,这是另一个主题:)

例如,要调用Collections.emptyList() ,必须提供T 它可以由程序员明确提供 –

 List list = Collections.emptyList(); // T=String 

但这很乏味,有点愚蠢。 显然在这种情况下, T只能是String 。 如果程序员必须重复显而易见的话,这是愚蠢的。

这就是类型推断有用的地方。 我们可以省略类型参数,编译器可以推断出程序员的意图

 List list = Collections.emptyList(); // T=String is implied 

请记住,程序员仍然会隐式提供


据说,程序员是所有类型参数的全知的独裁者,并且编译器和程序员对类型参数何时可以省略并且可以从上下文推断具有共同的理解。 当程序员省略了一个类型参数时,他知道编译器可以完全按照他的意图推断出它,基于一个严格的算法(他掌握了:)这不是编译器自行决定选择类型参数,而是程序员做的,并将其传达给编译器。

实际上,类型推断是如此复杂, 很少有程序员不知道在很多情况下发生了什么:)程序员更像是一个制作模糊命令的独裁者,编译器会尽力从中理解它。 我们主要是根据直觉编写代码,而不是关注细节,我们有点认为,如果编译器批准代码,代码就会完成我们想要的。

在任何情况下,所有类型参数都在编译时精确且可预测地修复。 任何省略的类型参数都等效于显式指定的参数。

某些类型参数是“不可否认的”,例如由捕获转换引入的类型变量。 它们不能明确指定,只能推断它们。 (尽管程序员应该知道它们是什么,即使它们不能被命名)


在前面的示例中, T只能被推断为String ,没有其他选择。 但在很多情况下, T候选者更多,类型推断算法必须有一个策略来解决它的候选者之一。 例如,考虑这个孤独的陈述

  Collections.emptyList(); 

T可以是任何类型; T被解析为Object ,因为,没有充分的理由将它解析为其他任何东西,比如IntegerString等。 Object更特殊,因为它是所有的超类型。


现在,让我们来建设者。 从forms上讲,构造函数不是方法。 但它们在很多方面非常相似。 特别是,构造函数的类型推断与方法几乎相同。 调用类CLASS的构造函数采用new CLASS(args)的forms。

就像方法一样,构造函数可以是通用的,具有自己的类型参数。 例如,

 class Bar { Bar(T x){ .. } 

类型推断也适用于generics构造函数

  new Bar("abc"); // inferred: T=String 

要为构造函数显式提供类型参数,

  new Bar("abc"); 

尽管构造函数是通用的,但这种情况非常罕见。


通用构造函数与通用CLASS不同! 考虑一下

 class Foo { Foo(T x){ .. } 

该类是通用的,构造函数不是。 要调用类Foo的构造函数,我们这样做

  new Foo(""); // CLASS = Foo 

到目前为止我们一直在谈论的方法类型推断在这里不适用,因为构造函数甚至不是通用的。 在Java 5/6中,CLASS没有类型推断,因此必须明确指定 。 这是愚蠢的,因为在这种情况下是显而易见的。 有解决方法(即使用静态工厂方法),但人们当然非常沮丧并要求解决方案。


在Java 7中,这个问题通过“钻石推理”解决 –

  new Foo<>(""); // inferred: T=String 

“钻石”指的是好奇的<>操作符。 这是必需的; 我们不能简单地写

  new Foo(""); 

因为那已经有了不同的含义 – 调用“raw” Foo的构造函数。

通过钻石推理,我们可以做Java 5/6中无法做到的事情

 List list = new ArrayList<>(); // Java 7. inferred: E=Object // equivalent to List list = new ArrayList(); //  is required in Java 5/6 

请记住, T=Object仍然通过钻石推理提供。


最后,我们回到你原来的问题

 List list = new ArrayList<>(); 

在这里,推断出E=Object (还有什么?)。 代码相当于

 List list = new ArrayList(); 

是的, list对象确实是一个ArrayList ,而不是ArrayList

另请注意,以下内容将是非法且无意义的

 List list = new ArrayList(); ^^^ 

new CLASS(args)中的new CLASS(args)必须是具体类型。 我们只能实例化特定元素类型的ArrayList


但是,变量list的声明类型List过于笼统。 对于局部变量,IMO以其更具体的类型声明它是最佳实践

 ArrayList list = new ArrayList<>(); 

不要在这里使用 – 这只会给每个人带来困惑。

在相关的说明中,很多人会争论“反对界面的程序”

  List list = new ArrayList<>(); ^^^^ 

这是错误的IMO。 我们是谁在本地块中提供抽象? 在实现中使用最具体的类型以获得最大的清晰度; 在接口中使用抽象类型。


ZZZZZZZZZZ