2013-05-06 13 views
32

La domanda è sorto durante la lettura di una risposta a questo question - How do I join two lists in java. Questo answer ha dato la soluzioneQual è il danno nell'usare la classe anonima?

List<String> newList = new ArrayList<String>() { { addAll(listOne); addAll(listTwo); } }; 

Leggendo i commenti, gli utenti hanno detto che era il male e brutto e non deve essere utilizzato nella produzione.

Mi piacerebbe sapere qual è il danno nell'utilizzo di questo? Perché è brutto, cattivo o cattivo da usare in produzione?


Nota: Alla domanda come una domanda, perché, il post si fa riferimento è troppo vecchio (2008) e di chi ha risposto è lontano da diversi mesi.

+1

può essere che confonderà altri sviluppatori :-), dopotutto non è una linea codice. – Subin

+2

possibile duplicato di Initialization Double Brace [Efficiency of Java "?] (Http://stackoverflow.com/questions/924285/efficiency-of-java-double-brace-initialization) – assylias

+4

@assylias: la domanda collegata a domanda principalmente sulle prestazioni. Non penso sia un duplicato –

risposta

46

Fatta eccezione per i già citati problemi relativi allo stile di programmazione e all'abuso di ereditarietà, esiste un altro problema sottile: le classi interne e le istanze di classi anonime (non statiche) fungono da chiusure. Ciò significa che sono che mantengono un riferimento implicito all'istanza della classe che lo contiene. Ciò può impedire la raccolta dei dati inutili e, infine, una perdita di memoria.

Dato un pezzo esempio di codice sorgente:

public interface Inner { 
    void innerAction(); 
} 

public class Outer { 

    public void methodInOuter() {} 

    private Inner inner = new Inner() { 
     public void innerAction() { 
      // calling a method outside of scope of this anonymous class 
      methodInOuter(); 
     } 
    } 
} 

Cosa succede al momento della compilazione, è che il compilatore crea un file di classe per la nuova sottoclasse anonima di Inner che ottiene un campo cosiddetto sintetico con la riferimento all'istanza della classe Outer. Il bytecode generato sarà meno equivalente a qualcosa di simile:

public class Outer$1 implements Inner { 

    private final Outer outer; // synthetic reference to enclosing instance 

    public Outer$1(Outer outer) { 
     this.outer = outer; 
    } 

    public void innerAction() { 
     // the method outside of scope is called through the reference to Outer 
     outer.methodInOuter(); 
    } 
} 

Tale acquisizione del riferimento all'istanza allegando accade anche per classi anonime che mai effettivamente accedono qualsiasi dei metodi o campi della classe contenitrice, come l'elenco DBI (double-blind initialized) nella tua domanda.

Ciò comporta il fatto che l'elenco DBI mantiene un riferimento all'istanza che lo contiene fintanto che esiste, impedendo che l'istanza che la racchiude venga raccolta. Supponiamo che l'elenco DBI si trovi a vivere a lungo nell'applicazione, ad esempio come parte del modello nel pattern MVC, e la classe racchiusa catturata è ad esempio un JFrame, che è una classe piuttosto ampia con molti campi. Se hai creato un paio di elenchi DBI, si otterrebbe una perdita di memoria molto rapidamente.

Una possibile soluzione sarebbe utilizzare DBI solo nei metodi statici, perché non è disponibile tale istanza di inclusione nel relativo ambito.

D'altra parte, vorrei ancora sostenere che l'utilizzo di DBI non è ancora necessario nella maggior parte dei casi. Per quanto riguarda la lista che unisce, creerei un semplice metodo riutilizzabile, che non è solo più sicuro, ma anche più conciso e chiaro.

public static <T> List<T> join(List<? extends T> first, List<? extends T> second) { 
    List<T> joined = new ArrayList<>(); 
    joined.addAll(first); 
    joined.addAll(second); 
    return joined; 
} 

E poi il codice del client diventa semplicemente:

List<String> newList = join(listOne, listTwo); 

Ulteriori approfondimenti: https://stackoverflow.com/a/924536/1064809

+2

+1. l'unica risposta che rimanda al problema ** reale ** –

+3

Anche questo è vero. Abusare dell'eredità, invece di impostare le proprietà dei dati perfettamente utilizzabili (valori/o elementi di raccolta), è completamente malvagia. –

+2

+1 per mostrare che l'uso del DBI nei metodi 'static' fa molto meno danno. – gaborsch

4

Poiché non è necessaria una sottoclasse separata, è sufficiente creare un nuovo ArrayList della classe normale e addAll() in entrambi gli elenchi.

Come così:

public static List<String> addLists (List<String> a, List<String> b) { 
    List<String> results = new ArrayList<String>(); 
    results.addAll(a); 
    results.addAll(b); 
    return results; 
} 

E 'il male per creare una sottoclasse, che non è necessaria. Non è necessario estendere o sottoclasse il comportamento - basta cambiare i valori dei dati .

+0

È possibile sostituire '' con '' e aggiungere un '' dopo 'static' e funzionerebbe su tutti gli elenchi. Inoltre, aggiungere vararg sarebbe bello, quindi è possibile unire qualsiasi numero di liste. –

+1

Ho questo nelle mie librerie :) Ma uno esita a mostrare qualcosa di complesso, dove qualcuno è andato così male all'inizio. La correttezza dovrebbe essere come un fulmine a ciel sereno. –

16

Questo particolare uso di classi anonime ha diversi problemi:

  1. è un poco noto linguaggio. Gli sviluppatori che non lo conoscono (o lo conoscono e non lo usano molto) verranno rallentati durante la lettura e/o la modifica del codice che lo utilizza.
  2. in realtà è un uso improprio di una caratteristica del linguaggio: non si sta cercando di definire un nuovo tipo di ArrayList, si vuole solo un po 'di lista di array con alcuni valori in essa esistenti
  3. si crea una nuova classe che occupa risorse: spazio su disco per mantenere la definizione della classe, il tempo di analizzare/verificare/... esso, permgen per contenere la definizione della classe, ...
  4. anche se il "codice reale" è leggermente più lungo, può essere facilmente spostato in un nome metodo di utilità (joinLists(listOne, listTwo))

A mio parere # 1 è il motivo più importante per evitarlo, seguito da vicino da # 2. # 3 di solito non è un grosso problema, ma non dovrebbe essere dimenticato.

+3

È un'eredità errata, quando l'ereditarietà non è richiesta. –

+0

@ThomasW: sì, penso che sia il mio n. 2, o l'hai interpretato diversamente? –

+0

3. [...], tempo per JVM per verificare la classe durante il caricamento del file ".class", ... –

2

Non è un approccio negativo di per sé, ad esempio, nelle prestazioni o qualcosa del genere, ma l'approccio è un po 'oscuro e quando si utilizza qualcosa di simile, si deve sempre (ad esempio, il 99%) spiegare questo approccio. Penso che sia uno dei principali motivi per non utilizzare questo approccio, e durante la digitazione:

List<String> newList = new ArrayList<String>(); 
newList.addAll(listOne); 
newList.addAll(listTwo); 

è un po 'più di battitura, è un po' più facile da leggere, che aiuta molto nella comprensione o il codice di debug.

17

I commenti "brutto" e "non usare in produzione" si riferiscono a questo uso specifico di classi anonime, non a classi anonime in generale.

Questo uso specifico assegna a newList una sottoclasse anonima di ArrayList<String>, una classe completamente nuova creata con un unico scopo in mente, ovvero l'inizializzazione di un elenco con il contenuto di due elenchi specifici.Questo non è molto leggibile (anche un lettore esperto passerebbe qualche secondo a capirlo), ma soprattutto, può essere raggiunto senza sottoclassi nello stesso numero di operazioni. In sostanza, la soluzione paga per una piccola comodità con la creazione di una nuova sottoclasse, che può causare problemi lungo la strada, ad esempio, in situazioni in cui si tenta di mantenere questa raccolta utilizzando un framework automatizzato che prevede che le raccolte abbiano specifiche tipi.

+0

+1 per aver sottolineato che il problema non è con le classi anonime in generale. –

1

Nel tuo esempio sembra davvero male e brutto, almeno per me - è difficile capire cosa sta succedendo nel codice. Ma ci sono alcuni modelli di utilizzo di classi anonime che le persone sono utilizzati per quanto li vedono molto spesso, ad esempio

Arrays.sort(args, new Comparator<String>() { 
     public int compare(String o1, String o2) { 
      return ... 
     }}); 

che chiamerei il sopra di un caso migliore pratica.

Problemi correlati