我想让一个函数返回一个保证实现两个接口的对象。确切的对象在编译时不一定已知。我的代码看起来像这样:
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
是不同的类型。
有什么办法可以让上面的代码编译通过吗?
您对类型变量有一个根本性的误解。当你声明一个像
这样的方法时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
永远不会在任何地方声明为返回或参数类型。中所说的一切。但我想补充一件事,因为棘手的类型非常有趣。 我相信你会
喜欢做的是有一个返回类型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是一种非常有趣的编程语言,它支持一流的交集和联合类型。你可以用锡兰语写上面的例子。
看看,它真的很有趣!
interface MyGetter<R extends A> {
R get(MyArgs foo);
}
private MyGetter<? extends B> getter;
这是有效的,因为对于通配符,接口边界会自动添加到指定的边界中。这可以让你暗示不能用 java 写下来的界限,包括交集、循环、无限类型堆栈等。这是一个奇怪的功能。无论如何,
getter.get(args)
将返回一个匿名类型,它是A和B的子类型。