@SuppressWarnings nella dichiarazione di array generico

6

Mentre facevo un test di codifica, mi sono imbattuto in un problema in cui ho bisogno di inizializzare un array di tipo generico in Java. Mentre cercavo di capire come farlo, ho esaminato la domanda questa Stack Overflow e Richiede di sopprimere un avviso che non sembra una buona pratica e potrebbe avere altri problemi di sicurezza di tipo. Per chiarezza, ecco il codice:

public class GenSet<E> {
  private E[] a;

  public GenSet(Class<E> c, int s) {
    // Use Array native method to create array
    // of a type only known at run time
    @SuppressWarnings("unchecked")
    final E[] a = (E[]) Array.newInstance(c, s);
    this.a = a;
  }

  E get(int i) {
    return a[i];
  }
}

Significa che sto affrontando il problema in modo sbagliato? Va bene avere il @SuppressWarnings("unchecked") in questo caso?

Devo usare solo ArrayList ?

    
posta ford prefect 26.09.2014 - 02:26
fonte

3 risposte

8

@SuppressWarnings("unchecked") a volte è corretto e inevitabile in codice ben scritto. La creazione di un array di tipi generici è un caso. Se puoi aggirare il problema usando Collection<T> o List<T> di generici, ti suggerisco di farlo, ma se solo un array lo farà, allora non c'è nulla che tu possa fare.

Il casting in un tipo generico è un altro caso simile. I cast generici non possono essere verificati in fase di esecuzione, quindi riceverai un avviso se lo fai, ad esempio

List<Integer> numbers = (List<Integer>) object;

Questo non lo rende un cattivo stile per avere lanci generici. Riflette semplicemente una limitazione del modo in cui i generici sono stati progettati in Java.

Per riconoscere che sto intenzionalmente scrivendo codice con avvisi, ho deciso di aggiungere un commento a tali righe. Questo fa risaltare la scelta. Inoltre, rende facile individuare queste righe, poiché @SuppressWarnings non può sempre essere utilizzato direttamente sulle linee di destinazione. Se si trova nella parte superiore del metodo, potrebbe non essere ovvio quale riga o linee generano avvisi.

((List<Integer>) object).add(1);   // warning: [unchecked] unchecked cast

Inoltre, per essere chiari, dovresti non aggirare il problema con un tipo non elaborato ( List invece di List<T> ). Questa è una cattiva idea. Produrrà anche avvertimenti se abiliti -Xlint , quindi non renderebbe la situazione migliore, comunque.

    
risposta data 26.09.2014 - 03:06
fonte
5

In base al fatto che stai chiedendo se dovresti usare ArrayList , suppongo che l'uso di un array non sia un requisito. Vorrei usare ArrayList o un'altra raccolta e fare affidamento su interfacce, non su implementazioni, per fare riferimento a questo:

List<Integer> myInts = new ArrayList<Integer>();
myInts.add(4);

I generici Java sono in una posizione difficile. Da un lato, Java ha l'obiettivo autoimposto (ma buono) di essere retrocompatibile. Poiché i generici sono essenzialmente incompatibili con il bytecode precedente alla 1.5, ciò comporta la cancellazione dei tipi . In sostanza, il tipo generico viene rimosso al momento della compilazione e le chiamate al metodo generico e gli accessi sono invece rinforzati con i cast di tipo. D'altra parte, questa limitazione rende i riferimenti generici veramente disordinati a volte al punto che devi usare @SuppressWarnings per i tipi unchecked e raw a volte.

Ciò significa che le raccolte che si basano su chiamate di metodo per isolare gli utenti di una raccolta dalla memoria funzionano bene, ma gli array sono reificati il che significa che per la lingua, i tipi di array devono essere disponibili in fase di runtime. Questo va contro alle collezioni che, come ho detto, hanno cancellato il tipo generico. Esempio:

String[] array1 = new String[1];
Object[] array2 = array1;

Nonostante il riferimento array2 sia dichiarato come Object[] , la JVM sa che l'array che ha come target può contenere solo stringhe. Poiché questi tipi sono reifable, sono essenzialmente incompatibili con i generici.

StackOverflow ha molte domande sull'argomento dei generici e degli array Java, eccone alcuni validi che ho trovato:

risposta data 26.09.2014 - 06:37
fonte
0

In questo caso, è perfettamente corretto sopprimere l'avviso di cast non controllato.

È un cast non controllato perché E non è noto in fase di runtime. Quindi il controllo del runtime può solo controllare il cast fino a Object[] (la cancellazione di E[] ), ma non in realtà fino a E[] stesso. Quindi, ad esempio, se E era Integer , se la classe di esecuzione effettiva dell'oggetto era String[] , non verrebbe rilevata dal controllo anche se non è Integer[] .)

Array.newInstance() restituisce Object e non E[] (dove E sarebbe l'argomento di tipo del parametro Class ), perché può essere utilizzato per creare entrambi gli array di primitive e matrici di riferimenti. Digitare variabili come E non può rappresentare i tipi primitivi e l'unico supertipo di tipi di matrice primitiva è Object . Gli oggetti Class che rappresentano i tipi primitivi sono Class parametrizzati con la sua classe wrapper come parametro type, ad es. int.class ha tipo Class<Integer> . Ma se passi int.class a Array.newInstance() , creerai un int[] , non E[] (che sarebbe Integer[] ). Ma se passi una Class<E> che rappresenta un tipo di riferimento, Array.newInstance() restituirà un E[] .

Quindi, in sostanza, chiamando Array.newInstance() con Class<E> verrà sempre restituito un E[] o una matrice di primitivi. Un tipo array-of-primitives non è un sottotipo di Object[] , quindi fallirebbe un controllo di runtime per Object[] . In altre parole, se il risultato è un Object[] , è garantito che sia un E[] . Quindi, anche se questo cast controlla solo fino a Object[] in fase di esecuzione, e non controlla la parte da Object[] a E[] , in questo caso il controllo fino a Object[] è sufficiente per garantire che è un E[] , e quindi la parte non controllata non è un problema in questo caso, ed è effettivamente un cast completamente controllato.

A proposito, dal codice che hai mostrato, non è necessario passare un oggetto di classe per inizializzare GenSet o usare Array.newInstance() . Ciò sarebbe necessario solo se la tua classe usasse effettivamente la classe E in fase di runtime. Ma non è così. Tutto ciò che fa è creare una matrice (che non è esposta all'esterno della classe) e ottenere elementi da essa. Questo può essere ottenuto utilizzando un Object[] . Devi solo eseguire il cast su E quando togli un elemento (che non è controllato da cui sappiamo essere al sicuro se mettiamo solo E s):

public class GenSet<E> {
  private Object[] a;

  public GenSet(int s) {
    this.a = new Object[s];
  }

  @SuppressWarnings("unchecked")
  E get(int i) {
    return (E)a[i];
  }
}
    
risposta data 23.06.2015 - 11:06
fonte

Leggi altre domande sui tag