En Java algunas clases pueden contener otras clases dentro de ellas. Estas clases se denominan clases anidadas. Las clases definidas dentro de otras clases generalmente se dividen en dos categorías: estáticas y no estáticas. Las clases anidadas no estáticas se denominan internas. Las clases anidadas que se declaran estáticas se denominan clases anidadas estáticas. De hecho, aquí no hay nada complicado, aunque la terminología parece algo confusa y, a veces, puede confundir incluso a un desarrollador de software profesional.

Clases anidadas e internas

Entonces, todas las clases ubicadas dentro de otras clases se denominan clases anidadas.

class OuterClass {
    ...
    class NestedClass {
        ...
    }
}
Las clases anidadas que no son estáticas se denominan clases internas y las que son estáticas se denominan clases anidadas estáticas . Clases internas de Java - 1

class OuterClass {
    ...
    static class StaticNestedClass {
        ...
    }
    class InnerClass {
        ...
    }
}
Por lo tanto, todas las clases internas están anidadas, pero no todas las clases anidadas son internas. Estas son las principales definiciones. Las clases internas son una especie de mecanismo de seguridad en Java. Sabemos que una clase ordinaria no puede asociarse con un modificador de acceso privado. Sin embargo, si nuestra clase es miembro de otra clase, entonces la clase interna puede hacerse privada. Esta función también se utiliza para acceder a miembros de clases privadas.

Ejemplo de clase interna

Entonces, intentemos crear alguna clase y dentro de ella, otra clase. Imagínese algún tipo de consola de juegos modular. Hay una "caja" en sí misma y se pueden conectar ciertos módulos a ella. Por ejemplo, un controlador de juego, un volante, un casco de realidad virtual, que, por regla general, no funcionan sin la propia consola. Aquí tenemos la clase GameConsole. Tiene 2 campos y 1 método: start() . La diferencia entre GameCosole y la clase a la que estamos acostumbrados es que tiene una clase GameController interna .

public class GameConsole {
    private String model;
    private int weight;

    public void run() {
        System.out.println("Game console is on");
    }


    public class GameController {

        private String color;

        public void start() {
            System.out.println("start button is pressed");
        }

        public void x() {
            System.out.println("x button is pressed");
        }

        public void y() {
            System.out.println("y button is pressed");
        }
        public void a() {
            System.out.println("a button is pressed");
        }

        public void b() {
            System.out.println("b button is pressed");
        }
        public void mover() {
            System.out.println("mover button is pressed");
        }

    }

}
En este punto, quizás te preguntes: ¿por qué no hacer que estas clases estén "separadas"? No es necesario anidarlos. De hecho es posible. Más bien, se trata del correcto diseño de las clases en cuanto a su uso. Las clases internas se crean para resaltar en el programa una entidad que está inextricablemente vinculada con otra entidad. Un controlador o, por ejemplo, un casco de realidad virtual son componentes de la consola. Sí, se pueden comprar por separado de la consola, pero no se pueden utilizar sin ella. Si hiciéramos que todas estas clases fueran clases públicas separadas, nuestro programa podría tener, por ejemplo, el siguiente código:

public class Main {

   public static void main(String[] args) {
       GameController controller = new GameController();
       controller.x();
   }
}
Lo que sucede en este caso no está claro, ya que el controlador en sí no funciona sin una consola. Hemos creado un objeto de consola de juegos. Creamos su subobjeto: el controlador del juego. Y ahora podemos jugar, solo presiona las teclas correctas. Los métodos que necesitamos se llaman en los objetos correctos. Todo es simple y conveniente. En este ejemplo, extraer el controlador del juego mejora la encapsulación (ocultamos los detalles de las partes de la consola dentro de la clase correspondiente) y permite una abstracción más detallada. Pero si, por ejemplo, creamos un programa que simule una tienda donde puedes comprar por separado un casco o un controlador de realidad virtual, este ejemplo fallará. Allí es mejor crear un controlador de juego por separado. Tomemos otro ejemplo. Mencionamos anteriormente que podemos hacer que la clase interna sea privada y aún así llamarla desde la clase externa. A continuación se muestra un ejemplo de dichas clases.

class OuterClass {
 
   // inner class
   private class InnerClass {
       public void print() {
           System.out.println("We are in the inner class...");
       }
   }

   // method of outer class. We are create an inner class from the method of outer one
   void display() {
       InnerClass inner = new InnerClass();
       inner.print();
   }
}
Aquí OuterClass es la clase externa, InnerClass es la clase interna, display() es el método dentro del cual estamos creando un objeto de la clase interna. Ahora escribamos una clase de demostración con un método principal donde invocaremos el método display() .

public class OuterDemoMain {

       public static void main(String args[]) {
           // create an object of the outer class
           OuterDemo outer = new OuterDemo();

           outer.display();
       }
}
Si ejecuta este programa, obtendrá el siguiente resultado:
Estamos en la clase interna...

Clasificación de clases internas

Las propias clases internas o clases anidadas no estáticas se dividen en tres grupos.
  • Clase interna tal como está. Solo una clase no estática dentro de la otra, como demostramos anteriormente con el ejemplo de GameConsole y GameController .
  • La clase interna local de método es una clase dentro de un método.
  • Clase interna anónima.
Clases internas de Java - 2

Método clase interna local

En Java puedes escribir una clase dentro de un método y es un tipo local. Al igual que las variables locales, el alcance de una clase interna está limitado dentro de un método. Una clase interna de método local solo se puede crear dentro del método donde se define la clase interna. Demostremos cómo usar la clase interna del método local.

public class OuterDemo2 {
  
   //instance method of the outer class OuterDemo2
   void myMethod() {
       String str = "and it's a value from OuterDemo2 class' myMethod ";

       // method-local inner class
       class methodInnerDemo {
           public void print() {
               System.out.println("Here we've got a method inner class... " );
               System.out.println(str);

           }
       }
      
       // Access to the inner class
       methodInnerDemo inn = new methodInnerDemo();
       inn.print();
   }
}
Ahora vamos a escribir una clase de demostración con un método principal donde invocaremos el método externo() .

public class OuterDemoMain {


   public static void main(String args[]) {
       OuterDemo2 outer = new OuterDemo2();
       outer.myMethod();
   }
}
La salida es:
Aquí tenemos una clase interna de método... y es un valor de myMethod de la clase OuterDemo2.

Clase interna anónima

Una clase interna declarada sin un nombre de clase se llama clase interna anónima. Cuando declaramos una clase interna anónima, inmediatamente la creamos una instancia. Normalmente, estas clases se utilizan siempre que es necesario anular una clase o método de interfaz.

abstract class OuterDemo3 {
   public abstract void method();

}
   class outerClass {

       public static void main(String args[]) {
           OuterDemo3 inner = new OuterDemo3() {
               public void method() {
                   System.out.println("Here we've got an example of an  anonymous inner class");
               }
           };
           inner.method();
       }
   }
El resultado está aquí:
Aquí tenemos un ejemplo de una clase interna anónima...

La clase interna anónima como argumento

También puedes pasar una clase interna anónima como argumento del método. Aquí hay un ejemplo.

interface OuterDemo4 {
       String hello();
   }

   class NewClass {
       // accepts the object of interface
       public void displayMessage(OuterDemo4 myMessage) {
           System.out.println(myMessage.hello());
           System.out.println("example of anonymous inner class as an argument");
       }

       public static void main(String args[]) {
           NewClass newClass = new NewClass();

           //here we pass an anonymous inner class as an argument
           newClass.displayMessage(new OuterDemo4() {
               public String hello() {
                   return "Hello!";
               }
           });
       }
}
El resultado está aquí:
¡Hola! ejemplo de clase interna anónima como argumento
Para reforzar lo aprendido, te sugerimos ver una lección en video de nuestro Curso de Java