Creare classi innestate

Una classe innestata è una classe definita in un’altra classe, una classe innestata non statica è chiamata anche classe interna. Ci sono 4 tipi di classi innestate:

  • Classe innestata membro, definita allo stesso livello delle property
  • Classe innestata locale, definita all’interno di un metodo
  • Classe innestata anonima, speciale tipo di classe innestata locale senza nome
  • Classe innestata statica definita allo stesso livello delle variabili statiche

Ci sono alcuni vantaggi nell’utilizzare correttamente le classi innestate, possono essere utilizzate per incapsulare classi di utilità all’interno di una classe, rendono più facile la creazioni di classi utilizzate in un sol punto, possono rendere il codice più leggibile ma possono, se usate male, renderlo più complesso.

Classe Innestata membro

Sono definite come membro di una classe (stesso livello dei metodi e property), questa tipologia di classi innestata deve avere le seguenti proprietà :

  • può essere dichiarata public, protected,private e default
  • può estendere o implementare interfacce
  • può essere abstract o final
  • non può dichiarare metodi e variabili statiche
  • può accedere ai membri della classe esterna, anche private

l’ultima proprietà è l’unica degna di attenzioni, significa che una classe innestata può accedere ai valori della classe che la definisce senza dover fare nulla di particolare :

public class Esterna {
  private String saluto = "Ciao";
  protected class Interna {
    public int ripetizioni = 3;
    public void go() {
      for (int i=0 ; i < ripetizioni ; i++) 
        System.out.println(saluto);
    }
  }
  public void chiamaInterna() {
    Interna i = new Interna();
    i.go();
  }
  public static void main(String... args) {
    Esterna e = new Esterna();
    e.chiamaInterna();
  }
}

A vederla sembra come una normale classe definita all’interno di un’altra classe, quello che cambia è che questa classe può accedere ai membri della classe che la contiene, vediamo che nel metodo go() viene utilizzata la variabile saluto definita in Esterna

Non essendo statica, per essere utilizzata ha bisogno di un instanza, vediamo che nel metodo chiamaInterna viene esplicitamente creata un istanza della classe Interna

Vediamo però che abbiamo scritto molto codice per invocare il metodo della nostra classe interna, Java ci permette di utilizzare un sintassi più concisa :

public static void main(String[] args) {
  Esterna e = new Esterna();
  Interna i = e.new Interna(); // crea la classe interna
  i.go();
}

ovviamente abbiamo bisogno di un istanza di Esterna per poter invocare new Interna(), non possiamo semplicemente utilizzare Esterna.new Interna() poichè la classe interna deve essere associata ad un istanza di Esterna.

Cosa succede se nella classe interna definiamo nomi di variabili uguali a quelli della classe contenuta ? lo possiamo fare, esiste un modo per poter accedere con certezza alle variabili della classe interna o esterna.

public class A {
  private int x = 10;
  class B {
    private int x = 20;
    class C {
      private int x = 30;
      public void allTheX() {
      System.out.println(x); // 30
      System.out.println(this.x); // 30
      System.out.println(B.this.x); // 20
      System.out.println(A.this.x); // 10
  } } }
  public static void main(String[] args) {
    A a = new A();
    A.B b = a.new B();
    A.B.C c = b.new C();
    c.allTheX();
  }}

Questo codice è un bel pò complesso, vediamo che ci sono 2 classi innestate, B è innestata ad A e C è innestata a B.

partiamo dall’analisi del metodo main, vediamo che viene instanziata A nel metodo classico, subito dopo viene istanziata B utilizzando la sintassi vista poc’anzi, più complessa è l’istanza di C

Torniamo però all’utilizzo delle variabili, vediamo nelle prime 2 System.out del metodo allTheX qui si accede alla variabile x definita nella classe C, questo è dato dal fatto che nella prima istruzione chiamiamo semplicemente x, nella seconda utilizziamo il prefisso this. che serve per dire a Java di utilizzare x della classe corrente.

Nella terza System.out vogliamo accedere a x della classe B, per farlo utilizziamo B.this.x

Stessa cosa per accedere a x della classe A, lo si fa specificando A.this.x

Classi Innestate locali

Sono classi innestate definite all’interno di un metodo, come per le variabili, la dichiarazione di una classe innestata locale non esiste finchè il metodo non viene invocato e va fuori dallo scope quando il metodo termina.

Hanno le seguenti proprietà :

  • Non specificano il modificatore di accesso
  • Non possono essere static e non possono dichiarare variabili o metodi statici
  • Hanno accesso alle variabili e ai metodi della classe in cui sono definite
  • Non possono accedere a variabili locali del metodo se queste non sono final o effective-final

Vediamo un esempio complicato per eseguire la moltiplicazione di due numeri :

public class Outer {
  private int length = 5;
  public void calculate() {
    final int width = 20;
    class Inner {
      public void multiply() {
        System.out.println(length * width);
      }
    }
    Inner inner = new Inner();
    inner.multiply();
  }
  public static void main(String[] args) {
    Outer outer = new Outer();
    outer.calculate();
  }
}

vediamo che abbiamo definito una classe innestata locale Inner all’interno del metodo calculate, il suo metodo multiply accede alle variabili length e width, length è dichiarata come final quindi ok, width non è dichiarata final ma Java sa che il suo valore sarà effettivamente final all’interno della chiamata del metodo, per cui è corretto.

Il concetto di effective-final è stato introdotto in Java 8, fino a Java 7 il compilatore chiedeva esplicitamente che le variabili fossero dichiarate final per poter essere utilizzate in una local inner class, ma in Java 8 non è necessario.

Il compilatore genera i file class per le classe innestate, e crea un costruttore passandogli le variabili final che può utilizzare, se il compilatore non ha la certezza che il valore di queste variabili non cambia sarà impossibilitato a far questo.

Cerchiamo ora di riconoscere le variabili effective-final in questo esempio :

public void isItFinal() {
  int one = 20;
  int two = one;
  two++;
  int three;
  if ( one == 4) three = 3;
  else three = 4;
  int four = 4;
  class Inner { }
  four = 5;
}

one è effective final, il suo valore è 20 e non cambia,
two non è effective final, viene alterato,
three è effective final perchè anche se nella if il suo valore è assegnato una sola volta,
four non è effective-final

Classi innestate anonime

Una classe innestata anonima è una classe innestata locale senza nome, è dichiarata e istanzata in un unico statement utilizzando l’istruzione new , richiede di estendere una classe esistente o implementare un interfaccia e sono solitamente utilizzate per implementazioni piccole non necessarie in altre parti di codice.

public class Anonima {
  abstract class ClasseAstratta {
    abstract void metodo1();
  }
  public int calcola(int valore) {
    ClasseAstratta c = new ClasseAstratta() {
      void metodo1() {
        System.out.println(valore);
      }
    };
  }
}

Abbiamo definito una classe astratta, nel nostro metodo calcola vediamo che stiamo istanziando ClasseAstratta, ma come è possibile ? è astratta e non può essere instanziata ! invece lo possiamo fare perchè contestualmente stiamo fornendo anche l’implementazione della classe.

Facciamo attenzione al ; ! questo deve esserci perchè la creazione della classe anonima è uno statement ed è da considerarsi una variabile a tutti gli effetti!

Si può fare la stessa cosa con un interfaccia :

public class AnonInner {
  interface VendutiOggi {
    int totaleVenduti();
  }
  public int calcolaVenduti(int prezzo) {
    VendutiOggi v = new VendutiOggi() {
      public int totaleVenduti() {
        return 10;
      }
    };
    return prezzo * v.totaleVenduti();
  }
}

l’unica cosa di diverso tra questa e la classe astratta è il fatto che nella dichiarazione della nostra classe anonima, il metodo dell’interfaccia ha come modificatore di accesso public questo è ovvio, poichè i metodi delle interfacce sono tutti public e abstract

Solo un ultima cosa sulle classi anonime, le puoi definire dove ti servono anche se sono argomenti di un altro metodo :

public class AnonimoTest {
  interface VendutiOggi {
    int totaleVenduti();
  }
  public int calcolaPrezzo(int prezzo, int quantita) {
    return prezzo * quantita;
  }
  public void eseguiCalcolo() {
    return this.calcolaPrezzo(5, new VendutiOggi() { 
      public int totaleVenduti() { return 10; }
    });
  }
}

Vediamo che la nostra classe anonima non è stata memorizzata in nessuna variabile, è stata creata e passata come argomento di un metodo

Classi Innestate statiche

Quest ultimo tipo di classi innestate non sono classi interne, le classi innestate statiche sono definite allo stesso livello delle property e possono essere istanziate senza avere un istanza della classe che le contiene.

Seguono le seguenti regole :

  • creano un namespace, poichè per accedervi è necessario utilizzare il nome della classe che le contiene
  • possono essere private e possono essere incapsulate
  • la classe che la contiene può accedere a campi e metodi della classe innestata statica
public class Esterna {
  static class InternaStatica {
    private int prezzo = 100;
  }
  public static void main(String... args) {
    InternaStatica i = new InternaStatica();
    System.out.println(i.prezzo); 
  }
}

Nel metodo main istanziamo InternaStatica senza aver bisogno di un oggetto della classe Esterna, ricordate le classi innestate ? avevamo bisogno di :

Esterna e = new Esterna();
InternaStatica i = e.new InternaStatica();

Qui non serve poichè è dichiarata static.

Le classe innestatate statiche hanno possono essere importate utilizzando una import regolare :

package uccelli;
public class Tucano {
  public static class Vola {}
}
package guardiano;
import uccelli.Tucano.Vola;
public class GuardaUccelli {
  public Vola volatore;
}

Ma essendo static, si può importare anche con una static import

import static uccelli.Tucano.Vola

Riepilogo

Memorizziamo la seguente tabella :

Classe Interna Membro Classe Interna locale Classe Anonima Classe statica innestata
Permette modificatori di accesso public, protected, private e default no no public, protected private e default
Può estendere classi o implementare interfacce Si Si No, al massimo una superclasse o interfaccia Si
Può essere astratta ? Si Si no Si
Può essere final ? Si Si no Si
Può accedere a property della classe che la contiene ? Si Si Si No (richiede istanza della classe che la contiene)
Può accedere a variabili locali della classe che la contiene ? No Si, se effective-final Si, se effective-final No
Può dichiarare metodi statici ? No No No Si