Suche…


Einführung

Mit Java können Entwickler eine Klasse in einer anderen Klasse definieren. Eine solche Klasse wird als verschachtelte Klasse bezeichnet . Verschachtelte Klassen werden als Innere Klassen bezeichnet, wenn sie als nicht statisch deklariert wurden. Andernfalls werden sie einfach als statische verschachtelte Klassen bezeichnet. Diese Seite dient zum Dokumentieren und Bereitstellen von Details mit Beispielen zur Verwendung von geschachtelten und inneren Klassen von Java.

Syntax

  • public class OuterClass {public class InnerClass {}} // Innere Klassen können auch privat sein
  • public class OuterClass {public static class StaticNestedClass {}} // Statisch verschachtelte Klassen können auch privat sein
  • public void method () {private class LocalClass {}} // Lokale Klassen sind immer privat
  • SomeClass anonymousClassInstance = new SomeClass () {}; // Anonyme innere Klassen können nicht benannt werden, daher ist der Zugriff moot. Wenn 'SomeClass ()' abstrakt ist, muss der Rumpf alle abstrakten Methoden implementieren.
  • SomeInterface anonymousClassInstance = new SomeInterface () {}; // Der Body muss alle Schnittstellenmethoden implementieren.

Bemerkungen

Terminologie und Klassifizierung

Die Java Language Specification (JLS) klassifiziert die verschiedenen Arten von Java-Klassen wie folgt:

Eine Klasse der obersten Ebene ist eine Klasse, die keine verschachtelte Klasse ist.

Eine verschachtelte Klasse ist eine Klasse, deren Deklaration im Hauptteil einer anderen Klasse oder Schnittstelle auftritt.

Eine innere Klasse ist eine verschachtelte Klasse, die nicht explizit oder implizit als statisch deklariert ist.

Eine innere Klasse kann eine nicht statische Member-Klasse , eine lokale Klasse oder eine anonyme Klasse sein . Eine Member-Klasse eines Interfaces ist implizit statisch und wird daher niemals als innere Klasse betrachtet.

In der Praxis beziehen sich Programmierer auf eine Klasse der obersten Ebene, die eine innere Klasse als "äußere Klasse" enthält. Es besteht auch die Tendenz, "verschachtelte Klasse" zu verwenden, um nur auf (explizit oder implizit) statische verschachtelte Klassen zu verweisen.

Beachten Sie, dass zwischen anonymen inneren Klassen und den Lambdas eine enge Beziehung besteht, aber Lambdas sind Klassen.

Semantische Unterschiede

  • Top-Level-Klassen sind der "Basisfall". Sie sind für andere Teile eines Programms sichtbar, die normalen Sichtbarkeitsregeln unterliegen, die auf der Zugriffsmodifikationssemantik basieren. Wenn sie nicht abstrakt sind, können sie durch jeden Code instanziiert werden, bei dem die relevanten Konstruktoren basierend auf den Zugriffsmodifizierern sichtbar sind.

  • Statische verschachtelte Klassen folgen mit zwei Ausnahmen denselben Zugriffs- und Instantiierungsregeln wie Klassen der obersten Ebene:

    • Eine verschachtelte Klasse kann als private deklariert werden, sodass sie außerhalb der sie umgebenden Top-Level-Klasse nicht zugänglich ist.
    • Eine verschachtelte Klasse hat Zugriff auf die private Mitglieder der umgebenden Klasse der obersten Ebene und auf alle getesteten Klassen.

    Dies macht statische verschachtelte Klassen nützlich, wenn Sie mehrere "Entitätstypen" innerhalb einer engen Abstraktionsgrenze darstellen müssen. ZB wenn die verschachtelten Klassen zum Ausblenden von "Implementierungsdetails" verwendet werden.

  • Innere Klassen bieten die Möglichkeit, auf nicht statische Variablen zuzugreifen, die in umschließenden Bereichen deklariert sind:

    • Eine nicht statische Member-Klasse kann sich auf Instanzvariablen beziehen.
    • Eine lokale Klasse (in einer Methode deklariert) kann auch auf die lokalen Variablen der Methode verweisen, vorausgesetzt, sie sind final . (Für Java 8 und höher können sie effektiv final sein .)
    • Eine anonyme innere Klasse kann entweder innerhalb einer Klasse oder einer Methode deklariert werden und auf Variablen nach denselben Regeln zugreifen.

    Die Tatsache, dass eine Instanz der inneren Klasse auf Variablen in einer umgebenden Klasseninstanz verweisen kann, hat Auswirkungen auf die Instantiierung. Insbesondere muss eine einschließende Instanz implizit oder explizit bereitgestellt werden, wenn eine Instanz einer inneren Klasse erstellt wird.

Ein einfacher Stapel mit einer verschachtelten 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;
    }
}

Und deren Verwendung, die (insbesondere) die Existenz der verschachtelten Klasse überhaupt nicht anerkennt.

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 vs. nicht statische verschachtelte Klassen

Wenn Sie eine verschachtelte Klasse erstellen, haben Sie die Wahl, diese verschachtelte Klasse statisch zu haben:

public class OuterClass1 {

    private static class StaticNestedClass {

    }

}

Oder nicht statisch:

public class OuterClass2 {

    private class NestedClass {

    }

}

Im Grunde haben statische verschachtelte Klassen keine umgebende Instanz der äußeren Klasse, nicht statische verschachtelte Klassen. Dies betrifft sowohl, wo / wann man eine verschachtelte Klasse instanziieren darf, als auch, auf welche Instanzen dieser verschachtelten Klassen zugegriffen werden darf. Hinzufügen zum obigen Beispiel:

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
    }
}

Daher hängt Ihre Entscheidung zwischen statisch und nicht statisch hauptsächlich davon ab, ob Sie direkt auf Felder und Methoden der äußeren Klasse zugreifen müssen. Dies hat jedoch auch Auswirkungen darauf, wann und wo Sie die verschachtelte Klasse erstellen können.

Als Faustregel sollten Sie Ihre verschachtelten Klassen statisch machen, sofern Sie nicht auf Felder und Methoden der äußeren Klasse zugreifen müssen. Ähnlich wie bei der Privatisierung Ihrer Felder, es sei denn, Sie müssen sie öffentlich machen, verringert dies die Sichtbarkeit der verschachtelten Klasse (indem sie keinen Zugriff auf eine äußere Instanz zulässt), wodurch die Fehlerwahrscheinlichkeit verringert wird.

Zugriffsmodifizierer für innere Klassen

Eine vollständige Erklärung der Zugriffsmodifizierer in Java finden Sie hier . Aber wie interagieren sie mit den inneren Klassen?

public gibt wie üblich uneingeschränkten Zugriff auf alle Bereiche frei, die auf den Typ zugreifen können.

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
    }
}

Sowohl protected als auch der Standardmodifikator (von nothing) verhalten sich erwartungsgemäß genauso wie bei nicht verschachtelten Klassen.

private sich interessanterweise nicht auf die Klasse, zu der er gehört. Sie beschränkt sich vielmehr auf die Kompilierungseinheit - die .java-Datei. Dies bedeutet, dass äußere Klassen uneingeschränkten Zugriff auf Felder und Methoden der inneren Klasse haben, selbst wenn sie als private gekennzeichnet sind.

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;
    }
}

Die innere Klasse selbst kann eine andere Sichtbarkeit als public . Durch das Markieren als private oder einen anderen Zugriffsmodifikator mit eingeschränktem Zugriff können andere (externe) Klassen den Typ nicht importieren und zuweisen. Sie können jedoch immer noch Verweise auf Objekte dieses Typs erhalten.

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
    }
}

Anonyme innere Klassen

Eine anonyme innere Klasse ist eine Form der inneren Klasse, die mit einer einzigen Anweisung deklariert und instanziiert wird. Infolgedessen gibt es keinen Namen für die Klasse, der an anderer Stelle im Programm verwendet werden kann. dh es ist anonym.

Anonyme Klassen werden normalerweise in Situationen verwendet, in denen Sie eine leichte Klasse erstellen müssen, die als Parameter übergeben werden soll. Dies erfolgt normalerweise mit einer Schnittstelle. Zum Beispiel:

public static Comparator<String> CASE_INSENSITIVE =
        new Comparator<String>() {
            @Override
            public int compare(String string1, String string2) {
                return string1.toUpperCase().compareTo(string2.toUpperCase());
            }
        };

Diese anonyme Klasse definiert ein Comparator<String> -Objekt ( CASE_INSENSITIVE ), das zwei Zeichenfolgen vergleicht und Unterschiede in diesem Fall ignoriert.

Andere Schnittstellen, die häufig mithilfe anonymer Klassen implementiert und instanziiert werden, sind Runnable und Callable . Zum Beispiel:

// 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"

Anonyme innere Klassen können auch auf Klassen basieren. In diesem Fall wird die anonyme Klasse implizit extends die bestehende Klasse. Wenn die Klasse, die erweitert wird, abstrakt ist, muss die anonyme Klasse alle abstrakten Methoden implementieren. Es können auch nicht abstrakte Methoden überschrieben werden.

Konstrukteure

Eine anonyme Klasse kann keinen expliziten Konstruktor haben. Stattdessen wird ein impliziter Konstruktor definiert, der super(...) , um Parameter an einen Konstruktor in der Klasse zu übergeben, die erweitert wird. Zum Beispiel:

SomeClass anon = new SomeClass(1, "happiness") {
            @Override
            public int someMethod(int arg) {
                // do something
            }
        };

Der implizite Konstruktor für unsere anonyme Unterklasse SomeClass ruft einen Konstruktor von SomeClass , der mit der SomeClass(int, String) . Wenn kein Konstruktor verfügbar ist, wird ein Kompilierungsfehler angezeigt. Alle Ausnahmen, die vom übereinstimmenden Konstruktor ausgelöst werden, werden auch vom impliziten Konstruktor ausgelöst.

Das funktioniert natürlich nicht, wenn Sie eine Schnittstelle erweitern. Wenn Sie eine anonyme Klasse aus einer Schnittstelle erstellen, lautet die Superklasse der Klassen java.lang.Object die nur über einen No-Args-Konstruktor verfügt.

Methode Lokale innere Klassen

Eine Klasse, die innerhalb einer Methode geschrieben wurde und als lokale Klasse der Klasse bezeichnet wird . In diesem Fall ist der Umfang der inneren Klasse innerhalb der Methode eingeschränkt.

Eine methodenlokale innere Klasse kann nur innerhalb der Methode instanziiert werden, in der die innere Klasse definiert ist.

Das Beispiel für die Verwendung der Methode local inner class:

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();
    }
}

Beim Ausführen wird eine Ausgabe ausgegeben: Method local inner class 1 .

Zugriff auf die äußere Klasse von einer nicht statischen inneren Klasse

Der Verweis auf die äußere Klasse verwendet den Klassennamen und this

public class OuterClass {
    public class InnerClass {
        public void method() {
            System.out.println("I can access my enclosing class: " + OuterClass.this);
        }
    }
}

Sie können direkt auf Felder und Methoden der äußeren Klasse zugreifen.

public class OuterClass {
    private int counter;

    public class InnerClass {
        public void method() {
            System.out.println("I can access " + counter);
        }
    }
}

Bei Namenskollisionen können Sie jedoch die äußere Klassenreferenz verwenden.

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;
        }
    }
}

Instanz einer nicht statischen inneren Klasse von außen erstellen

Eine innere Klasse, die für alle externen Klassen sichtbar ist, kann auch aus dieser Klasse erstellt werden.

Die innere Klasse hängt von der äußeren Klasse ab und erfordert einen Verweis auf eine Instanz davon. Um eine Instanz der inneren Klasse zu erstellen, muss der new Operator nur für eine Instanz der äußeren Klasse aufgerufen werden.

class OuterClass {

    class InnerClass {
    }
}

class OutsideClass {

    OuterClass outer = new OuterClass();
    
    OuterClass.InnerClass createInner() {
        return outer.new InnerClass();
    }
}

Beachten Sie die Verwendung als outer.new .



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow