Skip to content

5 Clases anidadas

Introducción

En Java se permite escribir una clase dentro de otra clase. La clase de dentro se llama clase anidada y la que la contiene, clase contenedora o externa.

Estas clases se utilizan con los siguientes propósitos:

  • Agrupación de clases relacionadas.
  • Control de visibilidad de las clases.
  • Proximidad entre la definición y el uso de las clases.
  • Definición de clases simples de ayuda o adaptación.
  • Código más claro que evita el exceso de clases muy pequeñas que no necesitan conocer los usuarios de un paquete.

Las clases anidadas se dividen en dos categorías: las clases anidadas estáticas y las clases anidadas no estáticas o clases internas.

Clases anidadas estáticas

Son clases declaradas de tipo static que se comportan como una clase normal de Java pero que se encuentran dentro de otra clase. También se pueden usar dentro de una interfaz.

Desde la clase anidada estática solamente se pueden acceder a los atributos estáticos de la clase contenedora.

Se pueden crear objetos sin crear ningún objeto de la clase contenedora.

Para hacer referencia a una clase anidada estática hay que indicar también la clase contenedora: ClaseContenedora.ClaseAnidada.

public class ContainerClass {
    public static class StaticNestedClass{
        public void staticNestedMethod(){
            System.out.println("Clase anidada estática");
        }
    }

    public void containerMethod(){
        System.out.println("Clase externa o contenedora");
    }
}
public class ShowStaticNestedClass {
    public void show(){
        ContainerClass.StaticNestedClass nested = new ContainerClass.StaticNestedClass();
        nested.staticNestedMethod();

        ContainerClass container = new ContainerClass();
        container.containerMethod();
    }

    public static void main(String[] args) {
        new ShowStaticNestedClass().show();
    }
}
Clase anidada estática
Clase externa o contenedora

Clases anidadas no estáticas o clases internas

Las clases anidadas no estáticas o clases internas tienen acceso a todos los atributos y métodos de la clase contenedora, por lo tanto, para que exista un objeto de una clase interna es necesario que exista un objeto de la clase contenedora.

public class ContainerClass {
    private int numContainer = 10;

    public class InnerClass{
        public void innerMethod(){
            System.out.printf("Clase interna. Puede acceder a la numContainer: %d\n", numContainer);
        }
    }

    public void containerMethod(){
        InnerClass innerClass = new InnerClass();
        innerClass.innerMethod();
    }
}
public class ShowInnerClass {
    private void show() {
        ContainerClass container = new ContainerClass();
        container.containerMethod();
    }

    public static void main(String[] args) {
        new ShowInnerClass().show();
    }
}
Clase interna. Puede acceder a numContainer: 10

También se puede crear un objeto de la clase interna desde fuera de la clase externa siempre y cuando la clase interna sea visible:

ContainerClass container = new ContainerClass();
ContainerClass.InnerClass inner = container.new InnerClass();
inner.innerMethod();

Se puede hacer una clasificación de las clases internas en función de dónde y cómo se utilicen:

  • Clases miembro
  • Clases dentro de un método.
  • Clases dentro de un bloque.
  • Clases anónimas

Clases internas miembro

Se utilizan como atributos de la clase contenedora. Si se declaran como privadas, la clase contenedora es la única que conoce su existencia.

public class ContainerClass {
    private int num = 10;
    private InnerClass inner = new InnerClass();

    private class InnerClass{
        private int num = 20;

        public void innerMethod(){
            System.out.printf("Número de la contenedora: %d. Número de la interna: %d\n",
                    ContainerClass.this.num, num);
        }
    }

    public void containerMethod(){
        inner.innerMethod();
    }
}
public class ShowInnerMemberClass {
    private void show() {
        ContainerClass container = new ContainerClass();
        container.containerMethod();
    }

    public static void main(String[] args) {
        new ShowInnerMemberClass().show();
    }
}
Número de la contenedora: 10. Número de la interna: 20

Al usar this dentro de una clase interna, éste se refiere al objeto de la clase interna. Para poder referirse al objeto de la clase contenedora, hay que anteponerle al this el nombre de dicha clase, tal y como se puede observar en el ejemplo: ContainerClass.this.num.

Clases internas dentro de un método

Se definen dentro de un método de la clase contenedora por lo que solamente se pueden utilizar dentro de dicho método.

Se utilizan cuando el método intenta solucionar un problema y necesita apoyarse en una clase pero no se necesita que esta clase esté disponible fuera, por lo tanto, son clases que quedan fuera del diseño.

La clase interna tiene acceso a los métodos y atributos de la clase contenedora y a las variables locales y parámetros del método donde se la declara.

public class ContainerClass {
    private int attribute = 10;

    public void containerMethod(int parameter){
        int localVariable = 20;

        class InnerClass{
            public void innerMethod(){
                System.out.printf("Clases interna a método--->\nAtributo de la clase contenedora: %d\n", attribute);
                System.out.printf("Variable local: %d\n", localVariable);
                System.out.printf("Parámetro: %d\n", parameter);
            }
        }
    }
}
public class ShowInnerMethodClass {
    private void show() {
        ContainerClass container = new ContainerClass();
        container.containerMethod(30);
    }

    public static void main(String[] args) {
        new ShowInnerMethodClass().show();
    }
}
Clase interna a método--->
Atributo de la contenedora: 10
Variable local: 20
Parámetro: 30

Clases internas dentro de un bloque

Sólo son visibles y utilizables dentro del bloque de código en el que se encuentran definidas.

public class ContainerClass {
    private int attribute = 10;

    public void containerMethod(int parameter){
        int localVariable = 20;

        if(parameter > localVariable){
            class InnerClass{
                public void innerMethod(){
                    System.out.printf("Clase interna a bloque--->\nAtributo de la contenedora: %d\n", attribute);
                    System.out.printf("Variable local: %d\n", localVariable);
                    System.out.printf("Parámetro; %d\n", parameter);
                }
            }
            InnerClass inner = new InnerClass();
            inner.innerMethod();
        }
    }
}
public class ShowInnerBlockClass {
    public void show(){
        ContainerClass container = new ContainerClass();
        container.containerMethod(30);
    }

    public static void main(String[] args) {
        new ShowInnerBlockClass().show();
    }
}
Clase interna a bloque--->
Atributo de la contenedora: 10
Variable local: 20
Parámetro: 30

¡Cuidado!

Si se intenta utilizar la clase fuera del bloque, da un error de compilación informando que la clase no puede ser resulta como tipo

Clases inline anónimas

Son clases sin nombre que se definen e instancian en una sola operación. Este tipo de clases se utiliza cuando se quiere anular el método de una clase o implementar una interfaz solamente para un momento puntual evitando crear una clase nueva para un solo uso. El término inline se debe a que en el cuerpo de un método se puede escribir una clase ahí mismo, en la línea, es decir, sin necesidad de hacerlo en otro archivo.

Por ejemplo, tenemos la siguiente class Class:

public class Class{
    protected String message = "Clases inline anónimas";

    public void showMessage(){
        System.out.println(message);
    }
}

Supongamos que queremos anular el método showMessage() para mostrar el mensaje en rojo en un momento puntual y creamos una subclase o clase hija de Class que sobrescribiera el método:

public class Subclass extends Class{

    @Override
    public void showMessage(){
        System.out.println(RED + message + RESET);
    }
}

Entonces estaríamos creando una clase para algo que vamos a hacer solamente una vez. Y si quisiéramos en otro momento hacer lo mismo pero en azul, tendríamos que crear otra subclase para hacerlo. Entonces, la solución es crear una clase inline anónima. Se llama anónima porque en ningún otro momento aparece el nombre Subclass, es decir, estamos haciendo lo mismo que antes pero sin crear la subclase y lo estamos haciendo sobre la marcha:

public class AnonymousInlineClass {
    public void show(){
        Class anonymousInner = new Class(){
            public void showMessage(String message){
                System.out.println(RED + message + RESET);
            }
        };

        anonymousInner.showMessage();

        new Class(){ // Lo mismo pero sin utilizar una variable
            public void showMessage(String message){
                System.out.println(RED + message + RESET);
            }
        }.showMessage();
    }

    public static void main(String[] args) {
        new AnonymousInlineClass().show();
    }
}

Veamos un ejemplo con interfaces

public interface Message {
    void showMessage();
}
    public class ShowAnonymousInlineClassWithInterface {
    public void show(){
        new Message(){

            @Override
            public void showMessage() {
                System.out.println(RED + "Clases inline anónimas" + RESET);
            }
        }.showMessage();

        new Message(){

            @Override
            public void showMessage() {
                System.out.println(BLUE + "Clases inline anónimas" + RESET);
            }
        }.showMessage();
    }

    public static void main(String[] args) {
        new ShowAnonymousInlineClassWithInterface().show();
    }
}