Szukaj…


Wprowadzenie

Korzystając z Javy, programiści mają możliwość zdefiniowania klasy w innej klasie. Taka klasa nazywa się klasą zagnieżdżoną . Klasy zagnieżdżone nazywane są klasami wewnętrznymi, jeśli zostały zadeklarowane jako niestatyczne, jeśli nie, są one po prostu nazywane statycznymi klasami zagnieżdżonymi. Ta strona służy do dokumentowania i dostarczania szczegółowych informacji o przykładach korzystania z klas zagnieżdżonych w Java i klas wewnętrznych.

Składnia

  • public class OuterClass {public class InnerClass {}} // Wewnętrzne klasy mogą być również prywatne
  • public class OuterClass {publiczna klasa statyczna StaticNestedClass {}} // Statycznie zagnieżdżone klasy mogą być również prywatne
  • public void method () {private class LocalClass {}} // Klasy lokalne są zawsze prywatne
  • SomeClass anonymousClassInstance = new SomeClass () {}; // Anonimowych klas wewnętrznych nie można nazwać, dlatego dostęp jest dyskusyjny. Jeśli „SomeClass ()” jest abstrakcyjne, ciało musi implementować wszystkie metody abstrakcyjne.
  • SomeInterface anonymousClassInstance = new SomeInterface () {}; // Ciało musi implementować wszystkie metody interfejsu.

Uwagi

Terminologia i klasyfikacja

Specyfikacja języka Java (JLS) klasyfikuje różne rodzaje klas Java w następujący sposób:

Klasa najwyższego poziomu to klasa, która nie jest klasą zagnieżdżoną.

Klasa zagnieżdżona to każda klasa, której deklaracja występuje w treści innej klasy lub interfejsu.

Klasa wewnętrzna to klasa zagnieżdżona, która nie jest jawnie ani niejawnie zadeklarowana jako statyczna.

Klasa wewnętrzna może być niestatyczną klasą członkowską, klasą lokalną lub klasą anonimową . Klasa elementu interfejsu jest domyślnie statyczna, więc nigdy nie jest uważana za klasę wewnętrzną.

W praktyce programiści określają klasę najwyższego poziomu, która zawiera klasę wewnętrzną, jako „klasę zewnętrzną”. Ponadto istnieje tendencja do używania „klasy zagnieżdżonej” w odniesieniu do tylko (jawnie lub domyślnie) statycznych klas zagnieżdżonych.

Zauważ, że istnieje ścisły związek między anonimowymi klasami wewnętrznymi a lambdami, ale lambda są klasami.

Różnice semantyczne

  • Klasy najwyższego poziomu to „przypadek podstawowy”. Są widoczne dla innych części programu podlegających normalnym zasadom widoczności opartym na semantyce modyfikatora dostępu. Jeśli nie są abstrakcyjne, mogą być tworzone przez dowolny kod, w którym odpowiednie konstruktory są widoczne na podstawie modyfikatorów dostępu.

  • Statyczne zagnieżdżone klasy podlegają tym samym regułom dostępu i tworzenia instancji, co klasy najwyższego poziomu, z dwoma wyjątkami:

    • Zagnieżdżoną klasę można zadeklarować jako private , co czyni ją niedostępną poza otaczającą ją klasą najwyższego poziomu.
    • Zagnieżdżona klasa ma dostęp do private członków otaczającej klasy najwyższego poziomu i wszystkich jej przetestowanych klas.

    To sprawia, że statyczne zagnieżdżone klasy są przydatne, gdy trzeba reprezentować wiele „typów jednostek” w ciasnych granicach abstrakcji; np. gdy zagnieżdżone klasy są używane do ukrywania „szczegółów implementacji”.

  • Klasy wewnętrzne dodają możliwość dostępu do zmiennych niestatycznych zadeklarowanych w obejmujących zakresach:

    • Niestatyczna klasa członka może odwoływać się do zmiennych instancji.
    • Klasa lokalna (zadeklarowana w ramach metody) może również odnosić się do zmiennych lokalnych metody, pod warunkiem że są one final . (W przypadku wersji Java 8 i nowszych mogą one być faktycznie ostateczne ).
    • Anonimowa klasa wewnętrzna może być zadeklarowana w ramach klasy lub metody i może uzyskiwać dostęp do zmiennych zgodnie z tymi samymi regułami.

    Fakt, że instancja klasy wewnętrznej może odwoływać się do zmiennych w instancji klasy obejmującej, ma wpływ na tworzenie instancji. W szczególności instancja zamykająca musi być zapewniona, pośrednio lub jawnie, podczas tworzenia instancji klasy wewnętrznej.

Prosty stos przy użyciu klasy zagnieżdżonej

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

I ich zastosowanie, które (w szczególności) wcale nie uznaje istnienia zagnieżdżonej klasy.

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

Klasy zagnieżdżone statyczne vs.

Podczas tworzenia zagnieżdżonej klasy stajesz przed wyborem statycznej tej zagnieżdżonej klasy:

public class OuterClass1 {

    private static class StaticNestedClass {

    }

}

Lub niestatyczna:

public class OuterClass2 {

    private class NestedClass {

    }

}

U podstaw statyczne klasy zagnieżdżone nie mają otaczającej instancji klasy zewnętrznej, podczas gdy statyczne klasy zagnieżdżone mają. Wpływa to zarówno na to, gdzie / kiedy można utworzyć instancję klasy zagnieżdżonej, jak i na jakie instancje tych klas zagnieżdżonych mają dostęp. Dodanie do powyższego przykładu:

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

Zatem decyzja dotycząca statycznej vs niestatycznej zależy głównie od tego, czy musisz mieć bezpośredni dostęp do pól i metod klasy zewnętrznej, choć ma to również konsekwencje dla tego, kiedy i gdzie możesz zbudować klasę zagnieżdżoną.

Jako ogólną zasadę ustaw statyczne klasy zagnieżdżone, chyba że potrzebujesz dostępu do pól i metod klasy zewnętrznej. Podobnie jak w przypadku pól prywatnych, chyba że są one publiczne, zmniejsza to widoczność dostępną dla zagnieżdżonej klasy (nie zezwalając na dostęp do zewnętrznej instancji), zmniejszając prawdopodobieństwo błędu.

Modyfikatory dostępu do klas wewnętrznych

Pełne wyjaśnienie modyfikatorów dostępu w Javie można znaleźć tutaj . Ale jak wchodzą w interakcje z klasami wewnętrznymi?

public , jak zwykle, daje nieograniczony dostęp do dowolnego zakresu, który może uzyskać dostęp do tego typu.

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

zarówno protected jak i domyślny modyfikator (niczego) zachowują się również zgodnie z oczekiwaniami, tak samo jak w przypadku klas nie zagnieżdżonych.

private ciekawe, private nie ogranicza się do klasy, do której należy. Ogranicza się raczej do jednostki kompilacji - pliku .java. Oznacza to, że klasy zewnętrzne mają pełny dostęp do pól i metod klas wewnętrznych, nawet jeśli są oznaczone jako 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;
    }
}

Sama klasa wewnętrzna może mieć widoczność inną niż public . Po oznaczeniu go jako private lub innego modyfikatora ograniczonego dostępu inne klasy (zewnętrzne) nie będą mogły importować i przypisywać typu. Nadal mogą jednak uzyskać odniesienia do obiektów tego typu.

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

Anonimowe klasy wewnętrzne

Anonimowa klasa wewnętrzna jest formą klasy wewnętrznej, która jest deklarowana i tworzona za pomocą jednej instrukcji. W rezultacie nie ma nazwy klasy, której można by użyć w innym miejscu w programie; tzn. jest anonimowy.

Anonimowe klasy są zwykle używane w sytuacjach, w których musisz być w stanie stworzyć lekką klasę przekazywaną jako parametr. Zazwyczaj odbywa się to za pomocą interfejsu. Na przykład:

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

Ta anonimowa klasa definiuje obiekt Comparator<String> ( CASE_INSENSITIVE ), który porównuje dwa łańcuchy ignorując różnice w wielkości liter.

Inne interfejsy, które są często realizowane za pomocą anonimowych i instancja klasy są Runnable i Callable . Na przykład:

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

Anonimowe klasy wewnętrzne mogą być również oparte na klasach. W tym przypadku anonimowa klasa domyślnie extends istniejącą klasę. Jeśli rozszerzana klasa jest abstrakcyjna, klasa anonimowa musi implementować wszystkie metody abstrakcyjne. Może również zastępować metody nieabstrakcyjne.

Konstruktory

Anonimowa klasa nie może mieć jawnego konstruktora. Zamiast tego definiowany jest domyślny konstruktor, który używa super(...) do przekazania dowolnych parametrów do konstruktora w rozszerzanej klasie. Na przykład:

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

SomeClass konstruktor naszej anonimowej podklasy SomeClass wywoła konstruktor SomeClass który pasuje do sygnatury wywołania SomeClass(int, String) . Jeśli żaden konstruktor nie jest dostępny, pojawi się błąd kompilacji. Wszelkie wyjątki zgłaszane przez dopasowanego konstruktora są również zgłaszane przez domyślny konstruktor.

Oczywiście nie działa to przy rozszerzaniu interfejsu. Gdy tworzysz anonimową klasę z interfejsu, nadklasą klas jest java.lang.Object który ma tylko konstruktor bez argumentów.

Metoda Lokalne klasy wewnętrzne

Klasa napisana w metodzie o nazwie metoda lokalna klasa wewnętrzna . W takim przypadku zakres klasy wewnętrznej jest ograniczony w ramach metody.

Lokalna klasa lokalna dla metody może być tworzona tylko w metodzie, w której klasa wewnętrzna jest zdefiniowana.

Przykład użycia metody lokalnej klasy wewnętrznej:

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

Wykonanie da wynik: Method local inner class 1 .

Dostęp do klasy zewnętrznej z niestatycznej klasy wewnętrznej

Odwołanie do klasy zewnętrznej wykorzystuje nazwę klasy i this

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

Możesz bezpośrednio uzyskać dostęp do pól i metod klasy zewnętrznej.

public class OuterClass {
    private int counter;

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

Ale w przypadku kolizji nazw można użyć odwołania do klasy zewnętrznej.

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

Utwórz instancję niestatycznej klasy wewnętrznej z zewnątrz

Z tej klasy można również utworzyć klasę wewnętrzną widoczną dla każdej klasy zewnętrznej.

Klasa wewnętrzna zależy od klasy zewnętrznej i wymaga odwołania do jej instancji. Aby utworzyć instancję klasy wewnętrznej, new operator musi być wywołany tylko w instancji klasy zewnętrznej.

class OuterClass {

    class InnerClass {
    }
}

class OutsideClass {

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

Zwróć uwagę na użycie jako outer.new . outer.new .



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow