【问题标题】:What is an apparently correct example of Java code causing heap pollution?什么是 Java 代码导致堆污染的明显正确示例?
【发布时间】:2018-06-12 12:31:19
【问题描述】:

在使用参数化可变参数(例如在

中)时,我正在尝试决定每次收到 Java 堆污染警告时该怎么做
public static <T> LinkedList<T> list(T... elements) {
    ...
}

在我看来,如果我确信不会在我的方法中使用一些奇怪的强制转换,我应该只使用 @SafeVarargs 并继续前进。但这是正确的,还是我需要更加小心?使用参数化可变参数时,是否存在明显正确但实际上不安全的代码?

阅读该主题后,我注意到所提供的示例非常人为。例如Java documentation显示如下错误方法:

public static void faultyMethod(List<String>... l) {
    Object[] objectArray = l;     // Valid
    objectArray[0] = Arrays.asList(42);
    String s = l[0].get(0);       // ClassCastException thrown here
}

这是说教,但很不切实际;有经验的程序员不太可能编写代码来做这样的事情。另一个example

Pair<String, String>[] method(Pair<String, String>... lists) { 
   Object[] objs = lists; 
   objs[0] = new Pair<String, String>("x", "y");  
   objs[1] = new Pair<Long, Long>(0L, 0L);  // corruption !!! 
   return lists; 
} 

这显然是以不切实际的方式混合类型。

那么,在参数化 varargs 下是否存在更微妙的堆污染情况?如果我没有以丢失输入信息或错误混合类型的方式转换变量,我是否有理由使用@SafeVarargs?换句话说,我是否有理由将此警告视为不是很重要的形式?

【问题讨论】:

    标签: java variadic-functions heap-pollution


    【解决方案1】:

    在 Java 中声明泛型数组 T[] 是有问题的,因为它们的类型在编译时是未知的,因此它们可能被滥用,如问题中的示例所示。因此,每当执行此操作时,Java 编译器都会发出警告。

    例如,如果我们声明一个泛型数组,如下所示

    T[] tArray = (T[]) new Object[] { 42 };
    

    我们收到“未经检查的演员表”警告。

    除了这种强制转换之外,将泛型数组引入程序的唯一其他方法是使用泛型可变参数。例如,在

    void bar() {
        foo(new Integer[]{ 42 })
    }
    
    void foo(T... args) {
    }
    

    这里再次引入了一个通用数组,但与未经检查的强制转换方式不同,因此它会收到自己的特定警告,以确保用户没有滥用它。

    确实,只要不将数组转换为不同类型的数组,似乎使用@SafeVarargs 应该是安全的,除非进行非典型的类型转换。

    【讨论】:

    • 你的意思是在运行时不知道,在运行时根本不知道泛型类型(几乎),那么为什么这对数组来说很特殊呢?您的第一个示例之所以有效,是因为数组是协变的并且强制转换可以工作,但它仅在 TObject 时才有效,并且在运行时这就是实际发生的情况。这就是我的意思是演员是假的。这个例子怎么样? static &lt;T extends Foo&gt; T[] create(T elem) { T[] array = (T[]) new Object[]{elem}; return array; } static class Foo { }
    【解决方案2】:

    好问题。这也困扰了我好一阵子。这里有两件事 - 您不关心数组中元素的实际运行时类型,就像您展示的示例一样:

    public static <T> LinkedList<T> list(T... elements) {
        // suppose you iterate over them and add
    }
    

    这是@SafeVarargs 安全的地方。

    第二个是你关心数组中元素的运行时类型(即使是偶然的)。数组,在 java 中,不能是泛型的,所以你不能创建一个类型T [] ts = new T[10],但你可以声明一个类型T[] ts...,因为数组是协变的可以将 Object[] 转换为 T[] - 如果您知道类型匹配。

    当您传递一个泛型数组时,所有这些都会变得有趣:

    // create a single element "generic" array
    static <T> T[] singleElement(T elem) {
        @SuppressWarnings("unchecked")
        T[] array = (T[]) new Object[] { elem };
        return self(array);
    }
    
    // @SafeVarargs
    static <T> T[] self(T... ts) {
        return ts;
    }
    

    Integer[] ints = singleElement(1); 调用它看起来完全合法,但会在运行时中断,这是放置@SafeVarargs 不安全的地方。

    它将中断,因为强制转换 (T[]) 实际上是无用的,并且不会强制执行任何编译时检查。即使您将该方法重写为:

     static <T> T[] singleElement(T elem) {
        @SuppressWarnings("unchecked")
        T[] array = (T[]) new Object[]{elem};
        System.out.println(array.getClass());
        return array;
    }
    

    还是不行。

    【讨论】:

    • 我今天重新审视这个问题,我又想到了其他事情:这里的问题不是错误的演员阵容而不是self的调用吗?似乎一旦编译器知道一个数组是T[],在它上面调用self 是安全的;问题发生在之前,当用户告诉编译器编译器数组是T[],而实际上不是。
    • @user118967 只要您将该通用数组返回给调用者,该转换就是不正确的,就像我展示的示例中一样。事实上,强制转换实际上是假的,在这种情况下不执行编译时检查。顺便说一句,self 的调用只是一个例子,看看我最近编辑的最后一个例子——你不需要 self 来证明它是坏的。
    • 这是一个非常有趣的例子,但我不会说问题在于使用@SafeVarargs。似乎问题是期望 singleElement 返回 Integer[] 只是因为 T 被实例化为 Integer (实际上,即使在您的最后一个示例中,即使 @SafeVarargs 不存在,问题仍然存在)。所以我会得出结论,原始问题的答案确实是 @SafeVarargs 本质上是一种形式,因为它不会隐藏可能发生的错误。
    • @user118967 我从来没有说过问题在于使用@SafeVarargs。如果您阅读它的文档:程序员断言... .... 在调用站点 - 这是编译器无法证明的,但您可以提供帮助它。不管它是否正确,这是一个完全不同的故事。请记住,这是必需的,这样只有在呼叫站点才会有任何警告。实际上,我不明白这里有什么不清楚的地方。
    猜你喜欢
    • 1970-01-01
    • 2015-01-08
    • 1970-01-01
    • 2014-11-01
    • 2019-10-29
    • 2010-09-08
    • 2019-08-31
    • 2012-10-17
    相关资源
    最近更新 更多