【问题标题】:Why not simply disable unchecked warnings?为什么不简单地禁用未经检查的警告?
【发布时间】:2013-01-20 08:43:33
【问题描述】:

当开发人员与非通用 API 交互时,他们通常会遇到“未经检查”的警告。考虑以下示例:

import java.util.AbstractList;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class IterableNodeList<T extends Node> extends AbstractList<T>
{
    private NodeList list;

    public IterableNodeList(NodeList list)
    {
        this.list = list;
    }

    public T get(int index)
    {
        return (T)this.list.item(index);
    }

    public int size()
    {
        return this.list.getLength();
    }
}

当然可以投入精力以没有警告的方式编写此代码:在类上使用类型参数 T 和构造函数参数 Class&lt;T&gt;,匹配成员变量和 cast() 调用.

或者,可以考虑简单地编辑 IDE 配置和构建脚本(例如 Maven POM)以完全禁用此编译器警告。现在,如果我们这样做,代码可以保持原样,但我确信这样做肯定有缺点。但是,我想不出任何合理、现实的例子

  • 此警告提供的价值比“坚持@SuppressWarnings,反正没有其他选择”以及在哪里
  • 生成的代码实际上与我们忽略(禁用)警告的代码不同(而且更安全)。

您能想出这样的例子或说出另一个原因,为什么全局禁用这些“未经检查的”警告是一个坏主意?或者这实际上是个好主意?

更新

前面的例子实际上并没有引起警告。有些答案现在不再有意义。很抱歉给您带来不便。

【问题讨论】:

    标签: java generics unchecked


    【解决方案1】:

    根据Effective Java 2nd EditionItem 24,广泛而频繁地使用@SupressWarnings 通常是个坏主意,尤其是如果您将此注释应用于整个类,因为此类警告显示您的代码可能存在危险片段,这可能引至ClassCastException

    但在某些情况下它可能有用,例如在ArrayListtoArray 方法实现中:

    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
    

    【讨论】:

    • 我的问题的重点是获取这些“可能是危险的代码片段”的示例,因为我想不出一个。如果我们无法提供编译器发出此类警告的好示例,我们可以使用构建脚本/IDE(Maven、eclipse)设置禁用它们,然后甚至不必在任何地方添加@SuppressWarnings!
    • @JensBannmann 查看我的回答here 并向下滚动到“关于堆污染的注意事项”以获取危险代码示例。
    • @PaulBellora 好的,这是一个很好的答案——尤其是“在呼叫站点而不是方法爆炸”部分。您能否在此处发布您的评论作为答案,或者甚至将“关于堆污染的注释”部分复制到答案中?目前我的问题没有一个直接的答案,如果有的话,我认为它可能对其他人有用。
    • @JensBannmann 实际上meriton's answer 非常接近同一点:“程序......在源代码中不包含强制转换的行抛出 ClassCastException。这可能会严重让大多数程序员感到困惑。”但是,当我有机会时,我会在此处看到有关将该部分移至答案的信息。这个问题其实更适合它。
    • @PaulBellora:我又一次偶然发现了这个。请在此处将您的堆污染说明变成答案:-)
    【解决方案2】:

    来自Effective Java 2nd Edition

    SuppressWarnings 注释可以在任何粒度上使用,从 对整个类的单个局部变量声明。 始终在尽可能小的范围内使用SuppressWarnings 注释。通常这将是一个 变量声明或非常短的方法或构造函数。切勿在整个班级上使用SuppressWarnings。这样做可能会掩盖严重警告。

    如果您发现自己在方法上使用了SuppressWarnings 注释或 超过一行的构造函数,你可以将它移动到本地 变量声明。您可能必须声明一个新的局部变量,但这是值得的 它。

    在return语句上放SuppressWarnings注解是违法的, 因为它不是一个声明 [JLS, 9.7]。你可能会想把注释 在整个方法上,但不要。相反,声明一个局部变量来保存 返回值并注释其声明

    【讨论】:

    • 我知道关于 SuppressWarnings 的一般建议。但是,我想看一个危险代码的实际示例。
    【解决方案3】:

    您的第二个示例在我的 Eclipse 中没有导致编译器警告,我也想不出它应该出现的原因。因此,这是我的首选解决方案。

    存在未检查警告的原因是忽略它们会导致堆污染

    检查普通类型转换,即如果值与所需类型不兼容,它们会导致ClassCastException。未经检查的强制转换不能保证,即即使值不是正确的类型,它们也能成功。这可能导致一个变量持有一个不是其声明类型的子类型的值,这是 Java 规范calls“堆污染”的条件。为了确保运行时类型系统的完整性,每当使用泛型类型的变量时,Java 编译器都会插入普通类型转换。在存在堆污染的情况下,这些强制转换可能会失败。

    例如,程序:

    static void appendTo(List list) {
        list.add(1); // unchecked warning
    }
    
    static void printLengths(List<String> strings) {
        for (String s : strings) { // throws ClassCastException
            System.out.println(s.length());
        }
    }
    
    public static void main(String[] args) throws Exception {
        List<String> strings = new ArrayList<>();
        strings.add("hello");
        appendTo(strings);
        printLengths(strings);
    }
    

    在源代码中不包含强制转换的行处抛出ClassCastException。这可能会严重混淆大多数程序员。

    这就是为什么我建议尽可能使用检查转换,或者使用非泛型转换,或者(在泛型代码中)反射转换:

    class Habitat<T> {
        private final Class<T> clazz;
    
        private List<T> inhabitants;
    
        void add(Object o) {
            inhabitants.add(clazz.cast(o));
        }
    }
    

    【讨论】:

    • 我不是在处理示例中的类型列表,我现在已经使这一点更加明显。我的第二个例子确实引起了编译器警告,第三个没有。但是除了让编译器满意之外,我们在第三个示例中获得了什么?为什么不简单地在编译器设置中禁用警告并保留我的第二个示例中的代码?
    • 你是对的,这些例子实际上并没有引起警告。我会改写这个问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多