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

前端 未结 2 1568
说谎
说谎 2020-12-04 05:25

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?

相关标签:
2条回答
  • 2020-12-04 05:42

    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

    0 讨论(0)
  • 2020-12-04 06:04

    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.

    0 讨论(0)
提交回复
热议问题