Clases anidadas en Java

1. Introducción

Este tutorial es una introducción rápida y directa a las clases anidadas en el lenguaje Java.

En pocas palabras, Java nos permite definir clases dentro de otras clases. Las clases anidadas nos permiten agrupar lógicamente las clases que solo se usan en un lugar, escribir código más legible y fácil de mantener y aumentar la encapsulación.

Antes de comenzar, echemos un vistazo a los distintos tipos de clases anidadas disponibles en el idioma:

  • Clases anidadas estáticas
  • Clases anidadas no estáticas
  • Clases locales
  • Clases anónimas

En las siguientes secciones, analizaremos cada uno de estos en detalle.

2. Clases anidadas estáticas

Aquí hay algunos puntos para recordar acerca de las clases anidadas estáticas:

  • Al igual que con los miembros estáticos, estos pertenecen a su clase adjunta, y no a una instancia de la clase.
  • Pueden tener todo tipo de modificadores de acceso en su declaración.
  • Solo tienen acceso a miembros estáticos en la clase adjunta
  • Pueden definir miembros estáticos y no estáticos

Veamos cómo podemos declarar una clase anidada estática:

public class Enclosing { private static int x = 1; public static class StaticNested { private void run() { // method implementation } } @Test public void test() { Enclosing.StaticNested nested = new Enclosing.StaticNested(); nested.run(); } }

3. Clases anidadas no estáticas

A continuación, aquí hay algunos puntos rápidos para recordar sobre las clases anidadas no estáticas:

  • También se les llama clases internas.
  • Pueden tener todo tipo de modificadores de acceso en su declaración.
  • Al igual que los métodos y las variables de instancia, las clases internas están asociadas con una instancia de la clase adjunta
  • Tienen acceso a todos los miembros de la clase adjunta, independientemente de si son estáticos o no estáticos.
  • Solo pueden definir miembros no estáticos

Así es como podemos declarar una clase interna:

public class Outer { public class Inner { // ... } }

Si declaramos una clase anidada con un modificador static , entonces es un miembro estático. De lo contrario, es una clase interna. Aunque sintácticamente la diferencia es solo una palabra clave (es decir, estática ), semánticamente hay una gran diferencia entre estos tipos de clases anidadas. Las instancias de clase interna están vinculadas a las de clase adjunta y, por lo tanto, tienen acceso a sus miembros. Debemos ser conscientes de este problema al seleccionar si hacer que una clase anidada sea interna.

Para instanciar una clase interna, primero debemos instanciar su clase adjunta.

Veamos cómo podemos hacer eso:

Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();

En las siguientes subsecciones, mostraremos algunos tipos especiales de clases internas.

3.1. Clases locales

Las clases locales son un tipo especial de clases internas, en las que la clase se define dentro de un método o bloque de alcance.

Veamos algunos puntos para recordar sobre este tipo de clase:

  • No pueden tener modificadores de acceso en su declaración
  • Tienen acceso a miembros estáticos y no estáticos en el contexto adjunto.
  • Solo pueden definir miembros de instancia

He aquí un ejemplo rápido:

public class NewEnclosing { void run() { class Local { void run() { // method implementation } } Local local = new Local(); local.run(); } @Test public void test() { NewEnclosing newEnclosing = new NewEnclosing(); newEnclosing.run(); } }

3.2. Clases anónimas

Las clases anónimas se pueden usar para definir una implementación de una interfaz o una clase abstracta sin tener que crear una implementación reutilizable.

Enumeremos algunos puntos para recordar sobre las clases anónimas:

  • No pueden tener modificadores de acceso en su declaración
  • Tienen acceso a miembros estáticos y no estáticos en el contexto adjunto.
  • Solo pueden definir miembros de instancia
  • Son el único tipo de clases anidadas que no pueden definir constructores o extender / implementar otras clases o interfaces.

Para definir una clase anónima, primero definamos una clase abstracta simple:

abstract class SimpleAbstractClass { abstract void run(); }

Ahora veamos cómo podemos definir una clase anónima:

public class AnonymousInnerUnitTest { @Test public void whenRunAnonymousClass_thenCorrect() { SimpleAbstractClass simpleAbstractClass = new SimpleAbstractClass() { void run() { // method implementation } }; simpleAbstractClass.run(); } }

Para obtener más detalles, podemos encontrar útil nuestro tutorial sobre clases anónimas en Java.

4. Sombreado

La declaración de los miembros de una clase interna hace sombra a los de la clase adjunta si tienen el mismo nombre.

En este caso, la palabra clave this se refiere a las instancias de la clase anidada y se puede hacer referencia a los miembros de la clase externa utilizando el nombre de la clase externa.

Veamos un ejemplo rápido:

public class NewOuter { int a = 1; static int b = 2; public class InnerClass { int a = 3; static final int b = 4; public void run() { System.out.println("a = " + a); System.out.println("b = " + b); System.out.println("NewOuterTest.this.a = " + NewOuter.this.a); System.out.println("NewOuterTest.b = " + NewOuter.b); System.out.println("NewOuterTest.this.b = " + NewOuter.this.b); } } @Test public void test() { NewOuter outer = new NewOuter(); NewOuter.InnerClass inner = outer.new InnerClass(); inner.run(); } }

5. Serialización

Para evitar una excepción java.io.NotSerializableException al intentar serializar una clase anidada, debemos:

  • Declare la clase anidada como estática
  • Haga que tanto la clase anidada como la clase adjunta implementen Serializable

6. Conclusión

En este artículo, hemos visto qué son las clases anidadas y sus diferentes tipos. También echamos un vistazo a cómo los modificadores de acceso y visibilidad de campo difieren entre esos diferentes tipos.

Como siempre, la implementación completa de este tutorial se puede encontrar en GitHub.