Classi statiche e classi interne in Java

Le classi annidate sono classi dichiarate come membri di altre classi o ambiti. La nidificazione delle classi è un modo per organizzare meglio il tuo codice. Ad esempio, supponiamo di avere una classe non annidata (nota anche come classe di primo livello ) che memorizza gli oggetti in un array ridimensionabile, seguita da una classe iteratore che restituisce ogni oggetto. Anziché inquinare lo spazio dei nomi della classe di primo livello, è possibile dichiarare la classe iteratore come membro della classe della raccolta di array ridimensionabili. Questo funziona perché i due sono strettamente correlati.

In Java, le classi nidificate sono classificate come classi membro statiche o classi interne . Le classi interne sono classi membro non statiche, classi locali o classi anonime. In questo tutorial imparerai come lavorare con le classi membri statiche e i tre tipi di classi interne nel tuo codice Java.

Evita perdite di memoria nelle classi nidificate

Vedi anche il suggerimento Java associato a questo tutorial, dove imparerai perché le classi nidificate sono vulnerabili alle perdite di memoria.

Classi statiche in Java

Nel mio tutorial Java 101 Classi e oggetti in Java, hai imparato come dichiarare campi statici e metodi statici come membri di una classe. In Inizializzazione di classi e oggetti in Java, si è appreso come dichiarare gli inizializzatori statici come membri di una classe. Ora imparerai come dichiarare classi statiche . Formalmente note come classi membro statiche , queste sono classi annidate dichiarate allo stesso livello di queste altre entità statiche, utilizzando la staticparola chiave. Ecco un esempio di una dichiarazione di classe membro statica:

 class C { static int f; static void m() {} static { f = 2; } static class D { // members } } 

Questo esempio introduce una classe di primo livello Ccon campo fstatico m(), metodo statico , un inizializzatore statico e una classe membro statica D. Notare che Dè un membro di C. Anche il campo statico f, il metodo statico m()e l'inizializzatore statico sono membri di C. Poiché tutti questi elementi appartengono alla classe C, è nota come classe di inclusione . La classe Dè nota come classe racchiusa .

Regole di recinzione e accesso

Sebbene sia racchiuso, una classe membro statica non può accedere ai campi di istanza della classe che lo racchiude e richiamare i suoi metodi di istanza. Tuttavia, può accedere ai campi statici della classe che la racchiude e richiamare i suoi metodi statici, anche i membri dichiarati private. Per dimostrare, il listato 1 dichiara an EnclosingClasscon un annidato SMClass.

Listato 1. Dichiarazione di una classe membro statica (EnclosingClass.java, versione 1)

 class EnclosingClass { private static String s; private static void m1() { System.out.println(s); } static void m2() { SMClass.accessEnclosingClass(); } static class SMClass { static void accessEnclosingClass() { s = "Called from SMClass's accessEnclosingClass() method"; m1(); } void accessEnclosingClass2() { m2(); } } } 

Il listato 1 dichiara una classe di primo livello denominata EnclosingClasscon campo sclasse, metodi di classe m1()e m2()classe membro statica SMClass. SMClassdichiara il metodo della classe accessEnclosingClass()e il metodo dell'istanza accessEnclosingClass2(). Nota quanto segue:

  • m2()L'invocazione del metodo SMClasss accessEnclosingClass()richiede il SMClass.prefisso perché accessEnclosingClass()è dichiarato static.
  • accessEnclosingClass()è in grado di accedere EnclosingClasss' sdi campo e chiamare il suo m1()metodo, anche se entrambi sono stati dichiarati private.

Il Listato 2 presenta il codice sorgente a una SMCDemoclasse dell'applicazione che dimostra come invocare SMClassil accessEnclosingClass()metodo di. Dimostra anche come creare un'istanza SMClasse invocare il suo accessEnclosingClass2()metodo di istanza.

Listato 2. Invocare i metodi di una classe membro statica (SMCDemo.java)

 public class SMCDemo { public static void main(String[] args) { EnclosingClass.SMClass.accessEnclosingClass(); EnclosingClass.SMClass smc = new EnclosingClass.SMClass(); smc.accessEnclosingClass2(); } } 

Come mostrato nel Listato 2, se vuoi invocare il metodo di una classe di primo livello dall'interno di una classe racchiusa, devi anteporre al nome della classe racchiusa il nome della sua classe racchiusa. Allo stesso modo, per istanziare una classe racchiusa è necessario anteporre al nome di quella classe il nome della sua classe racchiusa. È quindi possibile richiamare il metodo dell'istanza nel modo normale.

Compilare gli elenchi 1 e 2 come segue:

 javac *.java 

Quando si compila una classe che la racchiude che contiene una classe membro statica, il compilatore crea un file di classe per la classe membro statica il cui nome è costituito dal nome della classe che lo racchiude, un carattere del segno di dollaro e il nome della classe membro statico. In questo caso, la compilazione risulta in EnclosingClass$SMCClass.classe EnclosingClass.class.

Eseguire l'applicazione come segue:

 java SMCDemo 

Dovresti osservare il seguente output:

 Called from SMClass's accessEnclosingClass() method Called from SMClass's accessEnclosingClass() method 

Esempio: classi statiche e Java 2D

La libreria di classi standard di Java è una libreria runtime di file di classe, che memorizza le classi compilate e altri tipi di riferimento. La libreria include numerosi esempi di classi di membri statici, alcuni dei quali si trovano nelle classi di forme geometriche 2D Java che si trovano nel java.awt.geompacchetto. (Imparerai a conoscere i pacchetti nel prossimo tutorial di Java 101 ).

La Ellipse2Dclasse trovata in java.awt.geomdescrive un'ellisse, che è definita da un rettangolo di inquadratura in termini di un angolo superiore sinistro (x, y) insieme alle estensioni di larghezza e altezza. Mostra il seguente frammento di codice che l'architettura di questa classe è basata su Floate Doubleclassi utente statici, che sia sottoclasse Ellipse2D:

 public abstract class Ellipse2D extends RectangularShape { public static class Float extends Ellipse2D implements Serializable { public float x, y, width, height; public Float() { } public Float(float x, float y, float w, float h) { setFrame(x, y, w, h); } public double getX() { return (double) x; } // additional instance methods } public static class Double extends Ellipse2D implements Serializable { public double x, y, width, height; public Double() { } public Double(double x, double y, double w, double h) { setFrame(x, y, w, h); } public double getX() { return x; } // additional instance methods } public boolean contains(double x, double y) { // ... } // additional instance methods shared by Float, Double, and other // Ellipse2D subclasses } 

Le classi Floate Doublesi estendono Ellipse2D, fornendo Ellipse2Dimplementazioni in virgola mobile e in virgola mobile a doppia precisione . Gli sviluppatori utilizzano Floatper ridurre il consumo di memoria, in particolare perché potrebbero essere necessari migliaia o più di questi oggetti per costruire una singola scena 2D. Usiamo Doublequando è richiesta una maggiore precisione.

Non è possibile istanziare la Ellipse2Dclasse astratta , ma è possibile istanziare o Floato Double. Puoi anche estendere Ellipse2Dper descrivere una forma personalizzata basata su un'ellisse.

Ad esempio, supponiamo che tu voglia introdurre una Circle2Dclasse, che non è presente nel java.awt.geompacchetto. Il seguente frammento di codice mostra come creare un Ellipse2Doggetto con un'implementazione a virgola mobile:

 Ellipse2D e2d = new Ellipse2D.Float(10.0f, 10.0f, 20.0f, 30.0f); 

Il prossimo frammento di codice mostra come creare un Ellipse2Doggetto con un'implementazione in virgola mobile a doppia precisione:

 Ellipse2D e2d = new Ellipse2D.Double(10.0, 10.0, 20.0, 30.0); 

È ora possibile richiamare uno qualsiasi dei metodi dichiarati in Floato Doubleinvocando il metodo sul Ellipse2Driferimento restituito (ad esempio e2d.getX()). Allo stesso modo, è possibile richiamare uno qualsiasi dei metodi comuni a Floate Doublee dichiarati in Ellipse2D. Un esempio è:

 e2d.contains(2.0, 3.0) 

Questo completa l'introduzione alle classi membro statiche. Successivamente esamineremo le classi interne, che sono classi membri non statiche, classi locali o classi anonime. Imparerai come lavorare con tutti e tre i tipi di classi interne.

download Ottieni il codice Scarica il codice sorgente per gli esempi in questo tutorial. Creato da Jeff Friesen per JavaWorld.

Inner classes, type 1: Non-static member classes

You've learned previously in the Java 101 series how to declare non-static (instance) fields, methods, and constructors as members of a class. You can also declare non-static member classes, which are nested non-static classes that you declare at the same level as instance fields, methods, and constructors. Consider this example:

 class C { int f; void m() {} C() { f = 2; } class D { // members } } 

Here, we introduce top-level class C with instance field f, instance method m(), a constructor, and non-static member class D. All of these entities are members of class C, which encloses them. However, unlike in the previous example, these instance entities are associated with instances ofC and not with the C class itself.

Each instance of the non-static member class is implicitly associated with an instance of its enclosing class. The non-static member class's instance methods can call the enclosing class's instance methods and access its instance fields. To demonstrate this access, Listing 3 declares an EnclosingClass with a nested NSMClass.

Listing 3. Declare an enclosing class with a nested non-static member class (EnclosingClass.java, version 2)

 class EnclosingClass { private String s; private void m() { System.out.println(s); } class NSMClass { void accessEnclosingClass() { s = "Called from NSMClass's accessEnclosingClass() method"; m(); } } } 

Listing 3 declares a top-level class named EnclosingClass with instance field s, instance method m(), and non-static member class NSMClass. Furthermore, NSMClass declares instance method accessEnclosingClass().

Because accessEnclosingClass() is non-static, NSMClass must be instantiated before this method can be called. This instantiation must take place via an instance of EnclosingClass, as shown in Listing 4.

Listing 4. NSMCDemo.java

 public class NSMCDemo { public static void main(String[] args) { EnclosingClass ec = new EnclosingClass(); ec.new NSMClass().accessEnclosingClass(); } } 

Listing 4's main() method first instantiates EnclosingClass and saves its reference in local variable ec. The main() method then uses the EnclosingClass reference as a prefix to the new operator, in order to instantiate NSMClass. The NSMClass reference is then used to call accessEnclosingClass().

Should I use 'new' with a reference to the enclosing class?

Prefixing new with a reference to the enclosing class is rare. Instead, you will typically call an enclosed class's constructor from within a constructor or an instance method of its enclosing class.

Compile Listings 3 and 4 as follows:

 javac *.java 

When you compile an enclosing class that contains a non-static member class, the compiler creates a class file for the non-static member class whose name consists of its enclosing class's name, a dollar-sign character, and the non-static member class's name. In this case, compiling results in EnclosingClass$NSMCClass.class and EnclosingClass.class.

Run the application as follows:

 java NSMCDemo 

You should observe the following output:

 Called from NSMClass's accessEnclosingClass() method 

When (and how) to qualify 'this'

An enclosed class's code can obtain a reference to its enclosing-class instance by qualifying reserved word this with the enclosing class's name and the member access operator (.). For example, if code within accessEnclosingClass() needed to obtain a reference to its EnclosingClass instance, it would specify EnclosingClass.this. Because the compiler generates code to accomplish this task, specifying this prefix is rare.

Example: Non-static member classes in HashMap

The standard class library includes non-static member classes as well as static member classes. For this example, we'll look at the HashMap class, which is part of the Java Collections Framework in the java.util package. HashMap, which describes a hash table-based implementation of a map, includes several non-static member classes.

For example, the KeySet non-static member class describes a set-based view of the keys contained in the map. The following code fragment relates the enclosed KeySet class to its HashMap enclosing class:

 public class HashMap extends AbstractMap implements Map, Cloneable, Serializable { // various members final class KeySet extends AbstractSet { // various members } // various members } 

The and syntaxes are examples of generics, a suite of related language features that help the compiler enforce type safety. I'll introduce generics in an upcoming Java 101 tutorial. For now, you just need to know that these syntaxes help the compiler enforce the type of key objects that can be stored in the map and in the keyset, and the type of value objects that can be stored in the map.

HashMapfornisce un keySet()metodo che crea un'istanza KeySetquando necessario e restituisce questa istanza o un'istanza memorizzata nella cache. Ecco il metodo completo: