Java SafeVarargs annotation, does a standard or best practice exist?
Asked Answered
N

3

206

I've recently come across the java @SafeVarargs annotation. Googling for what makes a variadic function in Java unsafe left me rather confused (heap poisoning? erased types?), so I'd like to know a few things:

  1. What makes a variadic Java function unsafe in the @SafeVarargs sense (preferably explained in the form of an in-depth example)?

  2. Why is this annotation left to the discretion of the programmer? Isn't this something the compiler should be able to check?

  3. Is there some standard one must adhere to in order to ensure his function is indeed varags safe? If not, what are the best practices to ensure it?

Nonperformance answered 9/1, 2013 at 8:28 Comment(3)
Have you seen the example (and explanation) in JavaDoc?Dignitary
For your third question, one practice is to always have a first element and others: retType myMethod(Arg first, Arg... others). If you do not specify first, an empty array is allowed, and you may very well have a method by the same name with the same return type taking no arguments, which means the JVM will have a hard time determining which method should be called.Determined
@Dignitary I did, but I don't understand why its given in the varargs context as one can easily replecate this situation outside of a varargs function (verified this, compiles with expected type safety warnings and errors on runtime)..Nonperformance
I
284

1) There are many examples on the Internet and on StackOverflow about the particular issue with generics and varargs. Basically, it's when you have a variable number of arguments of a type-parameter type:

<T> void foo(T... args);

In Java, varargs are a syntactic sugar that undergoes a simple "re-writing" at compile-time: a varargs parameter of type X... is converted into a parameter of type X[]; and every time a call is made to this varargs method, the compiler collects all of the "variable arguments" that goes in the varargs parameter, and creates an array just like new X[] { ...(arguments go here)... }.

This works well when the varargs type is concrete like String.... When it's a type variable like T..., it also works when T is known to be a concrete type for that call. e.g. if the method above were part of a class Foo<T>, and you have a Foo<String> reference, then calling foo on it would be okay because we know T is String at that point in the code.

However, it does not work when the "value" of T is another type parameter. In Java, it is impossible to create an array of a type-parameter component type (new T[] { ... }). So Java instead uses new Object[] { ... } (here Object is the upper bound of T; if there upper bound were something different, it would be that instead of Object), and then gives you a compiler warning.

So what is wrong with creating new Object[] instead of new T[] or whatever? Well, arrays in Java know their component type at runtime. Thus, the passed array object will have the wrong component type at runtime.

For probably the most common use of varargs, simply to iterate over the elements, this is no problem (you don't care about the runtime type of the array), so this is safe:

@SafeVarargs
final <T> void foo(T... args) {
    for (T x : args) {
        // do stuff with x
    }
}

However, for anything that depends on the runtime component type of the passed array, it will not be safe. Here is a simple example of something that is unsafe and crashes:

class UnSafeVarargs
{
  static <T> T[] asArray(T... args) {
    return args;
  }

  static <T> T[] arrayOfTwo(T a, T b) {
    return asArray(a, b);
  }

  public static void main(String[] args) {
    String[] bar = arrayOfTwo("hi", "mom");
  }
}

The problem here is that we depend on the type of args to be T[] in order to return it as T[]. But actually the type of the argument at runtime is not an instance of T[].

3) If your method has an argument of type T... (where T is any type parameter), then:

  • Safe: If your method only depends on the fact that the elements of the array are instances of T
  • Unsafe: If it depends on the fact that the array is an instance of T[]

Things that depend on the runtime type of the array include: returning it as type T[], passing it as an argument to a parameter of type T[], getting the array type using .getClass(), passing it to methods that depend on the runtime type of the array, like List.toArray() and Arrays.copyOf(), etc.

2) The distinction I mentioned above is too complicated to be easily distinguished automatically.

Instinctive answered 10/1, 2013 at 6:43 Comment(4)
well now it all makes sense. thanks. so just to see that I understand you completely, lets say we have a class FOO<T extends Something> would it be safe to return the T[] of a varargs method as an array of Somthings ?Nonperformance
Might be worth noting that one case where it is (presumably) always correct to use @SafeVarargs is when the only thing you do with the array is pass it to another method that is already so annotated (for example: I frequently find myself writing vararg methods that exist to convert their argument to a list using Arrays.asList(...) and pass it to another method; such a case can always be annotated with @SafeVarargs because Arrays.asList has the annotation).Crankcase
@Instinctive I don't know if this was the case in Java 7, but Java 8 doesn't allow @SafeVarargs unless the method is static or final, because otherwise it could get overridden in a derived class with something unsafe.Mollusc
and in Java 9 it will also be allowed for private methods which also cannot be overridden.Annatto
M
8

For best practices, consider this.

If you have this:

public <T> void doSomething(A a, B b, T... manyTs) {
    // Your code here
}

Change it to this:

public <T> void doSomething(A a, B b, T... manyTs) {
    doSomething(a, b, Arrays.asList(manyTs));
}

private <T> void doSomething(A a, B b, List<T> manyTs) {
    // Your code here
}

I've found I usually only add varargs to make it more convenient for my callers. It would almost always be more convenient for my internal implementation to use a List<>. So to piggy-back on Arrays.asList() and ensure there's no way I can introduce Heap Pollution, this is what I do.

I know this only answers your #3. newacct has given a great answer for #1 and #2 above, and I don't have enough reputation to just leave this as a comment. :P

Marquismarquisate answered 11/7, 2018 at 9:48 Comment(0)
D
0

@SafeVarargs is used to indicate that methods will not cause heap pollution.

Heap pollution is when we mix different parameterized types in generic array.

For example:

public static <T> T[] unsafe(T... elements) {
    return elements; 
}

 Object [] listOfItems =  unsafe("some value", 34, new ArrayList<>());
 String stringValue = (String) listOfItems[0]; // some value
 String intValue = (String) listOfItems[1]; // ClassCastException

As you can see, such implementation could easily cause ClassCastException if we don't guess with the type.

Deadandalive answered 17/11, 2022 at 15:54 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.