Classi annidate in Java

1. Introduzione

Questo tutorial è un'introduzione rapida e precisa alle classi nidificate nel linguaggio Java.

In poche parole, Java ci consente di definire classi all'interno di altre classi. Le classi annidate ci consentono di raggruppare logicamente le classi che vengono utilizzate solo in un posto, scrivere codice più leggibile e gestibile e aumentare l'incapsulamento.

Prima di iniziare, diamo un'occhiata ai diversi tipi di classi annidate disponibili nella lingua:

  • Classi annidate statiche
  • Classi annidate non statiche
  • Classi locali
  • Classi anonime

Nelle prossime sezioni, discuteremo ciascuno di questi in dettaglio.

2. Classi annidate statiche

Ecco alcuni punti da ricordare sulle classi annidate statiche:

  • Come con i membri statici, questi appartengono alla loro classe che lo racchiude e non a un'istanza della classe
  • Possono avere tutti i tipi di modificatori di accesso nella loro dichiarazione
  • Hanno accesso solo ai membri statici nella classe che lo racchiude
  • Possono definire membri sia statici che non statici

Vediamo come possiamo dichiarare una classe annidata statica:

public class Enclosing { private static int x = 1; public static class StaticNested { private void run() { // method implementation } } @Test public void test() { Enclosing.StaticNested nested = new Enclosing.StaticNested(); nested.run(); } }

3. Classi annidate non statiche

Successivamente, ecco alcuni punti rapidi da ricordare sulle classi nidificate non statiche:

  • Sono anche chiamate classi interne
  • Possono avere tutti i tipi di modificatori di accesso nella loro dichiarazione
  • Proprio come le variabili di istanza e i metodi, le classi interne sono associate a un'istanza della classe che lo racchiude
  • Hanno accesso a tutti i membri della classe che li racchiude, indipendentemente dal fatto che siano statici o non statici
  • Possono definire solo membri non statici

Ecco come possiamo dichiarare una classe interna:

public class Outer { public class Inner { // ... } }

Se dichiariamo una classe nidificata con un modificatore static , allora è un membro statico. Altrimenti, è una classe interna. Anche se sintatticamente la differenza è solo una singola parola chiave (cioè statica ), semanticamente c'è un'enorme differenza tra questi tipi di classi annidate. Le istanze della classe interna sono legate a quelle della classe che la racchiude e quindi hanno accesso ai loro membri. Dobbiamo essere consapevoli di questo problema quando selezioniamo se rendere una classe nidificata come interna.

Per istanziare una classe interna, dobbiamo prima istanziare la sua classe che lo racchiude.

Vediamo come possiamo farlo:

Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();

Nelle prossime sottosezioni, mostreremo alcuni tipi speciali di classi interne.

3.1. Classi locali

Le classi locali sono un tipo speciale di classi interne, in cui la classe è definita all'interno di un metodo o di un blocco di ambito.

Vediamo alcuni punti da ricordare su questo tipo di classe:

  • Non possono avere modificatori di accesso nella loro dichiarazione
  • Hanno accesso a membri statici e non statici nel contesto di inclusione
  • Possono definire solo membri dell'istanza

Ecco un rapido esempio:

public class NewEnclosing { void run() { class Local { void run() { // method implementation } } Local local = new Local(); local.run(); } @Test public void test() { NewEnclosing newEnclosing = new NewEnclosing(); newEnclosing.run(); } }

3.2. Classi anonime

Le classi anonime possono essere utilizzate per definire un'implementazione di un'interfaccia o una classe astratta senza dover creare un'implementazione riutilizzabile.

Elenchiamo alcuni punti da ricordare sulle classi anonime:

  • Non possono avere modificatori di accesso nella loro dichiarazione
  • Hanno accesso a membri statici e non statici nel contesto di inclusione
  • Possono definire solo membri dell'istanza
  • Sono l'unico tipo di classi annidate che non possono definire costruttori o estendere / implementare altre classi o interfacce

Per definire una classe anonima, definiamo prima una semplice classe astratta:

abstract class SimpleAbstractClass { abstract void run(); }

Vediamo ora come possiamo definire una classe anonima:

public class AnonymousInnerUnitTest { @Test public void whenRunAnonymousClass_thenCorrect() { SimpleAbstractClass simpleAbstractClass = new SimpleAbstractClass() { void run() { // method implementation } }; simpleAbstractClass.run(); } }

Per maggiori dettagli, potremmo trovare utile il nostro tutorial sulle classi anonime in Java.

4. Shadowing

La dichiarazione dei membri di una classe interna ombreggia quelli della classe che la racchiude se hanno lo stesso nome.

In questo caso, la parola chiave this fa riferimento alle istanze della classe nidificata ed è possibile fare riferimento ai membri della classe esterna utilizzando il nome della classe esterna.

Vediamo un rapido esempio:

public class NewOuter { int a = 1; static int b = 2; public class InnerClass { int a = 3; static final int b = 4; public void run() { System.out.println("a = " + a); System.out.println("b = " + b); System.out.println("NewOuterTest.this.a = " + NewOuter.this.a); System.out.println("NewOuterTest.b = " + NewOuter.b); System.out.println("NewOuterTest.this.b = " + NewOuter.this.b); } } @Test public void test() { NewOuter outer = new NewOuter(); NewOuter.InnerClass inner = outer.new InnerClass(); inner.run(); } }

5. Serializzazione

Per evitare un'eccezione java.io.NotSerializableException durante il tentativo di serializzare una classe annidata, dovremmo:

  • Dichiarare la classe nidificata come statica
  • Rendi Serializable sia la classe nidificata che la classe che la racchiude

6. Conclusione

In questo articolo, abbiamo visto cosa sono le classi annidate e i loro diversi tipi. Abbiamo anche esaminato come la visibilità del campo ei modificatori di accesso differiscono tra questi diversi tipi.

Come sempre, l'implementazione completa di questo tutorial può essere trovata su GitHub.