Citron__
2018-06-16 08:51
采纳率: 33.3%
浏览 1.2k
已结题

有关可变参数与泛型结合,泛型数组的问题。

可能不应该在这种问题上死磕,但是还是想不明白:
代码来自《Effective Java》 第三版,32章
贴代码:

static <T> T[] toArray(T... args) {
    return args;
}

static <T> T[] pickTwo(T a, T b, T c) {
    switch(ThreadLocalRandom.current().nextInt(3)) {
      case 0: return toArray(a, b);
      case 1: return toArray(a, c);
      case 2: return toArray(b, c);
    }
    throw new AssertionError(); // Can't get here
}

public static void main(String[] args) {
    String[] attributes = pickTwo("Good", "Fast", "Cheap");
}

程序能够顺利通过编译,但是运行会报错。
报错原因为:pickTwo返回的类型为Objcet[]类型,因此无法强制转换为String[] 类型。

但是:

Objcet[] obj=new String[1]();
String[] str=(String[])obj;

是能正确运行的。

按照擦除和编译的语法糖,代码应该被编译成这样:

public static void main(String[] args) {
    String[] attributes = pickTwo("Good", "Fast", "Cheap");
}

static  String[] pickTwo(Object a, Object b, Object c) {
    switch(ThreadLocalRandom.current().nextInt(3)) {
      case 0: return (String[])toArray(a, b);
      case 1: return (String[])toArray(a, c);
      case 2: return (String[])toArray(b, c);
    }
    throw new AssertionError(); // Can't get here
}

static  String[] toArray(Object... args) {
    return (String[])args;
}

这也是泛型出现的意义。
可是
static <T> T[] pickTwo(T a, T b, T c) {
pickTwo明明根据传入类型告诉了编译器,需要返回T[]类型。
因此传入String类型的代码不应该就返回String[]
可是书上和运行结果确实证明了pickTwo方法确实始终返回了Objcet类型。

这是因为泛型数组的特殊性么?是在想不通,望指点

  • 写回答
  • 好问题 提建议
  • 追加酬金
  • 关注问题
  • 收藏
  • 邀请回答

2条回答 默认 最新

  • zzngb 2018-06-16 09:02

    因为java是不允许创建泛型数组的啊。。。先假设Java可以创建泛型数组,由于java泛型的类型擦除和数组的协变。下面的代码将会编译通过。

    List[] stringLists=new List[1];

    List intList = Arrays.asList(40);

    Object[] objects = stringLists;

    objects[0]=intList;

    String s=stringLists[0].get(0);

     由于泛型的类型擦除,List<Integer>,List<String>与List在运行期并没有区别,所以List<String>放入List<Integer>并不会产生ArrayStoreException异常。但是String s=stringLists[0].get(0);将会抛出ClassCastException异常。如果允许创建泛型数组,就绕过了泛型的编译时的类型检查,将List<Integer>放入List<String>[],并在实际存的是Integer的对象转为String时抛出异常。
    
    如果泛型没有限制类型。比如List s=new ArrayList[5];或者List<?>[] s=new ArrayList[5];还是可以使用的,应为没有了编译时的类型检查,需要开发者自己保证类型转换安全。
    
    评论
    解决 无用
    打赏 举报
  • threenewbee 2018-06-16 14:37
     你可以把String[]想象成Array<String>,而Object[]想象成Array<Object>,虽然String是Object的派生类,但是Array<String>不是Array<Object>的派生类。
    然后你可能要问,为什么Java不设计成Array<String>是Array<Object>的派生类(所谓的协变),这是出于性能的考虑。如果要这么设计,那么Java不得不放弃为了性能而使用的类型擦除机制了,因为程序需要在运行时允许做类型转换,这样整个jvm就推翻了。不过不要以为这么做不合理,C#就可以使现有条件的协变(接口),而动态语言更不在话下了。
    
    评论
    解决 无用
    打赏 举报

相关推荐 更多相似问题