SafeVarargs的用法

转载自:http://softlab.sdut.edu.cn/blog/subaochen/2017/04/safevarargs%E7%9A%84%E7%94%A8%E6%B3%95/

@SafeVarargs在JDK 7中引入,主要目的是处理可变长参数中的泛型,此注解告诉编译器:在可变长参数中的泛型是类型安全的。可变长参数是使用数组存储的,

而数组和泛型不能很好的混合使用[effective-java, p105,第25条:列表优先于数组]

1、

简单的说,数组元素的数据类型在编译和运行时都是确定的,而泛型的数据类型只有在运行时才能确定下来,因此当把一个泛型存储到数组中时,编译器在编译阶段
无法检查数据类型是否匹配,因此会给出警告信息:存在可能的“堆污染”(heap pollution),即如果泛型的真实数据类型无法和参数数组的类型匹配,会导致ClassCastException异常。

,因此当在可变长参数中使用泛型时,编译器都会给出警告信息。考虑#[java-7-new-feature-cookbook, p35]

package cn.edu.sdut.softlab.safevarargs;
 
import java.util.ArrayList;
 
/**
 * Created by subaochen on 17-4-3.
 */
public class SafeVarargsTest {
    public static void main(String[] args) {
        ArrayList<Integer> a1 = new ArrayList<>();
        a1.add(new Integer(1));
        a1.add(2);
 
        showArgs(a1, 12);
    }
 
    //@SafeVarargs
    public static <T> void showArgs(T... array) {
        for (T arg : array) {
            System.out.println(arg.getClass().getName() + ":" + arg);
        }
    }
 
}

当我们使用-Xlint:unchecked参数编译此代码时

2、

如果使用IDE进行编译,需要修改编译参数,增加-Xlint:unchecked编译选项。

,有如下的警告信息:

$ javac -Xlint:unchecked cn/edu/sdut/softlab/safevarargs/SafeVarargsTest.java
cn/edu/sdut/softlab/safevarargs/SafeVarargsTest.java:18: 警告: [unchecked] 参数化 vararg 类型T的堆可能已受污染
public static <T> void showArgs(T… array) {
^
其中, T是类型变量:
T扩展已在方法 <T>showArgs(T…)中声明的Object
1 个警告
但是显然在这个示例中,可变参数的泛型是安全的,因此可以启用#中的@SafeVarargs注解消除这个警告信息。请读者自行验证。
@SafeVarargs注解只能用在参数长度可变的方法或构造方法上,且方法必须声明为static或final,否则会出现编译错误。一个方法使用@SafeVarargs注解的前提是,
开发人员必须确保这个方法的实现中对泛型类型参数的处理不会引发类型安全问题,否则可能导致运行时的类型转换异常。下面给出一个“堆污染”的实例,参见1
 
package cn.edu.sdut.softlab.safevarargs;
 
import java.util.Arrays;
import java.util.List;
 
/**
 * Created by subaochen on 17-4-3.
 * 本例取自SafeVarargs的javadoc文档
 */
public class UnsafeMethodTest {
 
    public static void main(String[] args) {
        List&lt;String&gt; list1 = Arrays.asList("one", "two");
        List&lt;String&gt; list2 = Arrays.asList("three","four");
        unsafeMethod(list1, list2);
    }
 
    @SafeVarargs // 其实并不安全!
    static void unsafeMethod(List&lt;String&gt;... stringLists) {
        Object[] array = stringLists;
        List&lt;Integer&gt; tmpList = Arrays.asList(42, 56);
        array[0] = tmpList; // tmpList是一个List对象(类型已经擦除),赋值给Object类型的对象是允许的(向上塑型),能够编译通过
        String s = stringLists[0].get(0); // 运行时抛出ClassCastException!
    }
}
运行UnsafeMethodTest的结果如下:
Exception in thread “main” java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at cn.edu.sdut.softlab.safevarargs.UnsafeMethodTest.unsafeMethod( UnsafeMethodTest.java:22 )
at cn.edu.sdut.softlab.safevarargs.UnsafeMethodTest.main( UnsafeMethodTest.java:14 )
对这个结果意外吗?我们来详细分析一下。在1中,当代码执行到第22行时的状态如1所示,数组array和stringLists同时指向了参数数组,tmpList是一个包含两个Integer对象的list对象。
 
图 1: 可变长参数的初始状态
当程序执行到
 
3、
这里的赋值操作是合法的,因为tmplist是List类型的对象,array是一个Object类型的数组,根据Java允许“向上塑型”的原则,array数组能够接受任意类型的对象。
array[0] = tmpList;

时,几个变量的关系如2所示,虚线表示原先的指向关系,实线表示新的指向关系。此时,参数数组的第0个元素指向了包含两个Integer对象的list对象tmpList。当进一步执行:

   String s = stringLists[0].get(0);
时,从参数数组中取出第0个元素为list对象(tmpList),再取出list对象的第0个元素为Integer类型的对象(其值为42)。问题在这里出现了,我们试图将一个Integer类型的对象赋值给String类型的对象,显然会导致类型转换异常(ClassCastException)。
因此,这个方法是不应该标记为@SafeVarargs的。
图 2: 可变长参数遭到堆污染
原文地址:https://www.cnblogs.com/passedbylove/p/11410568.html