25

Ho seguente frammento di codice:La dipendenza ciclica tra classe anonima e classe genitore è errata?

public class Example { 

private Integer threshold; 

private Map<String, Progress> history; 

protected void activate(ComponentContext ctx) { 
    this.history = Collections.synchronizedMap(new LinkedHashMap<String, Progress>() { 
     @Override 
     protected boolean removeEldestEntry(Map.Entry<String, Progress> entry) { 
      return size() > threshold; 
     } 
    }); 
    } 
} 

Theres è una dipendenza ciclica tra anonima LinkedHashMap di classe e Example di classe. Va bene o no? Perchè no? Sta per essere bonariamente recuperato dal netturbino?

+0

Un caso d'uso molto classico di classi interne anonime. Potrei essere intollerante riguardo ai riferimenti ciclici, ma questo non mi ha mai infastidito (ea volte sarebbe difficile riuscire a farlo senza questo riferimento). – Chop

risposta

22

OK o no?

Questo è completamente a posto.

threshold è un campo, quindi può essere fatto riferimento all'interno di una classe anonima senza alcun problema. (Era threshold stato una variabile locale, avrebbe dovuto essere (efficacemente) finale.)

Le dipendenze cicliche tra le classi sono comuni e quando il grafico delle dipendenze è piccolo (come in questo caso) non pone alcun problema . Il fatto che il tuo LinkedHashMap sia una classe anonima non ha importanza qui.

Sta per essere recuperato con garbo dal garbage collector?

L'unica cosa da tenere in considerazione riguardo alle perdite di memoria + alle classi interne è che una classe interna (non statica) ha un riferimento implicito al suo oggetto che racchiude. Ciò significa che se crei molte e molte istanze della classe interna, non puoi aspettarti che le istanze degli oggetti di classe esterna siano triturati.

Ciò significa in questo caso che se si perdono riferimenti alla mappa history, le istanze di Example non verranno convertite in GC.


note correlate:

  • Considerando che si sta utilizzando synchronizedMap sembra che si sta lavorando su un programma multithreaded. In tal caso, è necessario prestare attenzione ai problemi di sincronizzazione e visibilità per il campo threshold.

  • Se possibile, cercare di rendere il campo threshold finale

  • Un'altra opzione sarebbe quella di creare una classe denominata per la vostra LinkedHashMap e comprendono threshold come un campo in quella classe, invece.

2

La dipendenza ciclica non è male nel suo, tuttavia potrebbe causare perdite di memoria insospettate.

Preso il tuo esempio così com'è, adesso sta bene come fa quello che vuoi che faccia.

Se invece, o qualcun altro modifica il codice per esporre il privato:

private Map<String, Progress> history; 

Poi si possono avere problemi.Ciò che accadrà è che passerai a riferirti anche alla classe di Esempio, intenzionale o no, dato che la tua classe interiore ha un riferimento implicito ad essa.

Non posso darti una citazione diretta in questo momento, ma Steve McConnell nel suo codice completo sta chiamando le dipendenze cicliche un anti-modello. Puoi leggere lì o indovino google per questo, per leggere su questo in grande dettaglio.

Un altro problema che mi viene in mente, la dipendenza ciclica è abbastanza difficile da testare mentre si crea un livello molto alto di accoppiamento tra gli oggetti.

In generale, è necessario evitare la dipendenza circolare a meno che non si abbia una buona ragione per non farlo, come ad esempio l'implementazione dell'elenco circolare collegato.

+4

"La dipendenza ciclica è abbastanza difficile da testare l'unità" - Il tizio ha chiesto una classe * anonima *: come mai testare una classe anonima senza avere la classe esterna? – mastov

2

Ogni istante che istanziate una classe interna non statica (sia essa denominata o anonima), questa istanza di classe interna ottiene automaticamente un riferimento all'istanza della classe genitrice che la include.

Quanto sopra significa che se la classe esterna contiene anche un riferimento alla classe interna non statica (come nel caso del codice), allora c'è una dipendenza ciclica tra le istanze di classe esterna e la parte interna non statica classe (di nuovo, sia denominata che anonima).

L'unica domanda effettiva in questa configurazione è se l'utilizzo di questo riferimento incrociato esistente è legittimo. Nel tuo caso specifico, non vedo alcun problema: la classe interna non statica utilizza una variabile di istanza della classe esterna che la include. Sembra Kosher per me.

In questa situazione, la perdita di memoria di solito si verifica quando un riferimento all'istanza della classe interna viene passato all'esterno della classe esterna (che è comunemente il caso con vari Listeners) - poiché questa istanza ha un riferimento all'istanza di la classe esterna, la classe esterna non può essere raccolta dalla spazzatura. Tuttavia, non credo che una perdita di memoria possa essere causata se si fa un riferimento incrociato tra le classi esterne e quelle interne - saranno raccolte insieme.

+1

Ancora, questo non è corretto. La classe genitore non deve avere riferimenti alla classe interiore. – John

+1

@ user3360241, penso di capire perché hai scritto il tuo commento. La risposta è stata modificata, va bene ora? – Vasiliy

6

Hai comunque questa dipendenza, perché ogni oggetto della classe interna anonima ha un riferimento implicito all'oggetto di una classe che lo racchiude. Java è progettato in questo modo, e le classi interne nidificate hanno questo riferimento per un motivo, quindi dal punto di vista delle specifiche del linguaggio questo compila e sembra perfettamente normale.

Per quanto riguarda la (mancanza di) "disegno odore", se questo oggetto classe anonima è completamente incapsulato in Example classe, non ha significato distintivo senza il suo contesto di cinta, e non è trapelato ovunque al di fuori della classe Example, non c'è nulla sbagliato con i campi di riferimento della classe che racchiude. Semplicemente usi questa classe interiore per raggruppare qualche logica.

Se tuttavia questo oggetto viene fuoriuscito dall'oggetto che lo racchiude (lo si restituisce via getter, ad esempio), è necessario vietarlo o rifattarlo in una classe interna statica che riceve threshold come parametro. Questo oggetto interno tiene riferimento all'oggetto che lo racchiude e può mantenerlo da GC, causando così una perdita di memoria.

+1

Sì, ma il contrario non si applica. Le classi interne hanno riferimenti impliciti alle classi genitore, ma il contrario non si applica. – John

+0

@ user3360241 sicuro. Non c'è niente nel mio post che lo contraddica. –

+1

La tua affermazione: hai comunque questa dipendenza implica quindi :) Poiché op sta chiedendo la dipendenza ciclica, è naturale trarre la conclusione che ti stai riferendo ad essa. – John

1

Non mi piace la soluzione (anche se sono d'accordo questo potrebbe funzionare):

  1. la classe di esempio dovrebbe attuare mappa o estendere LinkedHashMap perché la soglia variabile di istanza è definita lì e raffina il concetto di LinkedHashMap con una sua definizione

  2. l'esempio della classe NON deve implementare Map o estendere LinkedHashMap perché il metodo activate non raffina LinkedHashMap né Map ma utilizza i concetti di Maps.

1 + 2 => problema del concepimento.

Problemi correlati