Perché preferire le classi interne non statiche a quelle statiche?

32

Questa domanda riguarda la possibilità di fare in modo che una classe nidificata in Java sia una classe nidificata statica o una classe nidificata interna. Ho cercato qui e su Stack Overflow, ma non ho potuto trovare alcuna domanda riguardante le implicazioni del design di questa decisione.

Le domande che ho trovato mi fanno domande sulla differenza tra classi nidificate statiche e interne, il che è chiaro per me. Tuttavia, non ho ancora trovato una ragione convincente per usare mai una classe nidificata statica in Java - con l'eccezione delle classi anonime, che non considero per questa domanda.

Ecco la mia comprensione dell'effetto dell'uso di classi nidificate statiche:

  • Meno accoppiamenti: generalmente otteniamo un minore accoppiamento, in quanto la classe non può accedere direttamente agli attributi della classe esterna. Meno accoppiamento generalmente significa migliore qualità del codice, test più semplice, refactoring, ecc.
  • Singolo Class : Il caricatore di classi non deve occuparsi di una nuova classe ogni volta, noi creiamo un oggetto della classe esterna. Riceviamo semplicemente nuovi oggetti per la stessa classe più e più volte.

Per una classe interiore, trovo generalmente che le persone considerino un accesso agli attributi della classe esterna come un professionista. Mi permetto di dissentire da questo punto di vista dal punto di vista del design, dato che questo accesso diretto implica un elevato accoppiamento e se vogliamo estrarre la classe nidificata nella sua classe separata di livello superiore, possiamo farlo solo dopo esserci sostanzialmente girati in una classe nidificata statica.

Quindi la mia domanda si riduce a questo: ho torto nell'assumere che l'accesso agli attributi disponibile per le classi interne non statiche conduca ad un elevato accoppiamento, quindi a una minore qualità del codice, e che deduco da ciò che (non anonimo) le classi annidate dovrebbero generalmente essere statiche?

O in altre parole: esiste una ragione convincente per cui si preferirebbe una classe interna annidata?

    
posta Frank 12.05.2014 - 09:43
fonte

3 risposte

20

Joshua Bloch nella voce 22 del suo libro "Effective Java Second Edition" dice quando usare quale tipo di classe annidata e perché. Ci sono alcune citazioni qui sotto:

Un uso comune di una classe membro statica è una classe helper pubblica, utile solo in combinazione con la sua classe esterna. Ad esempio, si consideri un enum che descrive le operazioni supportate da un calcolatore. L'operazione enum dovrebbe essere una classe membro statica pubblica della classe Calculator . I client di Calculator potrebbero quindi riferirsi alle operazioni usando nomi come Calculator.Operation.PLUS e Calculator.Operation.MINUS .

Un uso comune di una classe membro non statica consiste nel definire un adattatore che consente a un'istanza della classe esterna di essere vista come un'istanza di qualche classe non correlata. Ad esempio, le implementazioni dell'interfaccia Map utilizzano in genere le classi membro non statiche per implementare le loro viste di raccolta , che vengono restituite dai metodi Map keySet , entrySet e values . Allo stesso modo, le implementazioni delle interfacce di raccolta, come Set e List , in genere usano classi membro non statiche per implementare i loro iteratori:

// Typical use of a nonstatic member class
public class MySet<E> extends AbstractSet<E> {
    ... // Bulk of the class omitted

    public Iterator<E> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator<E> {
        ...
    }
}

Se dichiari una classe membro che non richiede l'accesso a un'istanza accludente, sempre metti il modificatore static nella sua dichiarazione, rendendolo una classe membro statica piuttosto che una non statica.

    
risposta data 12.05.2014 - 10:34
fonte
9

Hai ragione nell'assumere che l'accesso all'attributo disponibile per le classi interne non statiche porti ad un elevato accoppiamento, quindi a una minore qualità del codice, e (non anonimo e non-local ) le classi interne dovrebbero essere generalmente statiche.

Le implicazioni progettuali della decisione di rendere la classe interna non statica sono presentate in Java Puzzler , Puzzle 90 (il carattere in grassetto in basso è mio):

Whenever you write a member class, ask yourself, Does this class really need an enclosing instance? If the answer is no, make it static. Inner classes are sometimes useful, but they can easily introduce complications that make a program difficult to understand. They have complex interactions with generics (Puzzle 89), reflection (Puzzle 80), and inheritance (this puzzle). If you declare Inner1 to be static, the problem goes away. If you also declare Inner2 to be static, you can actually understand what the program does: a nice bonus indeed.

In summary, it is rarely appropriate for one class to be both an inner class and a subclass of another. More generally, it is rarely appropriate to extend an inner class; if you must, think long and hard about the enclosing instance. Also, prefer static nested classes to non-static. Most member classes can and should be declared static.

Se sei interessato, un'analisi più dettagliata di Puzzle 90 è fornita in answer at Stack Overflow .

Vale la pena notare che sopra è essenzialmente una versione estesa della guida fornita in Java Classi e oggetti tutorial :

Use a non-static nested class (or inner class) if you require access to an enclosing instance's non-public fields and methods. Use a static nested class if you don't require this access.

Quindi, la risposta alla domanda che hai chiesto in altre parole per esercitazione è, l'unica ragione convincente per usare non-statico è quando l'accesso a un'istanza non -I campi e metodi pubblici sono richiesti .

La formulazione dell'esercitazione è piuttosto ampia (potrebbe essere il motivo per cui i Java Puzzlers tentano di rafforzarlo e restringerlo). In particolare, l'accesso diretto ai campi di istanza che racchiudono non è mai stato richiesto nella mia esperienza, nel senso che modi alternativi come il passarli come parametri costruttore / metodo sono sempre stati più facili da eseguire il debug e il mantenimento.

Nel complesso, i miei (piuttosto dolorosi) incontri con il debug di classi interne che accedevano direttamente ai campi dell'istanza che racchiudeva facevano impressione che questa pratica assomigliava all'utilizzo dello stato globale, insieme a i mali conosciuti associati ad esso.

Ovviamente, Java fa in modo che il danno di un tale "quasi globale" sia contenuto all'interno della classe che racchiude, ma quando ho dovuto eseguire il debug di una particolare classe interna, sembrava che un tale aiuto per la banda non aiutasse a ridurre il dolore: Dovevo ancora tenere a mente la semantica e i dettagli "stranieri" invece di concentrarmi completamente sull'analisi di un particolare oggetto problematico.

Per motivi di completezza, potrebbero essere i casi dove sopra il ragionamento non si applica Ad esempio, per la mia lettura di map.keySet javadocs , questa caratteristica suggerisce un accoppiamento stretto e, di conseguenza, invalida gli argomenti rispetto alle classi non statiche:

Returns a Set view of the keys contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa...

Non è detto che in qualche modo renderebbe il codice coinvolto più facile da mantenere, testare e debugare mentalmente, ma potrebbe almeno permettere di argomentare che la complicazione corrisponde / è giustificata dalla funzionalità prevista.

    
risposta data 12.05.2014 - 12:33
fonte
1

Alcuni malintesi:

Less coupling: We generally get less coupling, as the [static inner] class cannot directly access its outer class's attributes.

IIRC - In realtà, può. (Naturalmente se si tratta di campi oggetto, non avrà un oggetto esterno da guardare. Tuttavia, se ottiene tale oggetto da qualsiasi luogo, può accedere direttamente a quei campi).

Single Class: The class loader need not take care of a new class each time, we create an object of the outer class. We just get new objects for the same class over and over.

Non sono abbastanza sicuro di cosa intendi qui. Il programma di caricamento classi viene coinvolto solo due volte in totale, una volta per ogni classe (quando viene caricata la classe).

Segue il percorso:

In Javaland ci sembra un po 'paura di creare classi. Penso che debba sembrare un concetto significativo. Generalmente appartiene al suo stesso file. Ha bisogno di una caldaia significativa. Ha bisogno di attenzione per il suo design.

Contro altri linguaggi - es. Scala, o forse C ++: non c'è assolutamente alcun dramma che crea un piccolo titolare (classe, struttura, qualunque) ogni volta che due bit di dati appartengono insieme. Le regole di accesso flessibili aiutano a ridurre il boilerplate, consentendoti di mantenere fisicamente il relativo codice più vicino. Questo riduce la 'distanza mentale' tra le due classi.

Siamo in grado di sviare i problemi di progettazione rispetto all'accesso diretto, mantenendo la classe interna privata.

(... Se avessi chiesto "perché una pubblica classe interna non statica?" questa è un'ottima domanda - Non penso di aver mai trovato un caso giustificato per quelli).

Puoi usarli ogni volta che ti aiuta con la leggibilità del codice ed evitare le violazioni DRY e il boilerplate. Non ho un esempio pronto perché non succede spesso nella mia esperienza, ma succede.

Le classi interne statiche sono ancora un buon approccio predefinito , btw. Non statico ha un campo nascosto extra generato attraverso il quale la classe interna si riferisce all'esterno.

    
risposta data 05.10.2015 - 01:26
fonte

Leggi altre domande sui tag