【问题标题】:Java - Generics - Explicit casting and cast method of Class "Class<?>"Java - 泛型 - 类“Class<?>”的显式转换和转换方法
【发布时间】:2014-05-26 04:37:46
【问题描述】:

为什么在Class&lt;?&gt; 类上使用cast 方法会在编译时产生未经检查的警告?

如果您查看 cast 方法,您会发现以下代码:

public T cast(Object obj) 
{
    if (obj != null && !isInstance(obj))
        throw new ClassCastException(cannotCastMsg(obj));
    return (T) obj; // you can see there is a generic cast performed here
}

如果我进行泛型转换,编译器会抱怨说有unchecked warning


其他背景信息

您可以在 Book Effective Java 2 版第 166 页(pdf 的)中找到我如何解决这个问题的示例。

作者写了这段代码

public <T> T getFavorite(Class<T> type) 
{
    return type.cast(favorites.get(type));
}

public <T> T getFavorite(Class<T> type) 
{
    return (T) (favorites.get(type));
}

我只是不明白为什么编译器会抱怨未经检查的警告。最后,这两段代码都进行了显式转换 (T) object,不是吗?

【问题讨论】:

  • 这不是您要投射的课程,而是Object...
  • 对不起,我不明白。请考虑重新阅读问题。我添加了额外的背景。谢谢!
  • 在第一个代码sn-p中,方法的参数是Object;您需要在返回之前将其转换为正确的类型。
  • 好的,但是...我还是不明白 :(。对不起。

标签: java generics casting effective-java


【解决方案1】:

如果没有@SuppressWarnings("unchecked") 注释,java.lang.Class 的源代码会在编译期间产生警告。不会产生警告,因为 JDK 类位于已编译的库中。我确信自己编译 JDK 会产生一些警告。

@Bohemian 的评论说这是一个“官方的组合”基本上是正确的,并且有很多这样的代码示例。 (另一个例子是java.lang.Enum#getDeclaringClass。)它使用起来很安全,因为它所写的逻辑是正确的并且存在,所以你不必自己写这种丑陋的东西。

我的建议是不要过多考虑这个实现:重要的是 java.lang.Class#cast 符合检查强制转换的语义。

【讨论】:

  • 啊,当你说@SuppressWarnings 注释在cast() 上不存在时,我也很怀疑。虽然不确定,但无论如何,你比我更正确。
  • 那么,有隐式抑制警告吗? @user3580294
  • @Victor 不,不会生成警告,因为您的编译器从不编译源代码。我的回答有误,这就是我删除它的原因。
  • 我明白了。虽然很棘手..非常。所以我会把这个作为正确答案吗? (这里太啰嗦了,我要睡觉了,明天我再做决定!)谢谢!!!!
  • @Victor 据我所知,这是正确的。为了尝试提供一个不好的类比,这就像编写一本百科全书,其措辞有些可疑(例如,包含未经检查的演员表)。如果你给你的编辑器(编译器),草稿(源代码),你的编辑器(编译器)可能会在将草稿(代码)发送到打印机之前向你抱怨可能有问题(发出警告),这会产生最终产品(字节码)。但是如果给打印机提供了直接包含的成品(字节码)(import ____),则永远不会涉及编辑器(编译器),因此不会生成警告。
【解决方案2】:

为了强制执行内存安全,Java 确保引用类型的变量实际上包含对该类型(或其子类型)对象的引用。强制转换指令可能会违反这个不变量,即我们可以这样写:

Object o = new Integer(42);
String s = (String) o;  // compiles, but throws ClassCastException at runtime

为了防止这种情况,强制转换指令将检查所引用对象的类型,如果不是,则抛出 ClassCastException。

在将泛型引入 Java 语言之前,上述内容适用于所有类型转换。但是,使用类型擦除实现的泛型,运行时不知道类型参数代表哪个类,因此如果涉及类型参数,则无法执行此检查。

这就是为什么规范区分检查强制转换(只有在类型正确时才会在运行时成功)和未检查强制转换(即使类型不正确也可能成功,导致堆污染,并且可能是类型稍后出错)。例如:

class C<T> {
    final T field;

    C(Object o) {
        this.field = (T) o; // unchecked. Will never throw a ClassCastException.
    }
}

boolean test() {
    C<String> c = new C<String>(42);
    return c.field.startsWith("hello"); // throws ClassCastException, even though there is no cast in the source code at this line!
}

也就是说,未经检查的强制转换可能是不安全的,应该避免。

在这个背景下,很容易看到反射强制转换(Class.cast() 方法)被检查了,因为它们实际上在方法本身中实现了检查:

public T cast(Object obj) 
{
    if (obj != null && !isInstance(obj))
        throw new ClassCastException(cannotCastMsg(obj));
    return (T) obj;
}

当投射涉及类型参数时,这种安全网就是为什么反射投射比普通投射更受欢迎的原因。

【讨论】:

  • 我同意 Class.cast() 方法在运行时执行检查,因为您在运行时传递有关要转换的所需类的信息。但是我有两个观察结果,第一个代码 sn-p 没有编译,因为 String s = o 是一个缩小转换,因此 o 引用的东西不是 100% 与字符串兼容的对象。大约第二次剪断 classCastException 发生在 MagicCast 调用中,这就是 jvm 在运行时所说的 java.lang.Integer cannot be cast to java.lang.String
  • 关于第一个示例,我已修复它。第二个例子确实抛出了,但是如果你查看堆栈跟踪,不是在 magicCast 中,而是在调用者中。我修改了第二个示例以表明调用者可能不会立即检测到堆污染。
  • 对,我明白了,这是一个很好的解释,你把它留在这里。但最后,当“编译器”看到 ''return (T) obj;'' 我们都认为它必须按照 Java lenguaje 规范抱怨。
  • 好吧,您问为什么使用 cast 方法不会导致编译错误。如果您想知道强制转换方法可以在没有警告的情况下编译,您应该问一个不同的问题。
  • 感谢指正!关于这个问题,我想,一件事导致另一件事。我个人认为这两个答案都是正确的,并且可以指导有效的答案。我也会把它标记为正确的,但是 stackoverlow 的残酷积分系统不允许我这样做。就个人而言,我非常感谢您的意图,以及所有其他愿意合作回答的用户。
猜你喜欢
  • 2017-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-24
  • 1970-01-01
相关资源
最近更新 更多