返回泛型类型的交集

问题描述 投票:0回答:3

我想让一个函数返回一个保证实现两个接口的对象。确切的对象在编译时不一定已知。我的代码看起来像这样:

class HelloWorld {

  public interface A {}
  public interface B {}

  public static class C implements A, B {}
  public static class D implements A, B {}

  public static <T extends A & B> void g(T t) {}

  public static <T extends A & B> T f(boolean b) {
    if (b)
      return new C(); // Doesn't compile
    return new D(); // Doesn't compile
  }

  public static void main(String []args){
    g(f(true));
    g(f(false));
    <what_should_I_write_here> x = f(<user_inputted_boolean>); 
  }
}

尝试编译时出现以下错误:

HelloWorld.java:13:错误:类型不兼容:C 无法转换为 T
返回新的 C();
^
其中 T 是类型变量:
T 扩展了方法 f(boolean) 中声明的 A,B
HelloWorld.java:14:错误:不兼容的类型:D 无法转换为 T
返回新的 D();
^
其中 T 是类型变量:
T 扩展了方法 f(boolean) 中声明的 A,B

这不起作用,因为您无法从函数返回两种不同的类型,并且

C
D
是不同的类型。

有什么办法可以让上面的代码编译通过吗?

java generics java-7
3个回答
5
投票

您对类型变量有一个根本性的误解。当你声明一个像

这样的方法时
public static <T extends A & B> T f(boolean b) { … }

您声明一个类型变量

T
调用者可以为其分配实际类型(或调用者上下文的类型变量)。例如,呼叫者可以执行以下操作:

class SomethingCompletelyUnknownToF implements A,B {}

SomethingCompletelyUnknownToF var = f(trueOrFalse);
编译器会接受,因为

SomethingCompletelyUnknownToF

 的调用者用于 
T
 的类型 
f
 满足该类型必须实现 
A
B
 的约束。当然,这在运行时会失败,因为 
C
D
 都不能分配给 
SomethingCompletelyUnknownToF
。事实上,
f
不可能满足返回一个它甚至不知道的特定类型的期望。

总而言之,类型变量不是方法可以分配类型的变量,类型变量是调用者

选择的类型的占位符。 所以方法签名

public static <T extends A & B> void g(T t) { … }

更有意义,因为无论调用者为 
T

选择什么实际类型,当作为参数传递时,它将满足方法实现

A
B
的期望。当然,
g
不能期望它是
D
C
,因为它可能是实现
A
B
的完全未知的类型。

也就是说,Java 中没有办法表达返回类型扩展两种类型(除了声明扩展这两种类型的具体类型之外)。如果出现

RandomAccess

,无论如何也无需费心,因为它没有任何后果。请注意,JRE 类也从不声明返回的

List
何时保证实现此标记接口。这也可以通过从不期望它作为参数类型来实现。同样,
Serializable
永远不会在任何地方声明为返回或参数类型。
    


1
投票
他们的回答

中所说的一切。但我想补充一件事,因为棘手的类型非常有趣。 我相信你会

喜欢

做的是有一个返回类型f

,像这样:

public static A & B f(boolean b)

然后

f

 将返回一些同时实现 
A
B
 的对象。这将使方法类型的返回语句进行检查,并且您可以将其返回值分配给 
A
B
 变量。 

但是这当然是不允许的,Java中的交集类型只允许在类型变量上。

你可以试试这个:

public static Optional<? extends A & B> f(boolean b)

这里交集类型是可以的,因为它实例化了

Optional

 的类型参数。但这是不允许的,因为通配符只能有一个界限,
&
不能使用。


Ceylon是一种非常有趣的编程语言,它支持一流的交集和联合类型。你可以用锡兰语写上面的例子。

看看,它

真的很有趣!


0
投票
有一个鲜为人知的功能,称为隐式边界,它允许使用迂回编码:

interface MyGetter<R extends A> { R get(MyArgs foo); } private MyGetter<? extends B> getter;
这是有效的,因为对于通配符,接口边界会自动添加到指定的边界中。这可以让你暗示不能用 java 写下来的界限,包括交集、循环、无限类型堆栈等。这是一个奇怪的功能。

无论如何,

getter.get(args)

将返回一个匿名类型,它是A和B的子类型。

© www.soinside.com 2019 - 2024. All rights reserved.