Java为什么不应该';不允许在变量声明上使用泛型类型声明?
假设我们有这样一个类:Java为什么不应该';不允许在变量声明上使用泛型类型声明?,java,generics,language-design,Java,Generics,Language Design,假设我们有这样一个类: public class xx { public interface Foo<T> { T getValue(); void setValue(T value); } public void resetFoos(Iterable<Foo<?>> foos) { for (Foo<?> foo : foos) foo.setVa
public class xx {
public interface Foo<T> {
T getValue();
void setValue(T value);
}
public void resetFoos(Iterable<Foo<?>> foos) {
for (Foo<?> foo : foos)
foo.setValue(foo.getValue());
}
}
这一直让我恼火。另外,似乎有一个简单的解决方案
我的问题:有什么“好”的理由不应该扩展java语言以允许对变量声明进行泛型类型声明吗?例如:
public class xx {
public interface Foo<T> {
T getValue();
void setValue(T value);
}
public void resetFoos(Iterable<Foo<?>> foos) {
for (Foo<?> foo : foos) {
final <T> Foo<T> typedFoo = foo;
foo.setValue(foo.getValue());
}
}
}
我想知道是否有编译器向导可以解释为什么这样做太难,或者可以(而且应该)这样做
编辑:
针对这一建议的解决方案:
public <T> void resetFoos(Iterable<Foo<T>> foos) {
for (Foo<T> foo : foos) {
foo.setValue(foo.getValue());
}
}
编译错误为:
方法resetFoos
不能应用于给定类型
必需:Iterable
找到:List>
符合形式参数类型Iterable
其中,T
是一个类型变量:
T扩展在方法resetFoos(Iterable)
(通过ideone.com使用sun-jdk-1.7.0_10)您可以使resetFoos
方法本身具有通用性:
public <T> void resetFoos(Iterable<Foo<T>> foos) {
for ( Foo<T> foo : foos)
foo.setValue(foo.getValue());
}
public void resetFoos(Iterable foos){
for(Foo-Foo:foos)
setValue(foo.getValue());
}
这样编译器就知道foo.getValue()
中的T
与foo.setValue()
中的T
是相同的
这只是解决办法。我不知道为什么编译器不允许您在变量级别声明泛型,例如final Foo typedFoo=Foo代码>;我只知道不能在变量级别声明它。但是,在这里,方法级别已经足够了。这可能是可以做到的,可能有一些边缘情况。但事实上,没有做到这一点的原因很简单,JLS人员没有做到这一点
类型系统无法推断程序的所有内容。它可以做很多事情,但总有一些地方,编译器/类型检查器不知道人类可以证明的东西。因此,设计编译器和类型检查器的人必须决定在推理和智能方面要走多远——不管他们停在哪里,总会有人提出这样一个问题:“他们为什么不多走一步?”
我的猜测——这只是一个猜测——是这个推论没有成功,因为(a)它很容易解决(如Rgetman所示),(b)它使语言复杂化,(b.1)它引入了棘手的边缘情况,其中一些甚至可能与语言的其他功能不兼容,(c)JLS的设计师觉得他们没有时间进行这项工作。问题在于,在相关声明中:
foo.setValue(foo.getValue());
两次出现的?
可以(据编译器所知)绑定到不同的类型。编译器不知道一个?
(由getValue()
返回)与setValue
中预期的?
不一样,这似乎很愚蠢。但在其他情况下,结果并不那么明确。如果类型参数应该是相同的,则该语言要求它们的名称相同
中提供了一个很好的解决方法。对java语言的每次更改都将非常困难
您的示例并不是真正需要类型变量。编译器已经通过通配符捕获创建了一个类型变量,只是类型变量对程序员不可用。可能有几种补救办法
让程序员访问类型变量。这不是一项容易的任务。我们需要发明一些奇怪的语法和语义。这种奇怪和复杂可能无法证明这种好处的合理性
共享捕获转换。现在,每个表达式都分别经过捕获转换。规范没有识别出foo
是foo
,因此这两个表达式应该共享相同的捕获转换。它在一些简单的情况下是可行的,例如,一个有效的最终局部变量应该只转换一次,而不是每次访问
推断变量类型-var foo2=foo
从右侧推断出foo2
的类型,它实际上首先经历捕获转换,因此foo2
的类型将是Foo(对于某些x
),带有一个新的类型变量x(仍然不明显)。或者直接在foo
-上为(var-foo:foos)
使用var
。推断变量类型是一种成熟的/经过测试的技术,其好处远比解决这个问题更广泛。所以我会投这个票。如果我们能活那么久,它可能会出现在Java9中
基本上,类型变量在Java中目前只能有两个作用域:1)类作用域,或2)方法作用域。您会问,为什么不允许另一个作用域——本地代码块的作用域(在本例中,是for循环的内部)
是的,在某些情况下,这会很有帮助。将其添加到语言中并不难。但是,这些情况非常罕见,并且比帮助更容易让人困惑。此外,正如您已经发现的,存在一种相对简单而有效的解决方法--将本地块范围移动到一个私有帮助程序函数,它不能使用方法作用域类型变量时:
public void resetFoos(Iterable<Foo<?>> foos) {
for (Foo<?> foo : foos) {
resetFoo(foo);
}
}
private <T> void resetFoo(Foo<T> foo) {
foo.setValue(foo.getValue());
}
public void resetFoos(Iterable foo:foos){
重置foo(foo);
}
}
私有无效重置Foo(Foo-Foo){
setValue(foo.getValue());
}
是的,通过进行额外的方法调用,这可能会降低代码的效率,但这是一个次要问题。为什么不使用public void resetFoos(Iterable foos){…}
?伙计们,Iterable!=Iterable@Archie你的观点是什么?我们想知道你为什么不想使用这个解决方案。因为它不能解决IterableI的问题。我想把我的问题用黑体字写出来,我是想弄清楚实际的问题是什么,但显然不是!:)没有真正回答这个问题。但是每个Foo都必须有相同的类型参数
public static class FooImpl<T> implements Foo<T> {
private T value;
public FooImpl(T value) { this.value = value; }
@Override public T getValue() { return value; }
@Override public void setValue(T value) { this.value = value; }
}
public static <T> void resetFoos(Iterable<Foo<T>> foos) {
for (Foo<T> foo : foos) {
foo.setValue(foo.getValue());
}
}
public static void main(String[] args) {
final Foo<Object> objFoo = new FooImpl<>(new Object());
final Foo<Integer> numFoo = new FooImpl<>(new Integer(42));
final Foo<String> strFoo = new FooImpl<>("asdf");
List<Foo<?>> foos = new ArrayList<>(3);
foos.add(objFoo);
foos.add(numFoo);
foos.add(strFoo);
resetFoos(foos); // compile error
System.out.println("done");
}
public <T> void resetFoos(Iterable<Foo<T>> foos) {
for ( Foo<T> foo : foos)
foo.setValue(foo.getValue());
}
foo.setValue(foo.getValue());
public void resetFoos(Iterable<Foo<?>> foos) {
for (Foo<?> foo : foos) {
resetFoo(foo);
}
}
private <T> void resetFoo(Foo<T> foo) {
foo.setValue(foo.getValue());
}