Java Language
Classi nidificate e interiori
Ricerca…
introduzione
Usando Java, gli sviluppatori hanno la possibilità di definire una classe all'interno di un'altra classe. Tale classe è chiamata classe annidata . Le classi nidificate sono chiamate classi interne se sono state dichiarate non statiche, in caso contrario vengono semplicemente denominate classi nidificate statiche. Questa pagina è per documentare e fornire dettagli con esempi su come utilizzare le classi nidificate e interne di Java.
Sintassi
- public class OuterClass {public class InnerClass {}} // Le classi interne possono anche essere private
- public class OuterClass {public static class StaticNestedClass {}} // Le classi nidificate statiche possono anche essere private
- public void method () {classe privata LocalClass {}} // Le classi locali sono sempre private
- SomeClass anonymousClassInstance = new SomeClass () {}; // Le classi interne anonime non possono essere nominate, quindi l'accesso è discutibile. Se 'SomeClass ()' è astratto, il corpo deve implementare tutti i metodi astratti.
- SomeInterface anonymousClassInstance = new SomeInterface () {}; // Il corpo deve implementare tutti i metodi di interfaccia.
Osservazioni
Terminologia e classificazione
La JLS (Java Language Specification) classifica i diversi tipi di classe Java come segue:
Una classe di primo livello è una classe che non è una classe nidificata.
Una classe annidata è una classe la cui dichiarazione si verifica all'interno del corpo di un'altra classe o interfaccia.
Una classe interna è una classe nidificata che non è dichiarata in modo esplicito o implicito statico.
Una classe interna può essere una classe membro non statica , una classe locale o una classe anonima . Una classe membro di un'interfaccia è implicitamente statica, quindi non è mai considerata una classe interiore.
In pratica i programmatori si riferiscono a una classe di livello superiore che contiene una classe interna come "classe esterna". Inoltre, c'è una tendenza ad usare "classe nidificata" per riferirsi solo a classi nidificate statiche (esplicitamente o implicitamente).
Si noti che esiste una stretta relazione tra le classi interne anonime e le lambda, ma le lambda sono classi.
Differenze semantiche
Le classi di livello superiore sono il "caso base". Sono visibili ad altre parti di un programma soggette a normali regole di visibilità basate sulla semantica del modificatore di accesso. Se non astratti, possono essere istanziati da qualsiasi codice che mostra dove i costruttori rilevanti sono visibili in base ai modificatori di accesso.
Le classi nidificate statiche seguono le stesse regole di accesso e istanziazione delle classi di primo livello, con due eccezioni:
- Una classe nidificata può essere dichiarata come
private
, il che la rende inaccessibile al di fuori della classe di livello superiore che la racchiude. - Una classe nidificata ha accesso ai membri
private
della classe di livello superiore che la include e di tutta la sua classe testata.
Ciò rende le classi nidificate statiche utili quando è necessario rappresentare più "tipi di entità" all'interno di uno stretto limite di astrazione; ad esempio quando le classi nidificate vengono utilizzate per nascondere "dettagli di implementazione".
- Una classe nidificata può essere dichiarata come
Le classi interne aggiungono la possibilità di accedere a variabili non statiche dichiarate negli ambiti che racchiudono:
- Una classe membro non statica può fare riferimento a variabili di istanza.
- Una classe locale (dichiarata all'interno di un metodo) può anche fare riferimento alle variabili locali del metodo, a condizione che siano
final
. (Per Java 8 e versioni successive, possono essere effettivamente definitive .) - Una classe interna anonima può essere dichiarata all'interno di una classe o di un metodo e può accedere alle variabili in base alle stesse regole.
Il fatto che un'istanza di classe interna possa fare riferimento a variabili in un'istanza di classe che include ha implicazioni per l'istanziazione. In particolare, deve essere fornita un'istanza di inclusione, implicitamente o esplicitamente, quando viene creata un'istanza di una classe interna.
Una pila semplice che utilizza una classe annidata
public class IntStack {
private IntStackNode head;
// IntStackNode is the inner class of the class IntStack
// Each instance of this inner class functions as one link in the
// Overall stack that it helps to represent
private static class IntStackNode {
private int val;
private IntStackNode next;
private IntStackNode(int v, IntStackNode n) {
val = v;
next = n;
}
}
public IntStack push(int v) {
head = new IntStackNode(v, head);
return this;
}
public int pop() {
int x = head.val;
head = head.next;
return x;
}
}
E il suo uso, che (in particolare) non riconosce affatto l'esistenza della classe annidata.
public class Main {
public static void main(String[] args) {
IntStack s = new IntStack();
s.push(4).push(3).push(2).push(1).push(0);
//prints: 0, 1, 2, 3, 4,
for(int i = 0; i < 5; i++) {
System.out.print(s.pop() + ", ");
}
}
}
Classi nidificate statiche e non statiche
Quando si crea una classe nidificata, si affronta una scelta di avere quella statica della classe annidata:
public class OuterClass1 {
private static class StaticNestedClass {
}
}
O non statico:
public class OuterClass2 {
private class NestedClass {
}
}
Al suo interno, le classi nidificate statiche non hanno un'istanza circostante della classe esterna, mentre le classi nidificate non statiche lo fanno. Ciò influenza sia quando / quando è consentito creare un'istanza di una classe nidificata, sia quali istanze di quelle classi nidificate sono autorizzate ad accedere. Aggiungendo all'esempio precedente:
public class OuterClass1 {
private int aField;
public void aMethod(){}
private static class StaticNestedClass {
private int innerField;
private StaticNestedClass() {
innerField = aField; //Illegal, can't access aField from static context
aMethod(); //Illegal, can't call aMethod from static context
}
private StaticNestedClass(OuterClass1 instance) {
innerField = instance.aField; //Legal
}
}
public static void aStaticMethod() {
StaticNestedClass s = new StaticNestedClass(); //Legal, able to construct in static context
//Do stuff involving s...
}
}
public class OuterClass2 {
private int aField;
public void aMethod() {}
private class NestedClass {
private int innerField;
private NestedClass() {
innerField = aField; //Legal
aMethod(); //Legal
}
}
public void aNonStaticMethod() {
NestedClass s = new NestedClass(); //Legal
}
public static void aStaticMethod() {
NestedClass s = new NestedClass(); //Illegal. Can't construct without surrounding OuterClass2 instance.
//As this is a static context, there is no surrounding OuterClass2 instance
}
}
Pertanto, la decisione di statico o non statico dipende principalmente dal fatto che sia necessario o meno poter accedere direttamente ai campi e ai metodi della classe esterna, sebbene abbia anche conseguenze su quando e dove è possibile costruire la classe nidificata.
Come regola generale, rendere statiche le classi nidificate, a meno che non sia necessario accedere ai campi e ai metodi della classe esterna. Come rendere privati i propri campi a meno che non siano necessari per la pubblica, ciò riduce la visibilità disponibile per la classe nidificata (non consentendo l'accesso a un'istanza esterna), riducendo la probabilità di errore.
Modificatori di accesso per le classi interne
Una spiegazione completa dei Modificatori di accesso in Java può essere trovata qui . Ma come interagiscono con le classi interne?
public
, come al solito, dà accesso illimitato a qualsiasi ambito in grado di accedere al tipo.
public class OuterClass {
public class InnerClass {
public int x = 5;
}
public InnerClass createInner() {
return new InnerClass();
}
}
public class SomeOtherClass {
public static void main(String[] args) {
int x = new OuterClass().createInner().x; //Direct field access is legal
}
}
entrambi protected
e il modificatore di default (del nulla) si comportano come previsto, come fanno per le classi non annidate.
private
, abbastanza interessante, non limita la classe a cui appartiene. Piuttosto, limita l'unità di compilazione - il file .java. Ciò significa che le classi Outer hanno pieno accesso ai campi e ai metodi della classe Inner, anche se sono contrassegnati come private
.
public class OuterClass {
public class InnerClass {
private int x;
private void anInnerMethod() {}
}
public InnerClass aMethod() {
InnerClass a = new InnerClass();
a.x = 5; //Legal
a.anInnerMethod(); //Legal
return a;
}
}
La stessa classe interna può avere una visibilità diversa dal public
. Contrassegnandolo come private
o con un altro modificatore di accesso limitato, altre classi (esterne) non potranno importare e assegnare il tipo. Possono comunque ottenere riferimenti a oggetti di quel tipo, comunque.
public class OuterClass {
private class InnerClass{}
public InnerClass makeInnerClass() {
return new InnerClass();
}
}
public class AnotherClass {
public static void main(String[] args) {
OuterClass o = new OuterClass();
InnerClass x = o.makeInnerClass(); //Illegal, can't find type
OuterClass.InnerClass x = o.makeInnerClass(); //Illegal, InnerClass has visibility private
Object x = o.makeInnerClass(); //Legal
}
}
Classi interiori anonime
Una classe interiore anonima è una forma di classe interiore che viene dichiarata e istanziata con una singola affermazione. Di conseguenza, non esiste un nome per la classe che possa essere usato altrove nel programma; cioè è anonimo.
Le classi anonime vengono in genere utilizzate in situazioni in cui è necessario essere in grado di creare una classe leggera da passare come parametro. Questo è in genere fatto con un'interfaccia. Per esempio:
public static Comparator<String> CASE_INSENSITIVE =
new Comparator<String>() {
@Override
public int compare(String string1, String string2) {
return string1.toUpperCase().compareTo(string2.toUpperCase());
}
};
Questa classe anonima definisce un oggetto Comparator<String>
( CASE_INSENSITIVE
) che confronta due stringhe ignorando le differenze nel caso.
Altre interfacce che vengono spesso implementate e istanziate utilizzando classi anonime sono Runnable
e Callable
. Per esempio:
// An anonymous Runnable class is used to provide an instance that the Thread
// will run when started.
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello world");
}
});
t.start(); // Prints "Hello world"
Anche le classi interne anonime possono essere basate su classi. In questo caso, la classe anonima extends
implicitamente la classe esistente. Se la classe estesa è astratta, la classe anonima deve implementare tutti i metodi astratti. Può anche sovrascrivere i metodi non astratti.
Costruttori
Una classe anonima non può avere un costruttore esplicito. Invece, viene definito un costruttore implicito che utilizza super(...)
per passare qualsiasi parametro a un costruttore nella classe che viene estesa. Per esempio:
SomeClass anon = new SomeClass(1, "happiness") {
@Override
public int someMethod(int arg) {
// do something
}
};
Il costruttore implicito per la nostra sottoclasse anonima di SomeClass
chiamerà un costruttore di SomeClass
che corrisponde alla firma di chiamata SomeClass(int, String)
. Se nessun costruttore è disponibile, si otterrà un errore di compilazione. Eventuali eccezioni lanciate dal costruttore abbinato vengono anche lanciate dal costruttore implicito.
Naturalmente, questo non funziona quando si estende un'interfaccia. Quando si crea una classe anonima da un'interfaccia, la superclasse delle classi è java.lang.Object
che ha solo un costruttore no-args.
Metodo Class Inner Classes
Una classe scritta all'interno di un metodo chiamato metodo inner class locale . In tal caso l'ambito della classe interna è limitato all'interno del metodo.
Una classe interna del metodo locale può essere istanziata solo all'interno del metodo in cui è definita la classe interna.
L'esempio dell'uso del metodo inner class locale:
public class OuterClass {
private void outerMethod() {
final int outerInt = 1;
// Method Local Inner Class
class MethodLocalInnerClass {
private void print() {
System.out.println("Method local inner class " + outerInt);
}
}
// Accessing the inner class
MethodLocalInnerClass inner = new MethodLocalInnerClass();
inner.print();
}
public static void main(String args[]) {
OuterClass outer = new OuterClass();
outer.outerMethod();
}
}
L'esecuzione darà un risultato: Method local inner class 1
.
Accedere alla classe esterna da una classe interiore non statica
Il riferimento alla classe esterna usa il nome della classe e this
public class OuterClass {
public class InnerClass {
public void method() {
System.out.println("I can access my enclosing class: " + OuterClass.this);
}
}
}
È possibile accedere direttamente ai campi e ai metodi della classe esterna.
public class OuterClass {
private int counter;
public class InnerClass {
public void method() {
System.out.println("I can access " + counter);
}
}
}
Ma in caso di collisione di nomi è possibile utilizzare il riferimento di classe esterno.
public class OuterClass {
private int counter;
public class InnerClass {
private int counter;
public void method() {
System.out.println("My counter: " + counter);
System.out.println("Outer counter: " + OuterClass.this.counter);
// updating my counter
counter = OuterClass.this.counter;
}
}
}
Crea un'istanza di classe interna non statica dall'esterno
Una classe interna che è visibile a qualsiasi classe esterna può essere creata anche da questa classe.
La classe interiore dipende dalla classe esterna e richiede un riferimento a un'istanza di essa. Per creare un'istanza della classe interna, il new
operatore deve solo essere richiamato su un'istanza della classe esterna.
class OuterClass {
class InnerClass {
}
}
class OutsideClass {
OuterClass outer = new OuterClass();
OuterClass.InnerClass createInner() {
return outer.new InnerClass();
}
}
Si noti l'utilizzo come outer.new
.