我在多个不同的地方看到了实例化列表或ArrayList的人:
List<?> l = new ArrayList<>();
什么类型?这是否意味着它可以容纳任何类型?如果是这样,为什么要使用它而不仅仅是和ArrayList?
答案 0 :(得分:9)
这是否意味着它可以包含任何类型?
没有。这意味着您的l
变量可能指的是使用任何类型参数化的列表。所以它实际上是限制:您不会被允许向l
添加任何对象,因为您不知道它接受哪些项目。举一个具体的例子,l
可以是List<String>
,也可以是List<ExecutorService>
。
答案 1 :(得分:1)
正如Marko正确指出的那样,它对List类型有一个未知的限制。
Java文档说:
使用通配符指定无界通配符类型 (?),例如
List<?>
。这称为未知类型的列表。 有两种情况,无界通配符很有用 的方法:
- 如果您正在编写可以使用Object类中提供的功能实现的方法。
- 当代码使用泛型类中不依赖于类型参数的方法时。例如,List.size或List.clear。 事实上,
Class<?>
经常被使用,因为大多数方法都在 上课不依赖于T。
答案 2 :(得分:1)
让我把它作为一个长时间的床故事;读它睡着了:))
让我们从这一点开始 - 要调用泛型方法,必须提供其类型参数 。 (除非以&#34; raw&#34;方式调用该方法,即以删除的形式调用,这是另一个主题:)
例如,要调用Collections.<T>emptyList()
,必须提供T
。它可以由程序员明确提供 -
List<String> list = Collections.<String>emptyList(); // T=String
但这很乏味,有点愚蠢。显然,在这种情况下,T
只能是String
。如果程序员必须重复显而易见的话,这是愚蠢的。
类型推断的用途很有帮助。我们可以省略类型参数,编译器可以推断程序员想要的是什么
List<String> list = Collections.emptyList(); // T=String is implied
请记住,程序员仍然会隐式提供<String>
。
据说,程序员是所有类型参数的全知的独裁者,并且编译器和程序员对类型参数何时可以省略并且可以从上下文推断具有共同的理解。当程序员省略一个类型参数时,他知道编译器可以根据严格的算法(他掌握的:)完全按照他的意图推断它。 编译器可以自行选择类型参数,而不是程序员,并将其传递给编译器。
实际上,类型推断是如此复杂,很少没有程序员知道在很多情况下会发生什么:)程序员更像是一个制作模糊命令的独裁者,编译器尽力使其理解。我们主要是根据直觉编写代码,而不是关注细节,我们有点认为,如果编译器批准代码,代码就会完成我们想要的。
在任何情况下,所有类型参数都在编译时精确且可预测地修复。任何省略的类型参数都等效于显式指定的参数。
某些类型参数是&#34;不可否认&#34;,例如捕获转换引入的类型变量。它们无法明确指定,只能推断它们。 (尽管程序员应该知道它们是什么,即使它们不能被命名)
在上一个示例中,T
只能推断为String
,没有其他选择。但在很多情况下,T
有更多候选者,类型推断算法必须有一个策略将其解析为其中一个候选者。例如,考虑这个孤独的陈述
Collections.emptyList();
T
可以是任何类型; T
已解析为Object
,因为,没有充分理由将其解析为其他任何内容,例如Integer
或String
等。{{1因为它是所有的超类型而更加特殊。
现在,让我们来建设者。从形式上讲,构造函数不是方法。但它们在很多方面非常相似。特别是,构造函数的类型推断几乎与方法相同。调用类CLASS的构造函数采用Object
。
就像方法一样,构造函数可以是通用的,具有自己的类型参数。例如,
new CLASS(args)
和类型推断也适用于通用构造函数
class Bar
{
<T>Bar(T x){ .. }
显式提供构造函数的类型参数,
new Bar("abc"); // inferred: T=String
虽然构造函数是通用的,但它非常罕见。
通用构造函数与通用CLASS不同!考虑一下这个
new <String>Bar("abc");
该类是通用的,构造函数不是。要调用类class Foo<T>
{
Foo(T x){ .. }
的构造函数,我们执行
Foo<String>
到目前为止我们一直在讨论的方法类型推断在这里不适用,因为构造函数甚至不是通用的。在Java 5/6中,CLASS没有类型推断,因此必须明确指定 new Foo<String>(""); // CLASS = Foo<String>
。这是愚蠢的,因为<String>
在这种情况下是显而易见的。有解决方法(即使用静态工厂方法),但人们当然非常沮丧并要求解决方案。
在Java 7中,这个问题通过&#34;钻石推理&#34; -
<String>
&#34;金刚石&#34;是指好奇的 new Foo<>(""); // inferred: T=String
运算符。这是必需的;我们不能简单地写
<>
因为它已经有了不同的含义 - 调用&#34; raw&#34;的构造函数 new Foo("");
。
通过钻石推理,我们可以做Java 5/6中无法做到的事情
Foo
请记住,仍然通过钻石推理提供List<Object> list = new ArrayList<>(); // Java 7. inferred: E=Object
// equivalent to
List<Object> list = new ArrayList<Object>(); // <Object> is required in Java 5/6
。
最后,我们回到你原来的问题
T=Object
这里推断出List<?> list = new ArrayList<>();
(还有什么?)。代码等同于
E=Object
是的,List<?> list = new ArrayList<Object>();
对象确实是list
,而不是ArrayList<Object>
。
另请注意,以下内容将是非法且无意义的
ArrayList<SomethingElse>
List<?> list = new ArrayList<?>();
^^^
中的 CLASS
必须是具体类型。我们只能实例化特定元素类型的new CLASS(args)
。
变量ArrayList
的声明类型List<?>
过于笼统。对于局部变量,最好的做法是将IMO声明为更具体的类型
list
不要在这里使用ArrayList<Object> list = new ArrayList<>();
- 这只会让每个人感到困惑。
在相关的说明中,很多人会争论&#34;针对界面的程序&#34;
<?>
这是错误的IMO。我们是谁在本地块中提供抽象?在实现中使用最具体的类型以获得最大的清晰度; 在接口中使用抽象类型。
ZZZZZZZZZZ