为什么不能在有界通配符泛型中拥有多个接口?
我知道 Java 的泛型类型有各种违反直觉的属性。这里有一个我不太明白的地方,希望有人能给我解释一下。为类或接口指定类型参数时,可以将其绑定,使其必须使用 public class Foo列表
没问题,但是 List
无法编译。考虑以下完整的代码片段:
import java.util.List;
public class Test {
static interface A {
public int getSomething();
}
static interface B {
public int getSomethingElse();
}
static class AandB implements A, B {
public int getSomething() { return 1; }
public int getSomethingElse() { return 2; }
}
// Notice the multiple bounds here. This works.
static class AandBList<T extends A & B> {
List<T> list;
public List<T> getList() { return list; }
}
public static void main(String [] args) {
AandBList<AandB> foo = new AandBList<AandB>(); // This works fine!
foo.getList().add(new AandB());
List<? extends A> bar = new LinkedList<AandB>(); // This is fine too
// This last one fails to compile!
List<? extends A & B> foobar = new LinkedList<AandB>();
}
}
似乎 bar
的语义应该被明确定义——我认为允许两种类型而不是一种类型的交集不会导致类型安全性的任何损失。但我确信有一个解释。有谁知道它是什么?
I know there's all sorts of counter-intuitive properties of Java's generic types. Here's one in particular that I don't understand, and which I'm hoping someone can explain to me. When specifying a type parameter for a class or interface, you can bound it so that it must implement multiple interfaces with public class Foo<T extends InterfaceA & InterfaceB>
. However, if you're instantiating an actual object, this doesn't work anymore. List<? extends InterfaceA>
is fine, but List<? extends InterfaceA & InterfaceB>
fails to compile. Consider the following complete snippet:
import java.util.List;
public class Test {
static interface A {
public int getSomething();
}
static interface B {
public int getSomethingElse();
}
static class AandB implements A, B {
public int getSomething() { return 1; }
public int getSomethingElse() { return 2; }
}
// Notice the multiple bounds here. This works.
static class AandBList<T extends A & B> {
List<T> list;
public List<T> getList() { return list; }
}
public static void main(String [] args) {
AandBList<AandB> foo = new AandBList<AandB>(); // This works fine!
foo.getList().add(new AandB());
List<? extends A> bar = new LinkedList<AandB>(); // This is fine too
// This last one fails to compile!
List<? extends A & B> foobar = new LinkedList<AandB>();
}
}
It seems the semantics of bar
should be well-defined -- I can't think of any loss of type-safety by allowing an intersection of two types rather than just one. I'm sure there's an explanation though. Does anyone know what it is?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
有趣的是,接口 java.lang.reflect.WildcardType 看起来支持通配符参数的上限和下限;每个边界都可以包含多个边界,
这远远超出了语言所允许的范围。源代码中有一条隐藏注释,
界面的作者似乎认为这是一个意外的限制。
对你的问题的预设答案是,泛型已经太复杂了;增加更多的复杂性可能会成为最后一根稻草。
为了允许通配符具有多个上限,必须扫描规范并确保整个系统仍然有效。
我知道一个麻烦在于类型推断。当前的推理规则根本无法处理交集类型。没有规则可以减少约束
A&B << C。如果我们将其简化为
任何当前的推理引擎,则必须经过重大检修才能允许这种分叉。但真正严重的问题是,这允许多种解决方案,但没有理由优先选择其中一种。
然而,推理对于类型安全来说并不是必需的;在这种情况下,我们可以简单地拒绝推断,并要求程序员显式填写类型参数。因此,推理困难并不是反对交集类型的有力论据。
Interestingly, interface
java.lang.reflect.WildcardType
looks like it supports both upper bounds and lower bounds for a wildcard arg; and each can contain multiple boundsThis is way beyond what the language allows. There's a hidden comment in the source code
The author of the interface seems to consider that this is an accidental limitation.
The canned answer to your question is, generics is already too complicated as it is; adding more complexity might prove to be the last straw.
To allow a wildcard to have multiple upper bounds, one has to scan through the spec and make sure the entire system still works.
One trouble I know would be in the type inference. The current inference rules simply can't deal with intersection types. There's no rule to reduce a constraint
A&B << C
. If we reduced it toany current inference engine has to go through major overhaul to allow such bifurcation. But the real serious problem is, this allows multiple solutions, but there's no justification to prefer one over another.
However, inference is not essential to type safety; we can simply refuse to infer in this case, and ask programmer to explicitly fill in type arguments. Therefore, difficulty in inference is not a strong argument against intercection types.
来自 Java 语言规范:
那么为什么不支持呢?我的猜测是,遇到这样的事情你该怎么办? - 假设这是可能的:
那么应该返回什么
?没有语法可以捕获
A & 的返回值。 B。在这样的列表中添加一些东西也是不可能的,所以它基本上是无用的。
From the Java Language Specification:
So why is this not supported? My guess is, what should you do with such a thing? - let's suppose it were possible:
Then what should
return? There's no syntax to capture a return value of
A & B
. Adding something into such a list would not be possible either, so it's basically useless.没问题...只需在方法签名中声明您需要的类型即可。
这编译:
No problem... just declare the type you need in the method signature.
This compiles:
好问题。我花了一段时间才弄清楚。
让我们简化你的情况:你试图做同样的事情,就好像你声明一个扩展 2 个接口的类,然后声明一个具有这 2 个接口类型的变量,如下所示:
当然,非法。
这相当于您尝试使用泛型所做的事情。
你想要做的是:
但是,要使用 foobar,你需要使用两个接口的变量,这样:
这在 Java 中不合法。这意味着,您同时将列表的元素声明为两种类型,即使我们的大脑可以处理它,Java 语言也不能。
Good question. It took me a while to figure out.
Lets simplify your case: You are trying to do the same as if you declare a class that extends 2 interfaces, and then a variable that has as a type those 2 interfaces, something like this:
Of course, illegal.
And this is equivalent to what you try to do with generics.
What you are trying to do is:
But then, to use foobar, you would need to use a variable of both interfaces this way:
Which is not legal in Java. This means, you are declaring the elements of the list as beeing of 2 types simultaneously, and even if our brains can deal with it, Java language cannot.
值得一提的是:如果有人想知道这一点,因为他们真的想在实践中使用它,我已经通过定义一个接口来解决这个问题,该接口包含我正在使用的所有接口和类中的所有方法的并集。即我试图执行以下操作:
这是非法的 - 所以我这样做了:
这仍然不如能够输入
List
,例如,如果有人向 A 或 B 添加或删除方法,则列表的类型不会自动更新,它需要手动更改 C。但它满足了我的需求。For what it's worth: if anyone's wondering this because they would truly like to use this in practice, I've worked around it by defining an interface that contains the union of all methods in all the interfaces and class that I'm working with. i.e. I was trying to do the following:
which is illegal - so instead I did this:
This still isn't as useful as being able to type something as
List<? extends A implements B>
, e.g. if someone adds or removes methods to A or B, the typing of the list will not be updated automatically, it requires a manual change to C. But it's worked for my needs.