Java Language
Geneste en binnenklassen
Zoeken…
Invoering
Met behulp van Java kunnen ontwikkelaars een klasse binnen een andere klasse definiëren. Een dergelijke klasse wordt een geneste klasse genoemd . Geneste klassen worden binnenklassen genoemd als ze als niet-statisch worden verklaard, zo niet, dan worden ze eenvoudigweg statische geneste klassen genoemd. Deze pagina documenteert en geeft details met voorbeelden over het gebruik van Java Nested en Inner Classes.
Syntaxis
- public class OuterClass {public class InnerClass {}} // Innerlijke klassen kunnen ook privé zijn
- public class OuterClass {public static class StaticNestedClass {}} // Statische geneste klassen kunnen ook privé zijn
- public void method () {private class LocalClass {}} // Lokale klassen zijn altijd privé
- SomeClass anonymousClassInstance = new SomeClass () {}; // Anonieme binnenklassen kunnen niet worden benoemd, vandaar dat toegang niet mogelijk is. Als 'SomeClass ()' abstract is, moet de body alle abstracte methoden implementeren.
- SomeInterface anonymousClassInstance = new SomeInterface () {}; // De instantie moet alle interfacemethoden implementeren.
Opmerkingen
Terminologie en classificatie
De Java Language Specification (JLS) classificeert de verschillende soorten Java-klassen als volgt:
Een klasse op het hoogste niveau is een klasse die geen geneste klasse is.
Een geneste klasse is elke klasse waarvan de declaratie plaatsvindt in het hoofdgedeelte van een andere klasse of interface.
Een binnenklasse is een geneste klasse die niet expliciet of impliciet statisch wordt verklaard.
Een binnenklasse kan een niet-statische ledenklasse , een lokale klasse of een anonieme klasse zijn . Een lidklasse van een interface is impliciet statisch en wordt dus nooit als een binnenklasse beschouwd.
In de praktijk verwijzen programmeurs naar een klasse op het hoogste niveau die een binnenklasse bevat als de "buitenklasse". Er bestaat ook de neiging om "geneste klasse" te gebruiken om alleen te verwijzen naar (expliciet of impliciet) statische geneste klassen.
Merk op dat er een nauwe relatie bestaat tussen anonieme innerlijke klassen en de lambdas, maar lambdas zijn klassen.
Semantische verschillen
Topklasse klassen zijn het "basisgeval". Ze zijn zichtbaar voor andere delen van een programma en zijn onderworpen aan normale zichtbaarheidsregels op basis van semantiek van toegangsmodificatoren. Als ze niet abstract zijn, kunnen ze worden geïnstantieerd door elke code waarvan de relevante constructors zichtbaar zijn op basis van de toegangsmodificatoren.
Statische geneste klassen volgen dezelfde toegangs- en instantiëringsregels als klassen op het hoogste niveau, met twee uitzonderingen:
- Een geneste klasse kan als
private
worden verklaard, waardoor deze buiten zijn omringende klasse op het hoogste niveau ontoegankelijk is. - Een geneste klasse heeft toegang tot de
private
van de omringende klasse op het hoogste niveau en alle geteste klassen.
Dit maakt statische geneste klassen nuttig wanneer u meerdere "entiteitstypen" binnen een strakke abstractiegrens moet vertegenwoordigen; bijv. wanneer de geneste klassen worden gebruikt om "implementatiedetails" te verbergen.
- Een geneste klasse kan als
Binnenklassen voegen de mogelijkheid toe om toegang te krijgen tot niet-statische variabelen die zijn opgegeven in omhullende bereiken:
- Een niet-statische lidklasse kan verwijzen naar instantievariabelen.
- Een lokale klasse (aangegeven binnen een methode) kan ook verwijzen naar de lokale variabelen van de methode, op voorwaarde dat deze
final
. (Voor Java 8 en hoger kunnen ze effectief definitief zijn .) - Een anonieme binnenklasse kan binnen een klasse of een methode worden gedeclareerd en heeft toegang tot variabelen volgens dezelfde regels.
Het feit dat een instantie van de binnenklasse naar variabelen in een omringende instantie van de klasse kan verwijzen, heeft implicaties voor instantiatie. In het bijzonder moet een omhullende instantie worden verstrekt, impliciet of expliciet, wanneer een instantie van een binnenklasse wordt gemaakt.
Een eenvoudige stapel met een geneste klasse
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;
}
}
En het gebruik daarvan, dat (met name) het bestaan van de geneste klasse helemaal niet erkent.
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() + ", ");
}
}
}
Statische versus niet-statische geneste klassen
Wanneer u een geneste klasse maakt, staat u voor de keuze om die geneste klasse statisch te maken:
public class OuterClass1 {
private static class StaticNestedClass {
}
}
Of niet-statisch:
public class OuterClass2 {
private class NestedClass {
}
}
In de kern hebben statische geneste klassen geen omringende instantie van de buitenste klasse, terwijl niet-statische geneste klassen dat wel hebben. Dit is van invloed op zowel waar / wanneer iemand een geneste klasse mag instantiëren, als tot welke instanties van die geneste klassen toegang is toegestaan. Toevoegen aan het bovenstaande voorbeeld:
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
}
}
Uw beslissing over statisch versus niet-statisch hangt dus vooral af van het feit of u wel of niet rechtstreeks toegang moet hebben tot velden en methoden van de buitenste klasse, hoewel het ook gevolgen heeft voor wanneer en waar u de geneste klasse kunt construeren.
Als vuistregel moet u uw geneste klassen statisch maken, tenzij u toegang moet hebben tot velden en methoden van de buitenste klasse. Vergelijkbaar met het privé maken van uw velden tenzij u ze openbaar nodig hebt, vermindert dit de zichtbaarheid voor de geneste klasse (door geen toegang tot een externe instantie toe te staan), waardoor de kans op fouten wordt verkleind.
Toegang tot modificaties voor binnenklassen
Een volledige uitleg van Access Modifiers in Java is hier te vinden . Maar hoe gaan ze om met Innerlijke klassen?
public
geeft zoals gewoonlijk onbeperkte toegang tot elke scope die toegang heeft tot het type.
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
}
}
zowel protected
als de standaard modifier (van niets) gedragen zich ook zoals verwacht, hetzelfde als voor niet-geneste klassen.
private
, interessant genoeg, beperkt zich niet tot de klas waartoe het behoort. Het beperkt zich eerder tot de compilatie-eenheid - het .java-bestand. Dit betekent dat buitenklassen volledige toegang hebben tot velden en methoden van binnenklassen, zelfs als ze als private
zijn gemarkeerd.
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;
}
}
De Inner Class zelf kan een andere zichtbaarheid hebben dan public
. Door het als private
of een andere beperkte toegangsmodificator te markeren, mogen andere (externe) klassen het type niet importeren en toewijzen. Ze kunnen echter nog steeds naar dergelijke objecten verwijzen.
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
}
}
Anonieme binnenklassen
Een anonieme binnenklasse is een vorm van binnenklasse die met één verklaring wordt verklaard en geïnstantieerd. Als gevolg hiervan is er geen naam voor de klasse die elders in het programma kan worden gebruikt; ie het is anoniem.
Anonieme klassen worden meestal gebruikt in situaties waarin u een lichtgewicht klasse moet kunnen maken om als parameter te worden doorgegeven. Dit gebeurt meestal met een interface. Bijvoorbeeld:
public static Comparator<String> CASE_INSENSITIVE =
new Comparator<String>() {
@Override
public int compare(String string1, String string2) {
return string1.toUpperCase().compareTo(string2.toUpperCase());
}
};
Deze anonieme klasse definieert een Comparator<String>
-object ( CASE_INSENSITIVE
) dat twee tekenreeksen vergelijkt en verschillen voor het geval negeert.
Andere interfaces die vaak worden geïmplementeerd en geconcretiseerd met behulp van anonieme klassen zijn Runnable
en Callable
. Bijvoorbeeld:
// 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"
Anonieme binnenklassen kunnen ook op klassen worden gebaseerd. In dit geval extends
de anonieme klasse impliciet de bestaande klasse uit. Als de klasse die wordt uitgebreid abstract is, moet de anonieme klasse alle abstracte methoden implementeren. Het kan ook niet-abstracte methoden overschrijven.
constructors
Een anonieme klasse kan geen expliciete constructor hebben. In plaats daarvan wordt een impliciete constructor gedefinieerd die super(...)
om parameters door te geven aan een constructor in de klasse die wordt uitgebreid. Bijvoorbeeld:
SomeClass anon = new SomeClass(1, "happiness") {
@Override
public int someMethod(int arg) {
// do something
}
};
De impliciete constructor voor onze anonieme subklasse van SomeClass
roept een constructor van SomeClass
die overeenkomt met de SomeClass(int, String)
. Als er geen constructor beschikbaar is, krijgt u een compilatiefout. Alle uitzonderingen die door de overeenkomende constructor worden gegenereerd, worden ook door de impliciete constructor gegenereerd.
Uiteraard werkt dit niet bij het uitbreiden van een interface. Wanneer u een anonieme klasse maakt vanuit een interface, is de klassen-superklasse java.lang.Object
die alleen een no-args-constructor heeft.
Methode Lokale binnenklassen
Een klasse geschreven binnen een methode die methode lokale binnenklasse wordt genoemd . In dat geval is de reikwijdte van de binnenklasse beperkt binnen de methode.
Een methode-lokale binnenklasse kan alleen worden geïnstantieerd binnen de methode waarin de binnenklasse is gedefinieerd.
Het voorbeeld van het gebruik van de methode lokale binnenklasse:
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();
}
}
Uitvoeren geeft een uitvoer: Method local inner class 1
.
Toegang tot de buitenklasse vanuit een niet-statische binnenklasse
De verwijzing naar de buitenste klasse gebruikt de klassenaam en this
public class OuterClass {
public class InnerClass {
public void method() {
System.out.println("I can access my enclosing class: " + OuterClass.this);
}
}
}
U hebt rechtstreeks toegang tot velden en methoden van de buitenste klasse.
public class OuterClass {
private int counter;
public class InnerClass {
public void method() {
System.out.println("I can access " + counter);
}
}
}
Maar in geval van naambotsing kunt u de buitenste klassenreferentie gebruiken.
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;
}
}
}
Maak een instantie van niet-statische binnenklasse van buitenaf
Een innerlijke klasse die zichtbaar is voor elke externe klasse kan ook vanuit deze klasse worden gemaakt.
De binnenklasse is afhankelijk van de buitenklasse en vereist een verwijzing naar een instantie ervan. Om een instantie van de binnenklasse te maken, hoeft de new
operator alleen een instantie van de buitenklasse te worden aangeroepen.
class OuterClass {
class InnerClass {
}
}
class OutsideClass {
OuterClass outer = new OuterClass();
OuterClass.InnerClass createInner() {
return outer.new InnerClass();
}
}
Let op het gebruik als outer.new
. outer.new
.