Klasy statyczne i klasy wewnętrzne w Javie

Klasy zagnieżdżone to klasy zadeklarowane jako elementy członkowskie innych klas lub zakresów. Zagnieżdżanie klas to jeden ze sposobów na lepszą organizację kodu. Załóżmy na przykład, że masz niezagnieżdżoną klasę (nazywaną również klasą najwyższego poziomu ), która przechowuje obiekty w tablicy o zmiennym rozmiarze, po której następuje klasa iteratora zwracająca każdy obiekt. Zamiast zanieczyszczać przestrzeń nazw klasy najwyższego poziomu, można zadeklarować klasę iteratora jako element członkowski klasy kolekcji tablic o zmiennym rozmiarze. To działa, ponieważ te dwa elementy są ze sobą ściśle powiązane.

W Javie klasy zagnieżdżone są klasyfikowane jako statyczne klasy składowe lub klasy wewnętrzne . Klasy wewnętrzne to niestatyczne klasy członkowskie, klasy lokalne lub klasy anonimowe. Z tego samouczka dowiesz się, jak pracować ze statycznymi klasami składowymi i trzema typami klas wewnętrznych w kodzie Java.

Unikaj wycieków pamięci w klasach zagnieżdżonych

Zobacz również wskazówkę dotyczącą języka Java związaną z tym samouczkiem, w której dowiesz się, dlaczego klasy zagnieżdżone są podatne na wycieki pamięci.

Klasy statyczne w Javie

W moim samouczku Java 101 Klasy i obiekty w Javie nauczyłeś się, jak deklarować pola statyczne i metody statyczne jako elementy klasy. W inicjalizacji klas i obiektów w Javie nauczyłeś się, jak deklarować statyczne inicjatory jako elementy członkowskie klasy. Teraz dowiesz się, jak deklarować klasy statyczne . Formalnie znane jako statyczne klasy członkowskie , są to klasy zagnieżdżone, które deklarujesz na tym samym poziomie, co inne jednostki statyczne, używając staticsłowa kluczowego. Oto przykład deklaracji statycznej klasy składowej:

 class C { static int f; static void m() {} static { f = 2; } static class D { // members } } 

W tym przykładzie przedstawiono klasę najwyższego poziomu Cz polem fstatycznym, metodą m()statyczną, inicjatorem statycznym i statyczną klasą członkowską D. Zauważ, że Djest członkiem C. Pole statyczne f, metoda statyczna m()i inicjator statyczny są również elementami członkowskimi C. Ponieważ wszystkie te elementy należą do klasy C, jest ona nazywana klasą obejmującą . Klasa Djest znana jako klasa zamknięta .

Zasady dotyczące ogrodzenia i dostępu

Chociaż jest zamknięta, statyczna klasa składowa nie może uzyskać dostępu do pól instancji otaczającej klasy i wywołać jej metody instancji. Może jednak uzyskać dostęp do pól statycznych otaczającej klasy i wywołać jej metody statyczne, nawet te elementy członkowskie, które są zadeklarowane private. Aby zademonstrować, lista 1 deklaruje plik EnclosingClassz zagnieżdżonym SMClass.

Listing 1. Deklarowanie statycznej klasy składowej (EnclosingClass.java, wersja 1)

 class EnclosingClass { private static String s; private static void m1() { System.out.println(s); } static void m2() { SMClass.accessEnclosingClass(); } static class SMClass { static void accessEnclosingClass() { s = "Called from SMClass's accessEnclosingClass() method"; m1(); } void accessEnclosingClass2() { m2(); } } } 

Listing 1 deklaruje klasę najwyższego poziomu o nazwie EnclosingClassz klasy pola s, metody klasy m1()i m2(), i statycznej klasie członkowskim SMClass. SMClassdeklaruje metodę klasy accessEnclosingClass()i metodę instancji accessEnclosingClass2(). Zwróć uwagę na następujące kwestie:

  • m2()„s wywołanie SMClass” s accessEnclosingClass()metody wymaga SMClass.prefiksu ponieważ accessEnclosingClass()jest zadeklarowana static.
  • accessEnclosingClass()jest w stanie uzyskać dostęp EnclosingClass„s sdziedzinie i wywołać jego m1()metodę, choć oba zostały zgłoszone private.

Listing 2 przedstawia kod źródłowy do SMCDemoklasy aplikacji, który pokazuje jak wywołać SMClass„s accessEnclosingClass()metody. Pokazuje również, jak utworzyć wystąpienie SMClassi wywołać accessEnclosingClass2()metodę instancji.

Listing 2. Wywołanie metod statycznej klasy składowej (SMCDemo.java)

 public class SMCDemo { public static void main(String[] args) { EnclosingClass.SMClass.accessEnclosingClass(); EnclosingClass.SMClass smc = new EnclosingClass.SMClass(); smc.accessEnclosingClass2(); } } 

Jak pokazano na liście 2, jeśli chcesz wywołać metodę klasy najwyższego poziomu z zamkniętej klasy, musisz poprzedzić nazwę zamkniętej klasy nazwą jej klasy otaczającej. Podobnie, aby utworzyć instancję klasy zamkniętej, należy poprzedzić nazwę tej klasy nazwą klasy otaczającej. Następnie możesz wywołać metodę instancji w normalny sposób.

Skompiluj listy 1 i 2 w następujący sposób:

 javac *.java 

Podczas kompilowania otaczającej klasy, która zawiera statyczną klasę składową, kompilator tworzy plik klasy dla statycznej klasy składowej, której nazwa składa się z nazwy otaczającej klasy, znaku dolara i nazwy statycznej klasy składowej. W takim przypadku kompilacja skutkuje w EnclosingClass$SMCClass.classi EnclosingClass.class.

Uruchom aplikację w następujący sposób:

 java SMCDemo 

Należy zwrócić uwagę na następujące dane wyjściowe:

 Called from SMClass's accessEnclosingClass() method Called from SMClass's accessEnclosingClass() method 

Przykład: klasy statyczne i Java 2D

Standardowa biblioteka klas Java jest biblioteką środowiska wykonawczego zawierającą pliki klas, które przechowują skompilowane klasy i inne typy referencyjne. Biblioteka zawiera liczne przykłady statycznych klas składowych, z których niektóre znajdują się w klasach kształtów geometrycznych Java 2D znajdujących się w java.awt.geompakiecie. (Dowiesz się o pakietach w następnym samouczku Java 101 ).

Ellipse2DKlasy znaleźć w java.awt.geomopisuje elipsę, która jest wyznaczona przez prostokąt ramek w kategoriach (x, y), w lewym górnym rogu oraz szerokość i wysokość stopniu. Poniżej przedstawiono fragment kodu, że architektura ta klasa jest w oparciu o Floati Doubleklas członkowskich statyczne, co zarówno podklasy Ellipse2D:

 public abstract class Ellipse2D extends RectangularShape { public static class Float extends Ellipse2D implements Serializable { public float x, y, width, height; public Float() { } public Float(float x, float y, float w, float h) { setFrame(x, y, w, h); } public double getX() { return (double) x; } // additional instance methods } public static class Double extends Ellipse2D implements Serializable { public double x, y, width, height; public Double() { } public Double(double x, double y, double w, double h) { setFrame(x, y, w, h); } public double getX() { return x; } // additional instance methods } public boolean contains(double x, double y) { // ... } // additional instance methods shared by Float, Double, and other // Ellipse2D subclasses } 

FloatI Doubleklas rozszerzyć Ellipse2D, zapewniając zmiennoprzecinkowych i zmiennoprzecinkowych podwójnej precyzji Ellipse2Dimplementacje. Deweloperzy używają w Floatcelu zmniejszenia zużycia pamięci, zwłaszcza, że ​​możesz potrzebować tysięcy lub więcej tych obiektów do skonstruowania pojedynczej sceny 2D. Używamy, Doublegdy wymagana jest większa dokładność.

Nie można utworzyć wystąpienia abstrakcyjną Ellipse2Dklasę, ale można instancję albo Floatalbo Double. Możesz również rozszerzyć, Ellipse2Daby opisać niestandardowy kształt oparty na elipsie.

Jako przykład, powiedzmy, że chcesz wprowadzić Circle2Dklasę, której nie ma w java.awt.geompakiecie. Poniższy fragment kodu pokazuje, jak można utworzyć Ellipse2Dobiekt z implementacją zmiennoprzecinkową:

 Ellipse2D e2d = new Ellipse2D.Float(10.0f, 10.0f, 20.0f, 30.0f); 

Następny fragment kodu pokazuje, jak utworzyć Ellipse2Dobiekt z implementacją zmiennoprzecinkową o podwójnej precyzji:

 Ellipse2D e2d = new Ellipse2D.Double(10.0, 10.0, 20.0, 30.0); 

Możesz teraz wywołać dowolną z metod zadeklarowanych w Floatlub Doubleprzez wywołanie metody na zwróconym Ellipse2Dodwołaniu (np e2d.getX().). W ten sam sposób można wywołać dowolną z metod, które są wspólne dla Floati Doublei które są zadeklarowane w Ellipse2D. Przykładem jest:

 e2d.contains(2.0, 3.0) 

To kończy wprowadzenie do statycznych klas składowych. Następnie przyjrzymy się klasom wewnętrznym, które są niestatycznymi klasami składowymi, klasami lokalnymi lub klasami anonimowymi. Dowiesz się, jak pracować ze wszystkimi trzema typami klas wewnętrznych.

pobierz Pobierz kod Pobierz kod źródłowy przykładów w tym samouczku. Stworzone przez Jeffa Friesena dla JavaWorld.

Klasy wewnętrzne, typ 1: niestatyczne klasy składowe

W serii Java 101 nauczyłeś się już, jak deklarować niestatyczne (instancje) pola, metody i konstruktory jako elementy klasy. Możesz również zadeklarować niestatyczne klasy członkowskie , które są zagnieżdżonymi klasami niestatycznymi, które deklarujesz na tym samym poziomie co pola wystąpienia, metody i konstruktory. Rozważmy ten przykład:

 class C { int f; void m() {} C() { f = 2; } class D { // members } } 

Tutaj przedstawiamy klasę najwyższego poziomu Cz polem finstancji, metodą instancji m(), konstruktorem i niestatyczną klasą składową D. Wszystkie te byty są członkami klasy C, która je otacza. Jednak w przeciwieństwie do poprzedniego przykładu te jednostki instancji są skojarzone z instancjami,C a nie z Csamą klasą.

Każde wystąpienie niestatycznej klasy składowej jest niejawnie powiązane z wystąpieniem otaczającej go klasy. Metody instancji niestatycznej klasy składowej mogą wywoływać metody instancji klasy otaczającej i uzyskiwać dostęp do jej pól instancji. Aby zademonstrować ten dostęp, lista 3 deklaruje plik EnclosingClassz zagnieżdżonym NSMClass.

Listing 3. Zadeklaruj otaczającą klasę z zagnieżdżoną niestatyczną klasą składową (EnclosingClass.java, wersja 2)

 class EnclosingClass { private String s; private void m() { System.out.println(s); } class NSMClass { void accessEnclosingClass() { s = "Called from NSMClass's accessEnclosingClass() method"; m(); } } } 

Listing 3 deklaruje klasę najwyższego poziomu o nazwie EnclosingClassz polem swystąpienia, metodą wystąpienia m()i niestatyczną klasą składową NSMClass. Ponadto NSMClassdeklaruje metodę instancji accessEnclosingClass().

Ponieważ accessEnclosingClass()nie jest statyczny, NSMClassnależy utworzyć wystąpienie przed wywołaniem tej metody. Ta instancja musi odbywać się za pośrednictwem instancji programu EnclosingClass, jak pokazano na listingu 4.

Listing 4. NSMCDemo.java

 public class NSMCDemo { public static void main(String[] args) { EnclosingClass ec = new EnclosingClass(); ec.new NSMClass().accessEnclosingClass(); } } 

main()Metoda z listingu 4 najpierw tworzy instancję EnclosingClassi zapisuje jej odwołanie w zmiennej lokalnej ec. Następnie main()metoda używa EnclosingClassodwołania jako prefiksu do newoperatora w celu utworzenia wystąpienia NSMClass. Następnie NSMClassodwołanie jest używane do wywołania accessEnclosingClass().

Czy powinienem używać słowa „nowy” w odniesieniu do klasy obejmującej?

Prefiks newz odwołaniem do otaczającej klasy jest rzadki. Zamiast tego zwykle będziesz wywoływać konstruktor klasy zamkniętej z poziomu konstruktora lub metody wystąpienia klasy otaczającej.

Compile Listings 3 and 4 as follows:

 javac *.java 

When you compile an enclosing class that contains a non-static member class, the compiler creates a class file for the non-static member class whose name consists of its enclosing class's name, a dollar-sign character, and the non-static member class's name. In this case, compiling results in EnclosingClass$NSMCClass.class and EnclosingClass.class.

Run the application as follows:

 java NSMCDemo 

You should observe the following output:

 Called from NSMClass's accessEnclosingClass() method 

When (and how) to qualify 'this'

An enclosed class's code can obtain a reference to its enclosing-class instance by qualifying reserved word this with the enclosing class's name and the member access operator (.). For example, if code within accessEnclosingClass() needed to obtain a reference to its EnclosingClass instance, it would specify EnclosingClass.this. Because the compiler generates code to accomplish this task, specifying this prefix is rare.

Example: Non-static member classes in HashMap

The standard class library includes non-static member classes as well as static member classes. For this example, we'll look at the HashMap class, which is part of the Java Collections Framework in the java.util package. HashMap, which describes a hash table-based implementation of a map, includes several non-static member classes.

For example, the KeySet non-static member class describes a set-based view of the keys contained in the map. The following code fragment relates the enclosed KeySet class to its HashMap enclosing class:

 public class HashMap extends AbstractMap implements Map, Cloneable, Serializable { // various members final class KeySet extends AbstractSet { // various members } // various members } 

The and syntaxes are examples of generics, a suite of related language features that help the compiler enforce type safety. I'll introduce generics in an upcoming Java 101 tutorial. For now, you just need to know that these syntaxes help the compiler enforce the type of key objects that can be stored in the map and in the keyset, and the type of value objects that can be stored in the map.

HashMapudostępnia keySet()metodę, która tworzy wystąpienie w KeySetrazie potrzeby i zwraca to wystąpienie lub wystąpienie w pamięci podręcznej. Oto pełna metoda: