Pregunta Anotación Java SafeVarargs, ¿existe una práctica estándar o mejor?


Recientemente me encontré con el java @SafeVarargs anotación. Buscar en Google lo que hace que una función variada en Java no sea segura me dejó bastante confundido (¿tipo de intoxicación por montón?), Así que me gustaría saber algunas cosas:

  1. Lo que hace insegura una función variada de Java en el @SafeVarargs sentido (preferiblemente explicado en forma de un ejemplo en profundidad)?

  2. ¿Por qué esta anotación queda a discreción del programador? ¿No es esto algo que el compilador debería poder verificar?

  3. ¿Hay alguna norma a la que se deba adherir para garantizar que su función sea efectivamente segura? Si no, ¿cuáles son las mejores prácticas para garantizarlo?


145
2018-01-09 08:28


origen


Respuestas:


1) Hay muchos ejemplos en Internet y en StackOverflow sobre el problema particular con genéricos y varargs. Básicamente, es cuando tienes un número variable de argumentos de un tipo de tipo de parámetro:

void foo(T... args);

En Java, los varargs son un azúcar sintáctico que se somete a una simple "reescritura" en tiempo de compilación: un parámetro varargs de tipo X... se convierte en un parámetro de tipo X[]; y cada vez que se realiza una llamada a este método varargs, el compilador recopila todos los "argumentos variables" que van en el parámetro varargs, y crea una matriz como new X[] { ...(arguments go here)... }.

Esto funciona bien cuando el tipo varargs es concreto como String.... Cuando es una variable de tipo como T..., también funciona cuando T se sabe que es un tipo concreto para esa llamada. p.ej. si el método anterior fuera parte de una clase Foo<T>y tienes un Foo<String> referencia, luego llamar foo en eso estaría bien porque sabemos T es String en ese punto en el código.

Sin embargo, no funciona cuando el "valor" de T es otro parámetro de tipo. En Java, es imposible crear una matriz de un tipo de componente tipo-parámetro (new T[] { ... }) Entonces, Java utiliza new Object[] { ... } (aquí Object es el límite superior de T; si el límite superior fuera algo diferente, sería que en lugar de Object), y luego le da una advertencia del compilador.

Entonces, ¿qué hay de malo en crear new Object[] en lugar de new T[] ¿o lo que sea? Bueno, las matrices en Java conocen su tipo de componente en tiempo de ejecución. Por lo tanto, el objeto de matriz pasado tendrá el tipo de componente incorrecto en tiempo de ejecución.

Para probablemente el uso más común de varargs, simplemente para iterar sobre los elementos, esto no es un problema (no le importa el tipo de tiempo de ejecución de la matriz), por lo que es seguro:

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

Sin embargo, para cualquier cosa que dependa del tipo de componente de tiempo de ejecución de la matriz pasada, no será seguro. Aquí hay un ejemplo simple de algo que es inseguro y se bloquea:

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");
  }
}

El problema aquí es que dependemos del tipo de args ser T[] para devolverlo como T[]. Pero en realidad, el tipo de argumento en tiempo de ejecución no es una instancia de T[].

3) Si su método tiene un argumento de tipo T... (donde T es cualquier parámetro de tipo), entonces:

  • Seguro: si su método solo depende del hecho de que los elementos de la matriz son ejemplos de T
  • Inseguro: si depende del hecho de que la matriz es una instancia de T[]

Las cosas que dependen del tipo de tiempo de ejecución de la matriz incluyen: devolverlo como tipo T[], pasarlo como argumento a un parámetro de tipo T[], obteniendo el tipo de matriz usando .getClass(), pasándolo a métodos que dependen del tipo de tiempo de ejecución de la matriz, como List.toArray() y Arrays.copyOf(), etc.

2) La distinción que mencioné arriba es demasiado complicada para ser fácilmente distinguida automáticamente.


208
2018-01-10 06:43



Para mejores prácticas, considere esto.

Si tienes esto:

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

Cambiarlo a esto:

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
}

He descubierto que generalmente solo agrego varargs para que sea más conveniente para mis interlocutores. Casi siempre sería más conveniente para mi implementación interna usar un List<>. Por lo tanto, para llevarlo de vuelta Arrays.asList() y asegurarme de que no hay manera de que pueda presentar Heap Pollution, esto es lo que hago.

Sé que esto solo responde a tu # 3. newacct ha dado una excelente respuesta para los números 1 y 2 anteriores, y no tengo suficiente reputación como para dejar esto como un comentario. :PAG


0
2017-07-11 09:48