Статични класове и вътрешни класове в Java

Вложените класове са класове, които са декларирани като членове на други класове или обхвати. Влагането на класове е един от начините за по-добра организация на вашия код. Например, да речем, че имате несгънат клас (известен също като клас от най-високо ниво ), който съхранява обекти в мащабируем масив, последван от клас итератор, който връща всеки обект. Вместо да замърсявате пространството от имена на класа от най-високо ниво, можете да декларирате класа на итератора като член на класа на колекцията от масив, който може да се преоразмерява. Това работи, защото двете са тясно свързани.

В Java вложените класове са категоризирани като статични членове или като вътрешни класове . Вътрешните класове са нестатични класове членове, локални класове или анонимни класове. В този урок ще научите как да работите със статични класове членове и трите типа вътрешни класове във вашия Java код.

Избягвайте изтичане на памет в вложени класове

Вижте и съвета за Java, свързан с този урок, където ще научите защо вложени класове са уязвими от изтичане на памет.

Статични класове в Java

В моя урок за Java 101 Класове и обекти в Java научихте как да декларирате статични полета и статични методи като членове на клас. В инициализацията на клас и обект в Java научихте как да декларирате статични инициализатори като членове на клас. Сега ще научите как да декларирате статични класове . Формално известни като статични класове членове , това са вложени класове, които декларирате на същото ниво като тези други статични обекти, използвайки staticключовата дума. Ето пример за статична декларация за клас на член:

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

Този пример въвежда клас Cот най-високо ниво със статично поле f, статичен метод m(), статичен инициализатор и клас статичен член D. Забележете, че Dе член на C. Статичното поле f, статичният метод m()и статичният инициализатор също са членове на C. Тъй като всички тези елементи принадлежат на клас C, той е известен като затварящ клас . Класът Dе известен като затворения клас .

Правила за заграждане и достъп

Въпреки че е затворен, статичният член-член не може да осъществи достъп до полетата на екземпляра на затварящия клас и да извика неговите методи на екземпляр. Той обаче може да осъществи достъп до статичните полета на затварящия клас и да извика неговите статични методи, дори тези членове, които са декларирани private. За демонстрация в Листинг 1 се декларира EnclosingClassс вложен SMClass.

Листинг 1. Деклариране на статичен клас член (EnclosingClass.java, версия 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(); } } } 

Листинг 1 декларира клас от най-високо ниво, наречен EnclosingClassс поле на sклас, методи на клас m1()и m2()и статичен клас член SMClass. SMClassдекларира метод на клас accessEnclosingClass()и метод на екземпляр accessEnclosingClass2(). Обърнете внимание на следното:

  • m2()извикването на метода на SMClass' accessEnclosingClass()изисква SMClass.префикса, защото accessEnclosingClass()е деклариран static.
  • accessEnclosingClass()е в състояние да достъп EnclosingClassе sполе и се обадете на своя m1()метод, въпреки че и двете са били декларирани private.

Обява 2, който осигурява достъп до изходния код на SMCDemoклас приложение, което показва как да се позове SMClassе accessEnclosingClass()метод. Той също така демонстрира как да създадете екземпляр SMClassи да извикате своя accessEnclosingClass2()метод на екземпляр.

Листинг 2. Извикване на методи на статичен член-член (SMCDemo.java)

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

Както е показано в Листинг 2, ако искате да извикате метода на класа от най-високо ниво от затворения клас, трябва да добавите пред името на затворения клас префикса с името на неговия затварящ клас. По същия начин, за да създадете екземпляр на затворен клас, трябва да добавите пред името на този клас името на неговия затварящ клас. След това можете да извикате метода на екземпляра по нормалния начин.

Съставете списъци 1 и 2, както следва:

 javac *.java 

Когато компилирате заграждащ клас, който съдържа статичен клас член, компилаторът създава файл на клас за статичния клас член, чието име се състои от името на неговия заграждащ клас, знак за долар и името на статичния член. В този случай компилирането води до EnclosingClass$SMCClass.classи EnclosingClass.class.

Стартирайте приложението, както следва:

 java SMCDemo 

Трябва да наблюдавате следния изход:

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

Пример: Статични класове и Java 2D

Стандартната библиотека на класове на Java е библиотека за изпълнение на файлове с класове, която съхранява компилирани класове и други референтни типове. Библиотеката включва многобройни примери за статични класове членове, някои от които се намират в Java 2D геометричните класове фигури, намиращи се в java.awt.geomпакета. (Ще научите за пакетите в следващия урок за Java 101. )

В Ellipse2Dкласа намерени в java.awt.geomописва елипса, която се определя от правоъгълник рамкиране по отношение на един (х, у)-горния ляв ъгъл заедно с ширина и височина степен. Следният фрагмент на код показва, че архитектурата на този клас се основава на Floatи Doubleстатични членове, които и два подкласа 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 } 

В Floatи Doubleкласове удължават Ellipse2D, осигуряване с плаваща запетая и двойна точност с плаваща запетая Ellipse2Dреализации. Разработчиците използват Floatза намаляване на консумацията на памет, особено защото може да ви трябват хиляди или повече от тези обекти, за да конструирате една-единствена 2D сцена. Използваме, Doubleкогато се изисква по-голяма точност.

Не можете да създадете екземпляр на абстрактния Ellipse2Dклас, но можете да създадете екземпляр или Floatили Double. Можете също така да разширите, за Ellipse2Dда опишете персонализирана форма, която се основава на елипса.

Като пример, да предположим, че искате да въведете Circle2Dклас, който не присъства в java.awt.geomпакета. Следният фрагмент на код показва как бихте създали Ellipse2Dобект с реализация с плаваща запетая:

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

Следващият фрагмент на кода показва как бихте създали Ellipse2Dобект с реализация с двойна точност с плаваща запетая:

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

Вече можете да извикате някой от методите, декларирани в Floatили Doubleчрез извикване на метода на върнатата Ellipse2Dпрепратка (например, e2d.getX()). По същия начин можете да извикате някой от методите, които са общи за Floatи Double, и които са декларирани в Ellipse2D. Пример за това е:

 e2d.contains(2.0, 3.0) 

Това завършва въвеждането на статичните класове членове. След това ще разгледаме вътрешни класове, които са нестатични класове членове, локални класове или анонимни класове. Ще научите как да работите и с трите типа вътрешни класове.

изтегляне Вземете кода Изтеглете изходния код за примери в този урок. Създадено от Jeff Friesen за JavaWorld.

Inner classes, type 1: Non-static member classes

You've learned previously in the Java 101 series how to declare non-static (instance) fields, methods, and constructors as members of a class. You can also declare non-static member classes, which are nested non-static classes that you declare at the same level as instance fields, methods, and constructors. Consider this example:

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

Here, we introduce top-level class C with instance field f, instance method m(), a constructor, and non-static member class D. All of these entities are members of class C, which encloses them. However, unlike in the previous example, these instance entities are associated with instances ofC and not with the C class itself.

Each instance of the non-static member class is implicitly associated with an instance of its enclosing class. The non-static member class's instance methods can call the enclosing class's instance methods and access its instance fields. To demonstrate this access, Listing 3 declares an EnclosingClass with a nested NSMClass.

Listing 3. Declare an enclosing class with a nested non-static member class (EnclosingClass.java, version 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 declares a top-level class named EnclosingClass with instance field s, instance method m(), and non-static member class NSMClass. Furthermore, NSMClass declares instance method accessEnclosingClass().

Because accessEnclosingClass() is non-static, NSMClass must be instantiated before this method can be called. This instantiation must take place via an instance of EnclosingClass, as shown in Listing 4.

Listing 4. NSMCDemo.java

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

Listing 4's main() method first instantiates EnclosingClass and saves its reference in local variable ec. The main() method then uses the EnclosingClass reference as a prefix to the new operator, in order to instantiate NSMClass. The NSMClass reference is then used to call accessEnclosingClass().

Should I use 'new' with a reference to the enclosing class?

Prefixing new with a reference to the enclosing class is rare. Instead, you will typically call an enclosed class's constructor from within a constructor or an instance method of its enclosing class.

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.

HashMapпредоставя keySet()метод, който KeySetпри необходимост създава екземпляр и връща този екземпляр или кеширан екземпляр. Ето пълния метод: