Clases estáticas y clases internas en Java

Las clases anidadas son clases que se declaran como miembros de otras clases o ámbitos. Anidar clases es una forma de organizar mejor su código. Por ejemplo, supongamos que tiene una clase no anidada (también conocida como clase de nivel superior ) que almacena objetos en una matriz redimensionable, seguida de una clase iteradora que devuelve cada objeto. En lugar de contaminar el espacio de nombres de la clase de nivel superior, puede declarar la clase iteradora como miembro de la clase de colección de matrices redimensionable. Esto funciona porque los dos están estrechamente relacionados.

En Java, las clases anidadas se clasifican como clases miembro estáticas o clases internas . Las clases internas son clases miembro no estáticas, clases locales o clases anónimas. En este tutorial, aprenderá a trabajar con clases de miembros estáticos y los tres tipos de clases internas en su código Java.

Evite pérdidas de memoria en clases anidadas

Consulte también la sugerencia de Java asociada con este tutorial, donde aprenderá por qué las clases anidadas son vulnerables a las pérdidas de memoria.

Clases estáticas en Java

En mi tutorial de Java 101 Clases y objetos en Java, aprendió cómo declarar campos estáticos y métodos estáticos como miembros de una clase. En Inicialización de clases y objetos en Java, aprendió a declarar inicializadores estáticos como miembros de una clase. Ahora aprenderá a declarar clases estáticas . Formalmente conocidas como clases de miembros estáticos , estas son clases anidadas que declara al mismo nivel que estas otras entidades estáticas, utilizando la staticpalabra clave. A continuación, se muestra un ejemplo de una declaración de clase de miembro estático:

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

Este ejemplo presenta una clase de nivel superior Ccon campo festático m(), método estático , un inicializador estático y una clase de miembro estático D. Tenga en cuenta que Des miembro de C. El campo estático f, el método estático m()y el inicializador estático también son miembros de C. Dado que todos estos elementos pertenecen a la clase C, se conoce como clase envolvente . La clase Dse conoce como clase adjunta .

Reglas de acceso y cerramiento

Aunque está incluido, una clase miembro estática no puede acceder a los campos de instancia de la clase adjunta e invocar sus métodos de instancia. Sin embargo, puede acceder a los campos estáticos de la clase adjunta e invocar sus métodos estáticos, incluso aquellos miembros que están declarados private. Para demostrarlo, el Listado 1 declara an EnclosingClasscon anidado SMClass.

Listado 1. Declarar una clase miembro estática (EnclosingClass.java, versión 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(); } } } 

El Listado 1 declara una clase de nivel superior nombrada EnclosingClasscon campo de sclase, métodos de clase m1()y m2()clase de miembro estático SMClass. SMClassdeclara método de clase accessEnclosingClass()y método de instancia accessEnclosingClass2(). Tenga en cuenta lo siguiente:

  • m2()La invocación del método de SMClass' accessEnclosingClass()requiere el SMClass.prefijo porque accessEnclosingClass()está declarado static.
  • accessEnclosingClass()puede acceder EnclosingClassal scampo y llamar a su m1()método, aunque ambos hayan sido declarados private.

El Listado 2 presenta el código fuente a una SMCDemoclase de aplicación que demuestra cómo invocar SMClassel accessEnclosingClass()método. También demuestra cómo crear instancias SMClasse invocar su accessEnclosingClass2()método de instancia.

Listado 2. Invocar los métodos de una clase miembro estática (SMCDemo.java)

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

Como se muestra en el Listado 2, si desea invocar el método de una clase de nivel superior desde dentro de una clase adjunta, debe anteponer el nombre de la clase adjunta al nombre de la clase adjunta. Del mismo modo, para crear una instancia de una clase adjunta, debe anteponer el nombre de esa clase con el nombre de su clase adjunta. A continuación, puede invocar el método de instancia de la forma habitual.

Compile los listados 1 y 2 de la siguiente manera:

 javac *.java 

Cuando compila una clase adjunta que contiene una clase miembro estática, el compilador crea un archivo de clase para la clase miembro estática cuyo nombre consiste en el nombre de la clase adjunta, un carácter de signo de dólar y el nombre de la clase miembro estática. En este caso, la compilación de resultados en EnclosingClass$SMCClass.classy EnclosingClass.class.

Ejecute la aplicación de la siguiente manera:

 java SMCDemo 

Debe observar el siguiente resultado:

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

Ejemplo: clases estáticas y Java 2D

La biblioteca de clases estándar de Java es una biblioteca en tiempo de ejecución de archivos de clases, que almacena clases compiladas y otros tipos de referencia. La biblioteca incluye numerosos ejemplos de clases de miembros estáticos, algunos de los cuales se encuentran en las clases de formas geométricas 2D de Java ubicadas en el java.awt.geompaquete. (Aprenderá sobre los paquetes en el próximo tutorial de Java 101 ).

La Ellipse2Dclase que se encuentra en java.awt.geomdescribe una elipse, que está definida por un rectángulo de encuadre en términos de una esquina superior izquierda (x, y) junto con las extensiones de ancho y alto. A continuación se muestra de fragmentos de código que la arquitectura de esta clase se basa en Floaty Doubleclases de miembros estáticos, que tanto subclase 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 } 

Las clases Floaty Doublese extienden Ellipse2D, proporcionando Ellipse2Dimplementaciones de punto flotante y de punto flotante de doble precisión . Los desarrolladores lo utilizan Floatpara reducir el consumo de memoria, especialmente porque es posible que necesite miles o más de estos objetos para construir una única escena 2D. Usamos Doublecuando se requiere mayor precisión.

No puede instanciar la Ellipse2Dclase abstracta , pero puede instanciar Floato Double. También puede ampliar Ellipse2Dpara describir una forma personalizada basada en una elipse.

Como ejemplo, digamos que desea presentar una Circle2Dclase que no está presente en el java.awt.geompaquete. El siguiente fragmento de código muestra cómo crearía un Ellipse2Dobjeto con una implementación de punto flotante:

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

El siguiente fragmento de código muestra cómo crearía un Ellipse2Dobjeto con una implementación de punto flotante de doble precisión:

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

Ahora puede invocar cualquiera de los métodos declarados en Floato Doubleinvocando el método en la Ellipse2Dreferencia devuelta (por ejemplo, e2d.getX()). De la misma manera, puede invocar cualquiera de los métodos que son comunes a Floaty Double, y que están declarados en Ellipse2D. Un ejemplo es:

 e2d.contains(2.0, 3.0) 

Eso completa la introducción a las clases de miembros estáticos. A continuación, veremos las clases internas, que son clases de miembros no estáticos, clases locales o clases anónimas. Aprenderá a trabajar con los tres tipos de clases internas.

descargar Obtener el código Descargar el código fuente para ver ejemplos en este tutorial. Creado por Jeff Friesen para JavaWorld.

Clases internas, tipo 1: clases de miembros no estáticos

Ya aprendió anteriormente en la serie Java 101 cómo declarar campos, métodos y constructores no estáticos (instancia) como miembros de una clase. También puede declarar clases miembro no estáticas , que son clases no estáticas anidadas que declara al mismo nivel que los campos de instancia, métodos y constructores. Considere este ejemplo:

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

Aquí, presentamos una clase de nivel superior Ccon un campo de finstancia m(), un método de instancia , un constructor y una clase de miembro no estática D. Todas estas entidades son miembros de la clase C, que las encierra. Sin embargo, a diferencia del ejemplo anterior, estas entidades de instancia están asociadas con instancias deC y no con la Cclase en sí.

Cada instancia de la clase miembro no estática está asociada implícitamente con una instancia de su clase adjunta. Los métodos de instancia de la clase miembro no estática pueden llamar a los métodos de instancia de la clase adjunta y acceder a sus campos de instancia. Para demostrar este acceso, el Listado 3 declara un EnclosingClasscon un anidado NSMClass.

Listado 3. Declare una clase envolvente con una clase miembro no estática anidada (EnclosingClass.java, versión 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(); } } } 

El Listado 3 declara una clase de nivel superior nombrada EnclosingClasscon campo de sinstancia m(), método de instancia y clase miembro no estática NSMClass. Además, NSMClassdeclara el método de instancia 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.

HashMapproporciona un keySet()método que crea una instancia KeySetcuando es necesario y devuelve esta instancia o una instancia en caché. Aquí está el método completo: