Annotazione Java SafeVarargs, esiste una procedura standard o migliore?


175

Di recente mi sono imbattuto nell'annotazione Java @SafeVarargs. Cercare su Google ciò che rende pericolosa una funzione variadica in Java mi ha lasciato piuttosto confuso (avvelenamento da mucchio? Tipi cancellati?), Quindi mi piacerebbe sapere alcune cose:

  1. Cosa rende insicura una funzione variadica di Java nel @SafeVarargssenso (preferibilmente spiegata sotto forma di un esempio approfondito)?

  2. Perché questa annotazione è lasciata alla discrezione del programmatore? Non è qualcosa che il compilatore dovrebbe essere in grado di controllare?

  3. Esiste uno standard a cui bisogna attenersi per garantire che la sua funzione sia davvero sicura? In caso contrario, quali sono le migliori pratiche per garantirlo?


1
Hai visto l'esempio (e la spiegazione) in JavaDoc ?
jlordo,

2
Per la terza domanda, una pratica è di avere sempre un primo elemento e altri: retType myMethod(Arg first, Arg... others). Se non si specifica first, è consentito un array vuoto e si potrebbe benissimo avere un metodo con lo stesso nome con lo stesso tipo di ritorno che non accetta argomenti, il che significa che JVM avrà difficoltà a determinare quale metodo deve essere chiamato.
fge

4
@jlordo l'ho fatto, ma non capisco perché sia ​​dato nel contesto di varargs in quanto si può facilmente rimpiazzare questa situazione al di fuori di una funzione varargs (verificato questo, compila con avvisi di sicurezza del tipo previsti ed errori in fase di esecuzione) ..
Oren

Risposte:


244

1) Ci sono molti esempi su Internet e su StackOverflow sul problema particolare con generici e varargs. Fondamentalmente, è quando hai un numero variabile di argomenti di un tipo di parametro-tipo:

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

In Java, varargs è uno zucchero sintattico che subisce una semplice "riscrittura" in fase di compilazione: un parametro varargs di tipo X...viene convertito in un parametro di tipo X[]; e ogni volta che viene fatta una chiamata a questo metodo varargs, il compilatore raccoglie tutti gli "argomenti variabili" che vanno nel parametro varargs e crea un array proprio come new X[] { ...(arguments go here)... }.

Funziona bene quando il tipo di varargs è simile al concreto String.... Quando è una variabile di tipo come T..., funziona anche quando Tè noto per essere un tipo concreto per quella chiamata. ad esempio se il metodo sopra faceva parte di una classe Foo<T>e hai un Foo<String>riferimento, allora chiamarlo fooandrebbe bene perché sappiamo che Tè Stringa quel punto nel codice.

Tuttavia, non funziona quando il "valore" di Tè un altro parametro di tipo. In Java, è impossibile creare una matrice di un componente di tipo-parametro tipo ( new T[] { ... }). Quindi Java invece usa new Object[] { ... }(qui Objectè il limite superiore di T; se il limite superiore fosse qualcosa di diverso, sarebbe quello invece di Object), e quindi ti dà un avviso del compilatore.

Quindi cosa c'è di sbagliato nel creare new Object[]invece new T[]o altro? Bene, gli array in Java conoscono il loro tipo di componente in fase di esecuzione. Pertanto, l'oggetto array passato avrà il tipo di componente errato in fase di esecuzione.

Per l'uso probabilmente più comune di varargs, semplicemente per scorrere gli elementi, questo non è un problema (non ti interessa il tipo di runtime dell'array), quindi è sicuro:

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

Tuttavia, per tutto ciò che dipende dal tipo di componente di runtime dell'array passato, non sarà sicuro. Ecco un semplice esempio di qualcosa che non è sicuro e si blocca:

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

Il problema qui è che dipendiamo dal tipo di argsessere T[]per restituirlo come T[]. Ma in realtà il tipo di argomento in fase di runtime non è un'istanza di T[].

3) Se il tuo metodo ha un argomento di tipo T...(dove T è un parametro di tipo), allora:

  • Sicuro: se il metodo dipende solo dal fatto che gli elementi dell'array sono istanze di T
  • Non sicuro: se dipende dal fatto che l'array è un'istanza di T[]

Le cose che dipendono dal tipo di runtime dell'array includono: restituirlo come tipo T[], passarlo come argomento a un parametro di tipo T[], ottenere il tipo di array usando .getClass(), passarlo a metodi che dipendono dal tipo di runtime dell'array, come List.toArray()e Arrays.copyOf(), eccetera.

2) La distinzione che ho menzionato sopra è troppo complicata per essere facilmente distinta automaticamente.


7
bene ora ha tutto senso. Grazie. quindi, solo per vedere che ti capisco completamente, diciamo che abbiamo una classe FOO<T extends Something>sarebbe sicuro restituire la T [] di un metodo varargs come una matrice di Somthings?
Oren,

30
Vale la pena notare che un caso in cui è (presumibilmente) sempre corretto utilizzare @SafeVarargsè quando l'unica cosa che fai con l'array è passarlo a un altro metodo che è già così annotato (ad esempio: mi trovo spesso a scrivere metodi vararg che esiste per convertire il proprio argomento in un elenco usando Arrays.asList(...)e passarlo a un altro metodo; un caso del genere può sempre essere annotato @SafeVarargsperché Arrays.asListha l'annotazione).
Jules,

3
@newacct Non so se questo fosse il caso in Java 7, ma Java 8 non lo consente a @SafeVarargsmeno che il metodo non sia statico final, perché altrimenti potrebbe essere sovrascritto in una classe derivata con qualcosa di non sicuro.
Andy,

3
e in Java 9 sarà anche consentito per privatemetodi che non possono essere sovrascritti.
Dave L.,

4

Per le migliori pratiche, considerare questo.

Se hai questo:

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

Modificalo in questo:

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
}

Ho scoperto che di solito aggiungo solo varargs per renderlo più conveniente per i miei chiamanti. Sarebbe quasi sempre più conveniente per la mia implementazione interna utilizzare a List<>. Quindi, per andare avanti Arrays.asList()e assicurarsi che non ci sia modo di introdurre Heap Pollution, questo è quello che faccio.

So che questo risponde solo al tuo numero 3. newacct ha dato un'ottima risposta per il n. 1 e il n. 2 sopra, e non ho abbastanza reputazione da lasciarlo solo come commento. : P

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.