SafeVarargs & Variable arguments in Java
There are languages that enforce types at the compile time but forgets about the types in runtime. This is called as Type Erasure.
Erase types in runtime
For example in C, the compiler will ensure that the code is entirely type-proofed. So the bytecode generated will not worry about type information during runtime.
Just like two sides of a coin, on the other side. There are languages that do type checking on runtime (maybe also on compile time). This is called as reification.
Checks types in runtime
For example in Java, even though you can outsmart the compiler and assign things to the compiler. The types are checked at the runtime.
The classic example for Java type reification,
class TypeReificationSample { public static void main(String[] args) { Object[] strArr = new String[1]; strArr[0] = (Object) 13; }}
Here when you compile, you outsmarted the compiler by typecasting it to Object. But when you run the compiled class, you will see ❌
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
at TypeReificationSample.main(TypeReificationSample.java:4)
This shows that Java checks the array on the runtime.
When Generics was implemented in Java, the type erasure is introduced to make the language backwards compatible.
Let us take the classic example again,
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" }}
Generic type check
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" }}
Both the classes compile down to the same bytecode.
class TypeCheck {
TypeCheck();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: returnpublic 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
}
Check the 4th index of the stack. It is not having any information about the type.
Java 5 introduced variable arguments. This means that you can pass multiple arguments via ...
public void someMethodTakesMultipleArguments(String... args) { }// Pass in generic arguments
public <T> void someMethodTakesMultipleArguments(T... generic) { }
The ...
tells the compiler that it expects an array of String in the first case and an array of T in the second case.
The generic arguments will lead to potentially unsafe operations. It is possible that the method will cast or potentially change the type. This will lead to uncertainty.
So the compiler will issue a warning when compiling.
Note: GenericArguments.java uses unchecked or unsafe operations.
Because varargs can cause heap pollution.
Varargs method could cause heap pollution from non-reifiable varargs parameter
In order to solve that, Java7 introduced @SafeVarargs
annotation. That will tell the compiler that the method or constructor does not perform potentially unsafe operations on varargs
parameter.
Applying the @SafeVarargs
annotation to the method will suppress the compiler warning about the unchecked warnings.
But adding @SafeVarargs
to a potentially unsafe method will lead to ClassCastException
at the runtime.
When to apply this annotation: When you pass in variable arguments to a method or constructor and you don’t mutate or cast the object type. The method is potentially safe.
Where to apply this annotation: To the final
and static
methods. This prevents it from overriding the methods. The methods in the interface should not have this annotation.
If the varargs parameter array is used only to transmit a variable number of arguments from the caller to the method—which is, after all, the purpose of varargs—then the method is safe.
- Effective Java