为什么在一个有界通配符中不能有多个接口?

时间:2022-09-25 12:15:30

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:

我知道Java的泛型类型有各种各样的反直觉属性。这里有一个我不明白的,我希望有人能解释给我听。当为类或接口指定类型参数时,您可以将其绑定,以便它必须实现与public类Foo 。但是,如果您正在实例化一个实际的对象,那么它就不再工作了。列表< ?扩展InterfaceA>很好,但是List 无法编译。考虑以下完整的片段: 的多个接口,并扩展interfacea>

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?

看起来,bar的语义应该被定义得很好——我不能通过允许两种类型的交集而不是只有一个类型的交集来考虑类型安全的损失。我相信这是有原因的。有人知道是什么吗?

5 个解决方案

#1


32  

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 bounds

有趣的是,接口数组。通配符类型看起来支持通配符arg的上界和下界;每个都可以包含多个边界。

Type[] getUpperBounds();
Type[] getLowerBounds();

This is way beyond what the language allows. There's a hidden comment in the source code

这远远超出了语言允许的范围。在源代码中有一个隐藏的注释。

// one or many? Up to language spec; currently only one, but this API
// allows for generalization.

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.

为了允许通配符具有多个上界,我们必须扫描spec并确保整个系统仍然有效。

One trouble I know would be in the type inference. The current inference rules simply can't deal with intercection types. There's no rule to reduce a constraint A&B << C. If we reduced it to

我知道的一个问题是类型推断。当前的推理规则根本不能处理插入类型。没有规则可以减少约束A&B << c,如果我们将其还原为。

    A<<C 
  or
    A<<B

any 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.

然而,推理对于类型安全并不重要;在这种情况下,我们可以简单地拒绝推断,并要求程序员显式地填充类型参数。因此,推理的困难并不是针对不同类型的有力论证。

#2


21  

From the Java Language Specification:

从Java语言规范:

4.9 Intersection Types An intersection type takes the form T1 & ... & Tn, n>0, where Ti, 1in, are type expressions. Intersection types arise in the processes of capture conversion (§5.1.10) and type inference (§15.12.2.7). It is not possible to write an intersection type directly as part of a program; no syntax supports this. The values of an intersection type are those objects that are values of all of the types Ti, for 1in.

4.9交叉口类型的交叉式采用T1 &…Tn, n>0,其中Ti, 1in,是类型表达式。十字路口类型出现在捕获转换的过程(§5.1.10)和类型推断(§15.12.2.7)。不可能直接编写一个交集类型作为程序的一部分;不支持这种语法。交集类型的值是所有类型Ti的值,1in。

So why is this not supported? My guess is, what should you do with such a thing? - let's suppose it were possible:

为什么不支持这个呢?我的猜测是,你应该怎么处理这样的事情?-让我们假设这是可能的:

List<? extends A & B> list = ...

Then what should

那么应该

list.get(0);

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.

回报呢?没有语法来获取a和b的返回值,所以在这样的列表中添加一些东西是不可能的,所以它基本上是无用的。

#3


8  

No problem... just declare the type you need in the method signature.

没有问题…只需在方法签名中声明需要的类型。

This compiles:

这个编译:

public static <T extends A & B> void main(String[] args) throws Exception
{
    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
    List<T> foobar = new LinkedList<T>(); // This compiles!
}

#4


1  

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:

让我们简化一下您的例子:您正在尝试做与声明一个扩展了2个接口的类,然后是一个具有两个接口类型的变量,像这样:

  class MyClass implements Int1, Int2 { }

  Int1 & Int2 variable = new MyClass()

Of course, illegal. And this is equivalent to what you try to do with generics. What you are trying to do is:

当然,非法的。这等价于你用泛型来做的事情。你要做的是:

  List<? extends A & B> foobar;

But then, to use foobar, you would need to use a variable of both interfaces this way:

但是,要使用foobar,需要使用两个接口的变量:

  A & B element = foobar.get(0);

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.

这在Java中是不合法的。这意味着,您将同时声明列表的元素为2种类型,即使我们的大脑能够处理它,Java语言也不能。

#5


0  

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:

对于它的价值:如果有人想知道这一点,因为他们确实想在实践中使用它,我通过定义一个接口来解决它,它包含了所有接口和类中所有方法的联合。我想做的是:

class A {}

interface B {}

List<? extends A & B> list;

which is illegal - so instead I did this:

这是非法的,所以我这样做了:

class A {
  <A methods>
}

interface B {
  <B methods>
}

interface C {
  <A methods>
  <B methods>
}

List<C> list;

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.

这仍然不如能够键入列表 ,例如,如果有人向A或B添加或删除方法,列表的类型将不会自动更新,它需要手动更改c,但它已经满足了我的需要。

#1


32  

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 bounds

有趣的是,接口数组。通配符类型看起来支持通配符arg的上界和下界;每个都可以包含多个边界。

Type[] getUpperBounds();
Type[] getLowerBounds();

This is way beyond what the language allows. There's a hidden comment in the source code

这远远超出了语言允许的范围。在源代码中有一个隐藏的注释。

// one or many? Up to language spec; currently only one, but this API
// allows for generalization.

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.

为了允许通配符具有多个上界,我们必须扫描spec并确保整个系统仍然有效。

One trouble I know would be in the type inference. The current inference rules simply can't deal with intercection types. There's no rule to reduce a constraint A&B << C. If we reduced it to

我知道的一个问题是类型推断。当前的推理规则根本不能处理插入类型。没有规则可以减少约束A&B << c,如果我们将其还原为。

    A<<C 
  or
    A<<B

any 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.

然而,推理对于类型安全并不重要;在这种情况下,我们可以简单地拒绝推断,并要求程序员显式地填充类型参数。因此,推理的困难并不是针对不同类型的有力论证。

#2


21  

From the Java Language Specification:

从Java语言规范:

4.9 Intersection Types An intersection type takes the form T1 & ... & Tn, n>0, where Ti, 1in, are type expressions. Intersection types arise in the processes of capture conversion (§5.1.10) and type inference (§15.12.2.7). It is not possible to write an intersection type directly as part of a program; no syntax supports this. The values of an intersection type are those objects that are values of all of the types Ti, for 1in.

4.9交叉口类型的交叉式采用T1 &…Tn, n>0,其中Ti, 1in,是类型表达式。十字路口类型出现在捕获转换的过程(§5.1.10)和类型推断(§15.12.2.7)。不可能直接编写一个交集类型作为程序的一部分;不支持这种语法。交集类型的值是所有类型Ti的值,1in。

So why is this not supported? My guess is, what should you do with such a thing? - let's suppose it were possible:

为什么不支持这个呢?我的猜测是,你应该怎么处理这样的事情?-让我们假设这是可能的:

List<? extends A & B> list = ...

Then what should

那么应该

list.get(0);

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.

回报呢?没有语法来获取a和b的返回值,所以在这样的列表中添加一些东西是不可能的,所以它基本上是无用的。

#3


8  

No problem... just declare the type you need in the method signature.

没有问题…只需在方法签名中声明需要的类型。

This compiles:

这个编译:

public static <T extends A & B> void main(String[] args) throws Exception
{
    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
    List<T> foobar = new LinkedList<T>(); // This compiles!
}

#4


1  

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:

让我们简化一下您的例子:您正在尝试做与声明一个扩展了2个接口的类,然后是一个具有两个接口类型的变量,像这样:

  class MyClass implements Int1, Int2 { }

  Int1 & Int2 variable = new MyClass()

Of course, illegal. And this is equivalent to what you try to do with generics. What you are trying to do is:

当然,非法的。这等价于你用泛型来做的事情。你要做的是:

  List<? extends A & B> foobar;

But then, to use foobar, you would need to use a variable of both interfaces this way:

但是,要使用foobar,需要使用两个接口的变量:

  A & B element = foobar.get(0);

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.

这在Java中是不合法的。这意味着,您将同时声明列表的元素为2种类型,即使我们的大脑能够处理它,Java语言也不能。

#5


0  

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:

对于它的价值:如果有人想知道这一点,因为他们确实想在实践中使用它,我通过定义一个接口来解决它,它包含了所有接口和类中所有方法的联合。我想做的是:

class A {}

interface B {}

List<? extends A & B> list;

which is illegal - so instead I did this:

这是非法的,所以我这样做了:

class A {
  <A methods>
}

interface B {
  <B methods>
}

interface C {
  <A methods>
  <B methods>
}

List<C> list;

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.

这仍然不如能够键入列表 ,例如,如果有人向A或B添加或删除方法,列表的类型将不会自动更新,它需要手动更改c,但它已经满足了我的需要。