抽象类中的Java varargs和泛型问题

 -起司Cheese- 发布于 2023-02-12 18:40

我正在玩一些像编程一样的功能.并且遇到了一些非常深层嵌套的泛型问题.这是我的SCCE失败,涉及一个抽象类:

public abstract class FooGen {

    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 foogen = new FooGen() {   
         @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 {

      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 foogen = new FooGen();
      System.out.println(foogen.fn2(1.2, 3.4)); 
   }
}

这打印1.2.想法?似乎某些地方Java已经失去了对泛型的追踪.这推动了我的泛型知识的极限.:-)

(在回答时添加)

首先,感谢你们的赞成,感谢Paul和Daemon提供了有用的答案.

仍然想知道为什么它在第二版中作为数字工作,我有一个洞察力.作为一个思想实验,让我们添加一个.doubleValue()地方. 你不能. 在代码本身中,变量是IN,而不是Numbers.而在main()它只是声明类型,FooGen但没有地方添加代码.

在版本#2中,它确实不像Numbers一样"工作" .在内部,通过擦除,一切都是对象,正如Paul和Daemon所解释的那样,并且回想起来,我自己很好理解.基本上,在这个复杂的例子中,我被声明过度兴奋和误导.

不要以为我会打扰一个解决方法.整个想法是懒惰的.:-)为了提高效率,我创建了并行接口和采用原始双精度(和整数)的代码,这个技巧就可以了.

1 个回答
  • 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,而是与是否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));
    }
    

    2023-02-12 18:43 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有