java - 抽象类中的 Java 可变参数和泛型问题

标签 java generics variadic-functions

我正在玩一些函数式编程。并且遇到一些嵌套很深的泛型的问题。这是我失败的 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 提供的有用答案。

仍然想知道为什么它在第 2 版中用作 Numbers,我有了一个见解。作为一个思想实验,让我们添加一个 .doubleValue()某处。 你不能。 在代码本身中,变量是 IN,而不是数字。而在 main()它只是声明类型,FooGen<Number,Number>但是那里没有地方可以添加代码。

在版本 #2 中,它确实不像数字那样“有效”。在内部,通过删除,一切都是对象,正如 Paul 和 Daemon 所解释的那样,并且不好意思地回头看,我自己也很理解。基本上,在这个复杂的例子中,我被 <Number> 过度兴奋和误导了。声明。

别以为我会为解决方法而烦恼。整个想法是懒惰。 :-) 为了提高效率,我创建了采用原始 double (和整数)的并行接口(interface)和代码,并且这个技巧工作得很好。

最佳答案

Varargs 参数首先是数组。因此,如果没有语法糖,您的代码将如下所示:

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

abstract OUT fnN(IN[] ins);

除了 new IN[]不合法因为arrays of type parameters cannot be instantiated , 由于 type erasure .一个数组需要知道它的组件类型,但是 IN已被删除到其上限,Object , 在运行时。

不幸的是,varargs 调用隐藏了这个问题,在运行时你有相当于 fnN(new Object[] {in1, in2}) 的东西,而 fnN已被重写为 Number[] .

However, for a non-abstract FooGen, it works fine

这是因为通过实例化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);

如果您想保留可变参数支持,您可以将此方法视为实现细节并委托(delegate)给它:

abstract OUT fnNImpl(List<? extends IN> ins);

public final OUT fnN(IN... ins) {
    return fnNImpl(Arrays.asList(ins));
}

关于java - 抽象类中的 Java 可变参数和泛型问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20389858/

相关文章:

c# - 将不同的泛型存储在单个可枚举中

c# - C# 4.0 中的协变和逆变推理

php - 如何在 PHP 中代理可变参数函数

swift - 如何从一组颜色和 float 创建 NSGradient?

java - 如何接受/查找来自 opc ua 服务器的证书?

java - 如何按某些属性对对象列表进行排序

java - HttpParams 弃用解决方案

java - 在选项卡式 Pane 标题上的标签和 jButton 之间添加空格

java - 为什么 Eclipse Java 编译器会提示内部派生类型的转换未经检查?

c++ - 非可变参数的 printf 风格记录器