Java:通用类型转换中的(缺少)错误

时间:2018-05-07 22:59:26

标签: java generics error-handling casting

我无法弄清楚为什么我的Java代码中没有错误。我有一个使用泛型类型的类:

import java.util.*;  // For ArrayList
public class Hat<T>
{
  public ArrayList<T> convert(String s)
  {
    T t = (T) s;    // Cast happens here

    ArrayList<T> list = new ArrayList<T>();
    list.add(t);
    return list;
  }
}

然后,我执行一些我认为应该创建错误的代码:

Hat<Integer> h = new Hat<Integer>();
ArrayList<Integer> iList = h.convert("hello");

这样做会创建一个整数的ArrayList,它以某种方式将String作为元素!这不会在运行时抛出任何错误,即使您打印ArrayList(它打印&#34; [hello]&#34;)。

我希望从&#34;转换&#34;中抛出错误。方法。为什么这不会发生,是否有可能实现?有趣的是,当我尝试将元素从ArrayList作为Integer返回时,会发生这种情况,但错误并非来自&#34; convert&#34;方法

1 个答案:

答案 0 :(得分:3)

在Java中,泛型仅在编译时使用;在类型检查器验证程序并且对程序的执行没有影响之后,它们被“擦除”。特别是,在运行时,ArrayList<Integer>ArrayList<String>(或其他任何事件的ArrayList之间没有区别。完成类型检查后,程序将被删除,执行的程序相当于:

public class Hat
{
  public ArrayList convert(String s)
  {
    Object t = s;
    ArrayList list = new ArrayList();
    list.add(t);
    return list;
  }
}

Hat h = new Hat();
ArrayList iList = h.convert("hello");

的行为与你观察到的一样。

所以问题是,为什么这个程序在显然产生一个声称是ArrayList<Integer>但包含字符串的错误值时会进行类型检查?类型系统不应该拒绝这样的程序吗?

嗯,确实如此,除了有一个很大的漏洞:未经检查的演员阵容。当您对涉及泛型的类型执行强制转换时 - 在您的情况下,行T t = (T) s; - Java在运行时没有任何内容可用于测试强制转换是否有效,因为擦除。 Java设计者可能刚刚禁止这种转换,在这种情况下你的程序将无法编译。

但是,他们并没有这样做。相反,他们选择允许涉及泛型的演员表,并相信编写演员的程序员比编译器更聪明,并且知道演员阵容会成功。但是,如果您使用其中一个强制转换,则所有投注均已关闭,并且类型系统最终可能会使用实际包含字符串的ArrayList<Integer>结束。因此,为了警告您需要小心,他们有编译器 但是每当你写这样一个演员时发出一个“未经检查的演员”警告,提醒你有一个可疑的演员,你应该证明这是正确的。在我已经使用的代码库中,未经检查的强制转换需要使用@SuppressWarning注释,并且注释描述为什么强制转换始终有效。

那么,如果你希望处理未经检查的强制转换并且你宁愿发出运行时检查呢?在这种情况下,您将不得不自己编写运行时检查。您通常可以使用Class个对象执行此操作。在您的情况下,您可以在Class构造函数中添加额外的Hat参数,以表示您期望T的类,并使用它来进行在运行时检查的类型安全转换:

public class Hat<T>
{
  private final Class<? extends T> expectedClass;

  public Hat(Class<? extends T> expectedClass)
  {
    this.expectedClass = expectedClass;
  }

  public ArrayList<T> convert(String s)
  {
    T t = expectedClass.cast(s);  // This cast will fail at runtime if T isn't String

    ArrayList<T> list = new ArrayList<T>();
    list.add(t);
    return list;
  }
}

然后您的呼叫站点需要更改为:

Hat<Integer> h = new Hat<Integer>(Integer.class);
ArrayList<Integer> iList = h.convert("hello");   // throws