假设我们有这样一个类:
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());
}
}
即使它看起来像“应该”,它也无法编译:
xx.java:10: setValue(capture#496 of ?) in xx.Foo<capture#496 of ?> cannot be applied to (java.lang.Object)
foo.setValue(foo.getValue());
原因是foo
没有绑定泛型类型,因此编译器不会“知道”foo.getValue()
的输出与foo.setValue()
的输入兼容。
所以要解决这个问题,你必须创建一个新方法,只是为了在for()
循环中绑定泛型类型参数:
public class xx {
public interface Foo<T> {
T getValue();
void setValue(T value);
}
public void resetFoos(Iterable<Foo<?>> foos) {
for (Foo<?> foo : foos)
this.resetFoo(foo);
}
// stupid extra method here just to bind <T>
private <T> void resetFoo(Foo<T> foo) {
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());
}
}
}
或者更简洁地说明for()
循环:
public class xx {
public interface Foo<T> {
T getValue();
void setValue(T value);
}
public void resetFoos(Iterable<Foo<?>> foos) {
for (<T> Foo<?> foo : foos)
foo.setValue(foo.getValue());
}
}
我想知道某些编译器向导是否可以解释为什么这会太难,或者可以(而且应该)完成。
修改
回应这个建议的解决方案:
public <T> void resetFoos(Iterable<Foo<T>> foos) {
for (Foo<T> foo : foos) {
foo.setValue(foo.getValue());
}
}
此方法签名不允许将具有各种泛型类型的Foo
重置为一起。换句话说,尝试传入Iterable<Foo<?>>
会导致编译错误。
This example演示了这个问题:
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");
}
编译错误如下:
方法
resetFoos
无法应用于给定类型;必填:
Iterable<Foo<T>>
找到:
List<Foo<?>>
原因:没有类型变量
中声明的T
的实例存在,因此参数类型List<Foo<?>>
符合形式参数类型Iterable<Foo<T>>
其中T
是一个类型变量: 方法T extends Object
<T>resetFoos(Iterable<Foo<T>>)
(通过ideone.com使用sun-jdk-1.7.0_10)
答案 0 :(得分:2)
您可以将resetFoos
方法本身设为通用:
public <T> void resetFoos(Iterable<Foo<T>> foos) {
for ( Foo<T> foo : foos)
foo.setValue(foo.getValue());
}
这样编译器就知道来自T
的{{1}}与foo.getValue()
的{{1}}相同。
这只是解决方法。我不知道为什么编译器不允许你在诸如T
之类的变量级别声明泛型;我只知道你不能在变量级别声明它。但是,这里的方法水平就足够了。
答案 1 :(得分:2)
可能有可能完成,可能还有一些边缘情况。但实际上,它没有完成的原因仅仅是JLS人员没有做到这一点。
类型系统无法推断关于您的程序的所有内容。它会做一堆,但总会有一些地方,编译器/类型检查器不会知道人类可以证明的东西。因此,设计编译器和类型检查器的人必须决定在推断和聪明方面走多远 - 无论他们停在哪里,总会能够提出类似这样的问题:“为什么没有”他们采取了这个额外的步骤吗?“
我的猜测 - 这只是一个猜测 - 是这个推论没有成功,因为(a)它很容易解决(如rgettman所示),(b)它使语言变得复杂, (b.1)它引入了棘手的边缘情况,其中一些甚至可能与该语言的其他特征不相容,(c)JLS的设计者并不觉得他们有时间研究它。
答案 2 :(得分:2)
对Java语言的每次更改都会非常困难。
您的示例并非真正需要类型变量。编译器已经通过通配符捕获创建了一个类型变量,只是程序员无法使用类型变量。可能有几种补救措施
让程序员访问类型变量。这不是一件容易的事。我们需要发明一些bizzare语法和语义。奇怪和复杂性可能无法证明其好处。
共享捕获转换。现在,每个表达式都分别通过捕获转换。规范无法识别foo
是foo
,因此两个表达式应共享相同的捕获转换。在一些简单的情况下它是可行的,例如,有效的最终局部变量应该只被捕获转换一次,而不是每次访问。
推断变量类型 - var foo2 = foo;
从右侧推断foo2
的类型,实际上首先进行捕获转换,因此foo2
的类型将为{ {1}},带有新的类型变量x(仍然是不可否认的)。或者直接在Foo<x> for some x
- var
上使用foo
。推断变量类型是一种成熟/经过测试的技术,其优势远远超过解决此问题的范围。所以我会投票支持这个。如果我们可以活得那么久,它可能会出现在Java 9中。
答案 3 :(得分:2)
基本上,类型变量目前在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());
}
是的,这可能会通过额外的方法调用降低代码效率,但这只是一个小问题。
答案 4 :(得分:1)
问题在于有问题的陈述:
foo.setValue(foo.getValue());
?
的两次出现(就编译器所知)可以绑定到不同的类型。编译器无法确定?
(由getValue()
返回)与?
中预期的setValue
不同,这似乎很愚蠢}。但在其他情况下,结果并不那么明确。如果类型参数应该相同,则该语言要求类型参数的名称相同。
rgettman's answer中提供了一个很好的解决方法。