[英]Issue with Java varargs and generics in abstract classes
我正在玩一些像编程一样的功能。 和一些非常深层嵌套的泛型有问题。 这是我的SCCE失败,涉及一个抽象类:
public abstract class FooGen<IN, OUT> {
OUT fn2(IN in1, IN in2) { // clever? try at a lazy way, just call the varargs version
return fnN(in1, in2);
}
abstract OUT fnN(IN...ins); // subclasses implement this
public static void main(String[] args) {
FooGen<Number, Number> foogen = new FooGen<Number, Number>() {
@Override Number fnN(Number... numbers) {
return numbers[0];
}
};
System.out.println(foogen.fn2(1.2, 3.4));
}
}
这死了
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Number;
但是,对于非抽象的 FooGen,它工作正常:
public class FooGen<IN, OUT> {
OUT fn2(IN g1, IN g2) {
return fnN(g1, g2);
}
OUT fnN(IN...gs) {
return (OUT)gs[0];
}
public static void main(String[] args) {
FooGen<Number,Number> foogen = new FooGen<Number,Number>();
System.out.println(foogen.fn2(1.2, 3.4));
}
}
这打印1.2。 想法? 似乎某些地方Java已经失去了对泛型的追踪。 这推动了我的泛型知识的极限。 :-)
(在回答时添加)
首先,感谢你们的赞成,感谢Paul和Daemon提供了有用的答案。
仍然想知道为什么它在第二版中作为数字工作,我有一个洞察力。 作为思想实验,让我们在某处添加一个.doubleValue()
。 你不能。 在代码本身中,变量是IN,而不是Numbers。 而在main()
它只是声明类型, FooGen<Number,Number>
但是没有地方可以添加代码。
在版本#2中,它确实不像Numbers一样“工作”。 在内部,通过擦除,一切都是对象,正如Paul和Daemon所解释的那样,并且回想起来,我自己很好理解。 基本上,在这个复杂的例子中,我被<Number>
声明过度兴奋和误导。
不要以为我会打扰一个解决方法。 整个想法是懒惰的。 :-)为了提高效率,我创建了并行接口和采用原始双精度(和整数)的代码,这个技巧就可以了。
Varargs参数首先是最重要的数组。 因此,如果没有语法糖,您的代码将如下所示:
OUT fn2(IN in1, IN in2) {
return fnN(new IN[] {in1, in2});
}
abstract OUT fnN(IN[] ins);
除了new IN[]
之外不合法,因为类型参数的数组由于类型擦除而无法实例化 。 数组需要知道其组件类型,但IN
在运行时已被擦除到其上限Object
。
不幸的是,varargs调用隐藏了这个问题,并且在运行时你有相当于fnN(new Object[] {in1, in2})
,而fnN
已被覆盖以取一个Number[]
。
但是,对于非抽象的 FooGen,它工作正常
这是因为通过直接实例化FooGen
,您还没有覆盖fnN
。 因此它在运行时接受Object[]
并且不发生ClassCastException
。
例如,即使FooGen
不是abstract
,这也会失败:
FooGen<Number, Number> foogen = new FooGen<Number, Number>() {
@Override
Number fnN(Number... gs) {
return super.fnN(gs);
}
};
System.out.println(foogen.fn2(1.2, 3.4));
所以你可以看到它确实与FooGen
的抽象性FooGen
,而是与fnN
是否被缩小的参数类型重写。
解
没有简单的解决方法。 一个想法是让fnN
拿一个List<? extends IN>
List<? extends IN>
代替:
OUT fn2(IN in1, IN in2) {
//safe because the array won't be exposed outside the list
@SuppressWarnings("unchecked")
final List<IN> ins = Arrays.asList(in1, in2);
return fnN(ins);
}
abstract OUT fnN(List<? extends IN> ins);
如果您想保持varargs支持,可以将此方法视为实现细节并委托给它:
abstract OUT fnNImpl(List<? extends IN> ins);
public final OUT fnN(IN... ins) {
return fnNImpl(Arrays.asList(ins));
}
由于Java的一个名为“类型擦除”的特性,会发生此ClassCastException
。 编译泛型时会发生类型擦除。 由于Java编译器在运行时无法知道泛型类的类型,因此它将编译通用对象作为Object
实例。
在你的代码中,当FooGen
编译, fnN(IN... ins)
接收类型的参数Object[]
当您尝试将其中一个对象向下转换为通用类型OUT
时,会发生ClassCastException
。
这甚至没有提到在Java中禁止创建这种“通用数组”的事实。
以下是Angelika Langer的Java Generics FAQ的引用:
下面是另一个示例,说明了与可变参数列表一起忽略有关数组构造的警告的潜在危险。
Example (of a varargs method and its invocation):
public final class Test {
static <T> T[] method_1(T t1, T t2) {
return method_2(t1, t2); // unchecked warning
}
static <T> T[] method_2( T... args) {
return args;
}
public static void main(String... args) {
String[] strings = method_1("bad", "karma"); // ClassCastException
}
}
warning: [unchecked] unchecked generic array creation of type T[] for
varargs parameter
return method_2(t1, t2);
^
在此示例中,第一个方法调用第二个方法,第二个方法采用可变参数列表。 为了调用varargs方法,编译器会创建一个数组并将其传递给该方法。 在此示例中,要创建的数组是T []类型的数组,即组件类型为类型参数的数组。 Java中禁止创建此类数组,如果您尝试自己创建此类数组,则会收到错误消息。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.