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

175

Recientemente me he encontrado con la @SafeVarargsanotación de Java . Buscar en Google lo que hace que una función variada en Java sea insegura me dejó bastante confundido (¿envenenamiento en el montón? ¿Tipos borrados?), Así que me gustaría saber algunas cosas:

  1. ¿Qué hace que una función Java variada sea insegura en el @SafeVarargssentido (preferiblemente explicada en forma de un ejemplo en profundidad)?

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

  3. ¿Hay algún estándar que uno deba cumplir para garantizar que su función sea realmente segura? Si no, ¿cuáles son las mejores prácticas para garantizarlo?

Oren
fuente
1
¿Has visto el ejemplo (y explicación) en JavaDoc ?
jlordo
2
Para su tercera pregunta, una práctica es tener siempre un primer elemento y otros: retType myMethod(Arg first, Arg... others). Si no especifica first, se permite una matriz vacía, y es muy posible que tenga un método con el mismo nombre con el mismo tipo de retorno sin argumentos, lo que significa que la JVM tendrá dificultades para determinar qué método debe llamarse.
fge
44
@jlordo Lo hice, pero no entiendo por qué se da en el contexto de varargs, ya que uno puede reponer fácilmente esta situación fuera de una función de varargs (verificado esto, compila con advertencias de seguridad de tipo y errores esperados en tiempo de ejecución) ...
Oren

Respuestas:

244

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

<T> 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 de tipo varargs 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 igual que new X[] { ...(arguments go here)... }.

Esto funciona bien cuando el tipo varargs es concreto String.... Cuando se trata de una variable de tipo T..., también funciona cuando Tse sabe que es un tipo concreto para esa llamada. por ejemplo, si el método anterior era parte de una clase Foo<T>y usted tiene una Foo<String>referencia, entonces invocarlo fooestaría bien porque sabemos que Testá Stringen ese punto del código.

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

Entonces, ¿qué hay de malo en crear en new Object[]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 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 esto es seguro:

@SafeVarargs
final <T> 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 no es seguro 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 argsser 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 instancias 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[], obtener el tipo de matriz usando .getClass(), pasarlo 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é anteriormente es demasiado complicada para poder distinguirla automáticamente.

newacct
fuente
77
bueno ahora todo tiene sentido. Gracias. así que para ver que te entiendo completamente, digamos que tenemos una clase, FOO<T extends Something>¿sería seguro devolver la T [] de un método varargs como una matriz de Somthings?
Oren
30
Vale la pena señalar que un caso en el que es (presumiblemente) siempre correcto de usar @SafeVarargses cuando lo único que hace con la matriz es pasarlo a otro método que ya está anotado (por ejemplo: con frecuencia me encuentro escribiendo métodos vararg que existen para convertir su argumento en una lista usando Arrays.asList(...)y pasarlo a otro método; siempre se puede anotar este caso @SafeVarargsporque Arrays.asListtiene la anotación).
Jules
3
@newacct No sé si este fue el caso en Java 7, pero Java 8 no permite a @SafeVarargsmenos que el método sea statico final, porque de lo contrario podría anularse en una clase derivada con algo inseguro.
Andy
3
y en Java 9 también se permitirá para privatemétodos que tampoco se pueden anular.
Dave L.
4

Para las mejores prácticas, considere esto.

Si tienes esto:

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

Cámbialo 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 llamantes. Casi siempre sería más conveniente para mi implementación interna usar a List<>. Entonces, para aprovechar Arrays.asList()y asegurarme de que no hay manera de que pueda introducir la contaminación del montón, esto es lo que hago.

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

Refrigerador
fuente