我正在玩一些像编程这样的功能。还有一些嵌套很深的泛型的问题。这是我的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,而不是数字。在main()中,它只是声明了FooGen<Number,Number>类型,但没有地方添加代码。

在版本2中,它确实不能像数字一样“工作”。正如Paul和Daemon所解释的那样,从内部看,一切都是对象,并且令人毛骨悚然地回首,我自己也很好理解。基本上,在这个复杂的示例中,我对<Number>声明感到兴奋和误导。

不要以为我会解决。整个想法是要懒惰。 :-)为了提高效率,我创建了并行接口和代码,它们采用了原始的double(和ints),在这里,这种技巧就很好了。

最佳答案

Varargs参数是第一个和最重要的数组。因此,如果没有语法糖,您的代码将如下所示:

OUT fn2(IN in1, IN in2) {
    return fnN(new IN[] {in1, in2});
}

abstract OUT fnN(IN[] ins);

由于arrays of type parameters cannot be instantiated,所以new IN[]除外,因为type erasure并不合法。数组需要知道其组件类型,但是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的抽象性无关,但与fnN是否被狭窄的参数类型覆盖无关。

解决方案

没有简单的解决方法。一种想法是让fnN改为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));
}

08-17 05:00