为什么Java不支持构造函数的类型推断?

7
例如,要创建一个字符串的ArrayList,我们需要做类似以下的操作
List<String> list = new ArrayList<String>();

相反,它应该能够推断构造函数的参数类型,以便我们只需键入

List<String> list = new ArrayList();

为什么不能像泛型方法中推断类型参数一样推断类型呢?

4
问“为什么语言 X 不能执行特征 Y”是否有意义?答案通常是“因为它没有被设计成这样做”。 - skaffman
1
为什么!为什么!这是我对 Java 泛型实现的最大抱怨之一。将构造函数包装在静态工厂方法中可以使类型推断像魅力一样工作,那么为什么构造函数不行呢! - Joachim Sauer
@skaffman: 的确如此,但令人困惑的是类型推断确实存在于参数化方法中,因此其实现的规则和复杂性已经存在。只是在对象实例化方面不存在! - Joachim Sauer
对我来说,从右值推断类型(包括类型参数化)似乎更自然(也更安全),而不是从左值推断。如果不仅提供字段声明的接口,我会很满意 ArrayList<String> list = new(); - Cecil Has a Name
只要 ArrayList 不是 final,这个表达式就会不明确,因为可以合法地写出 ArrayList<String> strings = new ArrayList<String>();ArrayList<String> strings = new MyCustomArrayList();,而 new ArrayList<>(); 就比较清晰。 - Robert Munteanu
显示剩余2条评论
7个回答

22

虽然这是好知道的,但它并没有回答为什么当前版本不支持这个的问题。 - Joachim Sauer
@Joachim Sauer:我不能给出一个明确的答案,但是通过阅读钻石提案所引用的错误报告,我可以看出在Java 5时期这个解决方案并没有被很好地理解。即使在Java 5中没有推动构造函数推断,泛型也有点混乱。 - Robert Munteanu
3
我相信你要找的词汇是“设计疏忽”。 - Alex Feinman

6

就像其他人所说的那样,这是Java 7的列表之一。但是,我想指出Google Collections Library已经支持各种集合的这种排序,如果您愿意使用静态导入。例如,我经常编写以下代码:

List<String> list = newArrayList();

你所需要的只是静态导入,并且删除 newArrayList() 之间的空格 :)
(顺便提一下,静态导入应该是 Lists 类。类似的方法也适用于映射等。)

4
我个人认为这非常丑陋。:) - Bombe
4
一开始看起来很奇怪,但当你习惯了它之后,它可以显著减少杂乱。 - Jon Skeet
+1. 我现在使用它时已经不需要思考了,因为Eclipse会在自动完成时插入导入语句,而且我默认的构建路径上有Google Collections。 - finnw

2
除了JDK7的特性外,我猜你可能会使用extendssuper
class Animal {}
class  Dog extends Animal {}

List<? extends Animal> anims = new ArrayList<Dog>();
List<? super Dog> superdogs = new ArrayList<Animal>();

在这两种情况下,您将无法推断。

类型推断仅在您没有手动指定类型时才会使用(就像目前使用的有类型方法一样),因此它不会使这个任务变得不可能。 - Joachim Sauer

2

你想知道为什么当前的Java不支持它。

我只能说Java通常会尽可能采取小步骤。我猜测在Java 7之前,他们可能遇到了一些小的技术问题,他们不确定是否能够“正确”解决,可能与确保不会在一些旧的或非泛型代码中创建模糊不清的情况有关。

请注意罗伯特指出的语法将是这样的:

List<String> strings = new ArrayList<>();

注意到空的<>了吗?我猜这是为了消除这种类型推断与旧的非泛型代码之间的歧义;这可能只是他们最初没有想到的东西。

是的,我刚刚阅读了罗伯特的链接。<>是为了消除与原始类型的混淆,就像你所说的那样。 - Tarski
有趣的是,在C#中采取了相反的方向,显式类型在右侧,而变量的类型则是推断出来的。请注意,在C#的实现中,这也允许您从其他方法调用中推断返回类型,而无需显式定义类型。var strings = new ArrayList<String>();我必须补充一点,起初我并不太喜欢C#中的var关键字,但随着我越来越多地使用它以及泛型和匿名类型的类型推断,它们变得非常协调。 - jpierson

2

这可以实现,并且是计划在JDK7中添加的功能。


1

类型可以被推断,但作者们决定完全不使用类型推断比在某些情况下有一些有限的类型推断更好。

如果您想在JVM上使用类型推断,请查看scala


1
问题在于存在某种类型推断!参数化方法上的类型参数可以很好地推断出来!为什么不能将相同的规则应用于对象创建? - Joachim Sauer

0

我不是Java超级专家,所以我不能完全确定我将要陈述的内容。以下是我的想法:

由于Java通过擦除实现泛型,因此对于每个泛型类型都有一个基础的原始类型。如果您定义了一个泛型类型,那么将会有一个基础的原始类型,它将在所有地方使用Object

当您实例化一个新的ArrayList时,编译器从实例化类(例如您的示例中的ArrayList<String>)中推断出类型参数是错误的,因为存在一个具有该确切名称且没有类型参数的类(即原始类型ArrayList)。我还猜测这就是为什么在Java 7中,您必须在构造函数调用中添加<>来告诉编译器推断类型的原因。

有人可能会认为,只有在定义类为原始类型时,编译器才应该实例化原始类型,但我认为这会让人感到困惑。我认为编译器必须从不完整的表达式中推断出无法给出上下文的无效表达式,而这不适用于new ArrayList()语句。

我希望这很清楚,如果我错了,希望有人可以纠正我。


小提示:

另外,请注意原始类与使用 Object 作为类型参数的类型不同:

List<String> list = new ArrayList();

是有效的,而

List<String> list = new ArrayList<Object>();

不是这样的。在第一种情况下,原始类型可以像泛型类型一样使用,但在第二种情况下,您要求逆变性,这是不可用的(除非您使用通配符)。


我觉得回答这个问题是因为我认为这里的所有答案(都非常好!)实际上都没有真正回答最初的问题,即为什么 :) - Philippe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接