Java中的SafeVarargs和变量参数

有些语言在编译时强制执行类型,但忘记了运行时的类型。这被称为类型擦除。

例如,在C中,编译器将确保代码完全是类型证明的。因此生成的字节码不会担心运行时的类型信息。

就像一枚硬币的两面,另一面。有些语言在运行时进行类型检查(也可能在编译时)。这被称为具体化reification。

例如在Java中,即使你可以超越编译器并将内容分配给编译器。在运行时检查类型。

Java类型具体化的经典示例:

在编译时,通过将其类型转换为Object来超越编译器。但是当你运行编译的类时,你会看到错误:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
 at TypeReificationSample.main(TypeReificationSample.java:4)

 这表明Java在运行时才检查数组。

当泛型Generics在Java中实现时,引入了类型擦除以使语言向后兼容。

让我们再次看一个经典的例子

class TypeCheck {
    public static void main(String[] args) {
        List<String> strList = new ArrayList<String>();
        strList.add("Hello");
        String hello = strList.get(0);
        System.out.println(hello); // "Hello"
    }
}

通常使用类型检查方式:

class GenericTypeCheck {
    public static void main(String[] args) {
        List strList = new ArrayList();
        strList.add("Hello");
        String hello = (String) strList.get(0);
        System.out.println(hello); // "Hello"
   }
}

这两个类都编译成相同的字节码。

class TypeCheck {
  TypeCheck();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #4                  // String Hello
      11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      16: pop
      17: aload_1
      18: iconst_0
      19: invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      24: checkcast     #7                  // class java/lang/String
      27: astore_2
      28: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: aload_2
      32: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      35: return
}

看看上面输出堆栈的第4个索引。它没有关于该类型的任何信息。

Java 5引入了变量参数。这意味着可以传递多个参数 ..

public void someMethodTakesMultipleArguments(String... args) { }
// Pass in generic arguments
public <T> void someMethodTakesMultipleArguments(T... generic) { }

符号“...”告诉编译器第一种情况下是String数组,在第二种情况下是T数组。

泛型参数将导致潜在的不安全操作。该方法可能会转换或可能更改类型。这将导致不确定性。

因此编译器会在编译时发出警告。

Note: GenericArguments.java uses unchecked or unsafe operations.

因为varargs会导致堆污染:

Varargs method could cause heap pollution from non-reifiable varargs parameter

为了解决这个问题,Java7引入了@SafeVarargs注释。这将告诉编译器该方法或构造函数不会对varargs参数执行可能不安全的操作。

将@SafeVarargs注释在某个方法上面表示禁止编译器警告未经检查的警告。

但是添加@SafeVarargs到可能不安全的方法上将导致在运行时抛出ClassCastException。

何时应用此注释?

将变量参数传递给方法或构造函数时,不要改变或转换对象类型。该方法可能是安全的。

在哪里应用此注释?

使用在final和static方法。这可以防止它覆盖方法。接口中的方法不应该使用这个注释。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java程序员-张凯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值