lunes, 28 de mayo de 2012

CLASES INTERNAS

TEMA 10

- Creación de clases internas
- El enlace con la clase externa
- Utilización de .this y .new
- Clases interna y generalización
- Clases internas en los métodos y ámbitos
- Clases internas anónimas
  • Un nuevo análisis del método factoría
- Clases anidadas
  • Clases dentro de interfaces
  • Acceso al exterior desde una clase múltiplemente anidada
- ¿Para qué se usan las clases internas?
  • Cierres y retrollamadas
  • Clases internas y marcos de control
- Cómo heredar de clases internas
- ¿Pueden sustituirse las clases internas?
- Clases internas locales
- Identificadores de una clase interna

Resulta posible situar la definición de una clase dentro de la definición de otra. Dichas clases se llaman clases internas.


Las clases internas son interesantes porque nos permiten agrupar clases relacionadas y controlar la visibilidad mutua de esas clases. Sin embargo son algo totalmente diferente del mecanismo de composición que hemos visto y tampoco hay que pensar en ellas como una forma de ocultar código: colocamos clases dentro de otras clases. Una clase interna conoce los detalles de la clase contenedora y puede comunicarse con ella. Además el tipo de código que puede escribirse con las clases internas es más elegante y claro (aunque no siempre, claro).

CREACIÓN DE CLASES INTERNAS
Una clase interna se define dentro de otra clase contenedora:
//: innerclasses/Parcel1.java

// Creating inner classes.



public class Parcel1 {

  class Contents {

    private int i = 11;

    public int value() { return i; }

  }

  class Destination {

    private String label;

    Destination(String whereTo) {

      label = whereTo;

    }

    String readLabel() { return label; }

  }

  // Using inner classes looks just like

  // using any other class, within Parcel1:

  public void ship(String dest) {

    Contents c = new Contents();

    Destination d = new Destination(dest);

    System.out.println(d.readLabel());

  }

  public static void main(String[] args) {

    Parcel1 p = new Parcel1();

    p.ship("Tasmania");

  }

} /* Output:

Tasmania

*///:~


Las clases internas dentro del método ship() parecen clases normales, la única diferencia es que los nombres están anidados dentro de Parcel1. Pero pronto veremos que esta diferencia no es la única.
Lo normal es que una clase externa tenga un método que devuelva una referencia a una clase interna, como vemos en los métodos to() y contents():
//: innerclasses/Parcel2.java

// Returning a reference to an inner class.



public class Parcel2 {

  class Contents {

    private int i = 11;

    public int value() { return i; }

  }

  class Destination {

    private String label;

    Destination(String whereTo) {

      label = whereTo;

    }

    String readLabel() { return label; }

  }

  public Destination to(String s) {

    return new Destination(s);

  }

  public Contents contents() {

    return new Contents();

  }

  public void ship(String dest) {

    Contents c = contents();

    Destination d = to(dest);

    System.out.println(d.readLabel());

  }

  public static void main(String[] args) {

    Parcel2 p = new Parcel2();

    p.ship("Tasmania");

    Parcel2 q = new Parcel2();

    // Defining references to inner classes:

    Parcel2.Contents c = q.contents();

    Parcel2.Destination d = q.to("Borneo");

  }

} /* Output:

Tasmania

*///:~


Si queremos construir un objeto de la clase interna en cualquier lugar que no sea dentro de un método no estático de la clase externa, debemos especificar el tipo de dicho objeto como NombreClaseExterna.NombreClaseInterna, como podemos ver en main().

Ejercicio 1. Escribe una clase denominada Outer que contenga una clase interna llamada Inner. Añade un método a Outer que devuelva un objeto de tipo Inner. En main(), crea e inicializa una referencia a un objeto Inner.

EL ENLACE CON LA CLASE EXTERNA
Cuando se crea una clase interna, cada objeto de esta clase dispone de un enlace al objeto contenedor que lo ha creado, así puede acceder a los miembros de dicho objeto contenedor sin utilizar ninguna cualificación especial. Además, las clases internas tienen derechos de acceso a todos los elementos de la clase contenedora. Vamos a verlo con un ejemplo:
//: innerclasses/Sequence.java

// Holds a sequence of Objects.



interface Selector {

  boolean end();

  Object current();

  void next();

}



public class Sequence {

  private Object[] items;

  private int next = 0;

  public Sequence(int size) { items = new Object[size]; }

  public void add(Object x) {

    if(next < items.length)

      items[next++] = x;

  }

  private class SequenceSelector implements Selector {

    private int i = 0;

    public boolean end() { return i == items.length; }

    public Object current() { return items[i]; }

    public void next() { if(i < items.length) i++; }

  }

  public Selector selector() {

    return new SequenceSelector();

  }

  public static void main(String[] args) {

    Sequence sequence = new Sequence(10);

    for(int i = 0; i < 10; i++)

      sequence.add(Integer.toString(i));

    Selector selector = sequence.selector();

    while(!selector.end()) {

      System.out.print(selector.current() + " ");

      selector.next();

    }

  }

} /* Output:

0 1 2 3 4 5 6 7 8 9

*///:~


La secuencia Sequence es una matriz de tamaño fijo de objetos Object con una clase envoltorio. El método add() se utiliza para añadir un nuevo objeto al final de la secuencia si hay sitio. Para extraer los elementos de la secuencia tenemos la interfaz Selector. Este es un ejemplo de patrón de diseño iterador que veremos en próximos temas. Un Selector permite ver si nos encontramos al final de la secuencia [end()], acceder al objeto actual [current()] y desplazarse al objeto siguiente [next()] de la secuencia. Al ser Selector una interfaz, otras clases la pueden implementar de manera diferente y otros métodos pueden tomar la interfaz como argumento para crear código de propósito más general.
La clase privada SequenceSelector proporciona la funcionalidad Selector. En main podemos ver la creación de una secuencia y la adición a ella de una serie de objetos String. A continuación se genera un objeto Selector con una llamada a selector() y este objeto se utiliza para desplazarse a través de la secuencia y para seleccionar cada elemento.
Si nos fijamos en la clase interna SequenceSelector podemos ver que cada uno de sus métodos [end(), current() y next()] hacen referencia a items, que es un campo privado dentro de la clase contenedora. La clase interna puede acceder a los campos y métodos de la clase contenedora como si fueran de su propiedad.
Así pues, una clase interna tiene acceso automático a los miembros de la clase contenedora, ya que la clase interna captura en secreto una referencia al objeto concreto de la clase contenedora responsable de su creación. De esta forma, cuando hacemos referencia a un miembro de la clase contenedora, dicha referencia se utiliza para seleccionar dicho miembro. El compilador se encarga de estos detalles. Sólo podrá crearse un objeto de la clase interna en asociación con otro objeto de la clase contenedora (siempre que la clase interna no sea estática, como veremos más adelante). La creación de un objeto de la clase interna necesita de una referencia al objeto de la clase contenedora y el compilador avisará si no puede acceder a dicha referencia. La mayoría de las veces todo este mecanismo funciona sin que el programador tenga que intervenir para nada.

Ejercicio 2. Crea una clase que almacene un objeto String y que disponga de un método toString() que muestre esa cadena de caracteres. Añade varias instancias de la nueva clase a un objeto Sequence y luego visualízalas.
Ejercicio 3. Modifica el Ejercicio 1 para que Outer tenga un campo private String (inicializado por el contructor) e Inner tenga un método toString() que muestre este campo. Crea un objeto de tipo Inner y visualízalo.

UTILIZACIÓN DE .THIS Y .NEW
Si necesitamos una referencia al objeto de la clase externa, se debe indicar el nombre de la clase externa seguido de .this, vamos a ver un ejemplo:
//: innerclasses/DotThis.java

// Qualifying access to the outer-class object.



public class DotThis {

  void f() { System.out.println("DotThis.f()"); }

  public class Inner {

    public DotThis outer() {

      return DotThis.this;

      // A plain "this" would be Inner's "this"

    }

  }

  public Inner inner() { return new Inner(); }

  public static void main(String[] args) {

    DotThis dt = new DotThis();

    DotThis.Inner dti = dt.inner();

    dti.outer().f();

  }

} /* Output:

DotThis.f()

*///:~


A veces necesitamos que un objeto cree otro objeto de una de sus clases internas, para ello debemos proporcionar una referencia al objeto de la clase externa utilizando la sintaxis .new, como vemos en el siguiente ejemplo:
//: innerclasses/DotNew.java

// Creating an inner class directly using the .new syntax.



public class DotNew {

  public class Inner {}

  public static void main(String[] args) {

    DotNew dn = new DotNew();

    DotNew.Inner dni = dn.new Inner();

  }

} ///:~


Para crear un objeto de la clase interna es necesario utilizar un objeto de la clase externa, como vemos en el ejemplo anterior. No es posible crear un objeto de la clase interna si no se dispone de un objeto de la clase externa, ya que el objeto de la clase interna se conecta transparentemente al de la clase externa que lo haya creado. Sin embargo, si definimos una clase anidada, (clase interna estática), entonces no será necesaria la referencia al objeto de la clase externa.
Vamos a ver cómo se aplicaría .new al ejemplo "Parcel":
//: innerclasses/Parcel3.java

// Using .new to create instances of inner classes.



public class Parcel3 {

  class Contents {

    private int i = 11;

    public int value() { return i; }

  }

  class Destination {

    private String label;

    Destination(String whereTo) { label = whereTo; }

    String readLabel() { return label; }

  }

  public static void main(String[] args) {

    Parcel3 p = new Parcel3();

    // Must use instance of outer class

    // to create an instance of the inner class:

    Parcel3.Contents c = p.new Contents();

    Parcel3.Destination d = p.new Destination("Tasmania");

  }

} ///:~


Ejercicio 4. Añade un método a la clase Sequence.SequenceSelector que genere la referencia a la clase externa Sequence.
Ejercicio 5. Crea una clase con una clase interna. En otra clase separada, crea una instancia de la clase interna.

CLASES INTERNAS Y GENERALIZACIÓN
Las clases internas muestran su utilidad real cuando generalizamos a una clase base y, en particular, a una interfaz. (Generar una referencia a una interfaz a partir de un objeto que la implemente es prácticamente lo mismo que realizar una generalización a una clase base). La razón es que la clase interna (la implementación de la interfaz) puede ser no visible y estar no disponible, lo cual es útil para ocultar la implementación. Lo único que se obtiene es una referencia a la clase base o a la interfaz.
Vamos a crear interfaces para los ejemplos anteriores:
//: innerclasses/Destination.java

public interface Destination {

  String readLabel();

} ///:~



//: innerclasses/Contents.java

public interface Contents {

  int value();

} ///:~


Contents y Destination son interfaces disponibles para el programador de clientes. (La interfaz hace que todos sus miembros sean automáticamente públicos).
Cuando obtenemos una referencia a la clase base o a la interfaz, es posible que no podamos averiguar el tipo exacto, como vemos en el ejemplo:
//: innerclasses/TestParcel.java



class Parcel4 {

  private class PContents implements Contents {

    private int i = 11;

    public int value() { return i; }

  }

  protected class PDestination implements Destination {

    private String label;

    private PDestination(String whereTo) {

      label = whereTo;

    }

    public String readLabel() { return label; }

  }

  public Destination destination(String s) {

    return new PDestination(s);

  }

  public Contents contents() {

    return new PContents();

  }

}



public class TestParcel {

  public static void main(String[] args) {

    Parcel4 p = new Parcel4();

    Contents c = p.contents();

    Destination d = p.destination("Tasmania");

    // Illegal -- can't access private class:

    //! Parcel4.PContents pc = p.new PContents();

  }

} ///:~


Como vemos la clase interna PContents es private, por lo que sólo puede acceder a ella Parcel4. Recordamos que las clases normales (no internas) no pueden ser privadas o protegidas, sólo pueden tener acceso público o de paquete. PDestination es protegida, por lo que sólo pueden acceder a ella Parcel4, las clases contenidas en el mismo paquete y las clases que heredan de Parcel4. Así, el programador de clientes tiene conocimiento de estos miembros y acceso restringido a los mismos. Ni siquiera podemos realizar una especialización a una clase interna privada (ni a una clase interna protegida, a menos que usemos una clase que herede de ella), porque no se puede acceder al nombre, como podemos ver en class TestParcel. Por tanto, las clases internas privadas proporcionan una forma para que los diseñadores de clases eviten las dependencias de la codificación de tipos y oculten totalmente los detalles relativos a la implementación. Además, al extender una interfaz, desde el punto de vista del programador de clientes, no se puede acceder a ningún método adicional que no forme parte de la interfaz pública.

Ejercicio 6. Crea una interfaz con al menos un método, dentro de su propio paquete. Crea una clase en un paquete separado. Añade una clase interna protegida que implemente la interfaz. En un tercer paquete, define una clase que herede de la anterior y, dentro de un método, devuelve un objeto de la clase interna protegida, efectuando una generalización a la interfaz durante el retorno.
Ejercicio 7. Crea una clase con un campo privado y un método privado. Crea una clase interna con un método que modifique el campo de la clase externa e invoque el método de la clase externa. En un segundo método de la clase externa, crea un objeto de la clase interna e invoca su método, mostrando a continuación el efecto que esto tenga sobre el objeto de la clase externa.
Ejercicio 8. Determina si una clase externa tiene acceso a los elementos privados de su clase interna.

CLASES INTERNAS EN LOS MÉTODOS Y ÁMBITOS
Además de las clases internas "simples", hay otras un poco más complejas. Las clases internas pueden crearse dentro de un método o incluso de un ámbito arbitrario. Hay dos razones para hacer esto:
  1. Según hemos visto anteriormente, podemos implementar una interfaz de algún tipo para poder crear y devolver una referencia.
  2. Podemos tratar de resolver un problema complicado y crear una clase para solucionar este problema sin que la clase esté públicamente disponible.
En los siguientes ejemplos vamos a modificar el código anterior para utilizar:
  1. Una clase definida dentro de un método.
  2. Una clase definida dentro de un ámbito en el interior de un método.
  3. Una clase anónima que implemente una interfaz.
  4. Una clase anónima que amplíe una clase que disponga de un constructor no predeterminado.
  5. Una clase anónima que se encargue de la inicialización de campos.
  6. Una clase anónima que lleve a cabo la construcción utilizando el mecanismo de inicialización de instancia (las clases internas anónimas no pueden tener constructores).
En el siguiente ejemplo se muestra la creación de una clase completa dentro del ámbito de un método, esto se denomina clase interna local:
//: innerclasses/Parcel5.java

// Nesting a class within a method.



public class Parcel5 {

  public Destination destination(String s) {

    class PDestination implements Destination {

      private String label;

      private PDestination(String whereTo) {

        label = whereTo;

      }

      public String readLabel() { return label; }

    }

    return new PDestination(s);

  }

  public static void main(String[] args) {

    Parcel5 p = new Parcel5();

    Destination d = p.destination("Tasmania");

  }

} ///:~


La clase PDestination es parte del método destination(), en lugar de Parcel5. No se puede acceder a ella desde fuera de destination(). Si nos fijamos en la instrucción return, lo único que sale del método destination() es una referencia a Destination, que es la clase base. El hecho de que el nombre de la clase PDestination se coloque dentro de destination() no quiere decir que PDestination no sea un objeto válido una vez que destination() termine.
Podemos utilizar el identificador PDestination para nombrar cada clase interna dentro de un mismo subdirectorio sin que se produzcan colisiones de clases.
El siguiente ejemplo muestra cómo anidar clases dentro de una ámbito arbitrario:
//: innerclasses/Parcel6.java

// Nesting a class within a scope.



public class Parcel6 {

  private void internalTracking(boolean b) {

    if(b) {

      class TrackingSlip {

        private String id;

        TrackingSlip(String s) {

          id = s;

        }

        String getSlip() { return id; }

      }

      TrackingSlip ts = new TrackingSlip("slip");

      String s = ts.getSlip();

    }

    // Can't use it here! Out of scope:

    //! TrackingSlip ts = new TrackingSlip("x");

  }

  public void track() { internalTracking(true); }

  public static void main(String[] args) {

    Parcel6 p = new Parcel6();

    p.track();

  }

} ///:~


La clase TrackingSlip está anidada dentro de una instrucción if. Esto no significa que no se compile con el resto de código, pero sólo está disponible en el ámbito donde está definida.

Ejercicio 9. Crea una interfaz con al menos un método e implementa dicha interfaz definiendo una clase interna dentro de un método que devuelva una referencia a la interfaz.
Ejercicio 10. Repite el ejercicio anterior, pero definiendo la clase interna dentro de un ámbito en el interior de un método.
Ejercicio 11. Crea una clase interna privada que implemente una interfaz pública. Escribe un método que devuelva una referencia a una instancia de la clase interna privada, generalizada a la interfaz. Demuestra que la clase interna está completamente oculta, tratando de realizar una especialización sobre la misma.

CLASES INTERNAS ANÓNIMAS
Veamos el siguiente ejemplo:
//: innerclasses/Parcel7.java

// Returning an instance of an anonymous inner class.



public class Parcel7 {

  public Contents contents() {

    return new Contents() { // Insert a class definition

      private int i = 11;

      public int value() { return i; }

    }; // Semicolon required in this case

  }

  public static void main(String[] args) {

    Parcel7 p = new Parcel7();

    Contents c = p.contents();

  }

} ///:~


El método contents() combina la creación del valor de retorno con la definición de la clase que representa dicho valor de retorno. La clase es anónima, no tiene nombre. En lugar de crear un objeto Contents, introducimos la definición de clase.
Lo que esta extraña sintaxis significa es: "Crea un objeto de una clase anónima que herede de Contents". La referencia devuelta por new se generalizará automáticamente a una referencia de tipo Contents. La sintaxis de la clase interna anónima es una abreviatura de:
//: innerclasses/Parcel7b.java

// Expanded version of Parcel7.java



public class Parcel7b {

  class MyContents implements Contents {

    private int i = 11;

    public int value() { return i; }

  }

  public Contents contents() { return new MyContents(); }

  public static void main(String[] args) {

    Parcel7b p = new Parcel7b();

    Contents c = p.contents();

  }

} ///:~


La clase interna anónima Contents se crea utilizando un constructor predeterminado.
Si la clase base necesita un constructor con un argumento, el siguiente código muestra lo que hay que hacer:
//: innerclasses/Parcel8.java

// Calling the base-class constructor.



public class Parcel8 {

  public Wrapping wrapping(int x) {

    // Base constructor call:

    return new Wrapping(x) { // Pass constructor argument.

      public int value() {

        return super.value() * 47;

      }

    }; // Semicolon required

  }







  public static void main(String[] args) {

    Parcel8 p = new Parcel8();

    Wrapping w = p.wrapping(10);

  }

} ///:~


Simplemente pasamos el argumento apropiado al constructor de la clase base, como ocurre con la x que se pasa en new Wrapping(x). Aunque es una clase normal con una implementación, Wrapping se está usando también como "interfaz" con sus clases derivadas:
//: innerclasses/Wrapping.java

public class Wrapping {

  private int i;

  public Wrapping(int x) { i = x; }

  public int value() { return i; }

} ///:~


Wrapping tiene un constructor que requiere un argumento.
El punto y coma al final de la clase interna marca el final de la expresión que contiene a la clase anónima.
También se puede realizar la inicialización cuando se definen los campos en una clase anónima:
//: innerclasses/Parcel9.java

// An anonymous inner class that performs

// initialization. A briefer version of Parcel5.java.



public class Parcel9 {

  // Argument must be final to use inside

  // anonymous inner class:

  public Destination destination(final String dest) {

    return new Destination() {

      private String label = dest;

      public String readLabel() { return label; }

    };

  }

  public static void main(String[] args) {

    Parcel9 p = new Parcel9();

    Destination d = p.destination("Tasmania");

  }

} ///:~


Si definimos una clase interna anónima y queremos usar un objeto que esté definido fuera de ésta, el compilador requiere que la referencia al objeto sea final, como vemos en el argumento del método destination(). Si no lo hacemos así, obtendremos un error en tiempo de compilación.
La técnica del ejemplo resulta adecuada si simplemente realizamos una asignación a un campo. Pero, ¿y si necesitamos realizar algún tipo de actividad similar a la de los constructores? No podemos tener un constructor con nombre dentro de una clase anónima, pues esta no tiene nombre, pero con el mecanismo de inicialización de instancia, podemos crear un constructor para una clase interna anónima de la siguiente manera:
//: innerclasses/AnonymousConstructor.java

// Creating a constructor for an anonymous inner class.

import static net.mindview.util.Print.*;



abstract class Base {

  public Base(int i) {

    print("Base constructor, i = " + i);

  }

  public abstract void f();

}



public class AnonymousConstructor {

  public static Base getBase(int i) {

    return new Base(i) {

      { print("Inside instance initializer"); }

      public void f() {

        print("In anonymous f()");

      }

    };

  }

  public static void main(String[] args) {

    Base base = getBase(47);

    base.f();

  }

} /* Output:

Base constructor, i = 47

Inside instance initializer

In anonymous f()

*///:~


La variable i no tiene por qué ser final. Nunca se utiliza esta variable dentro de la clase anónima.
Vamos a ver un ejemplo con inicialización de instancia:
//: innerclasses/Parcel10.java

// Using "instance initialization" to perform

// construction on an anonymous inner class.



public class Parcel10 {

  public Destination

  destination(final String dest, final float price) {

    return new Destination() {

      private int cost;

      // Instance initialization for each object:

      {

        cost = Math.round(price);

        if(cost > 100)

          System.out.println("Over budget!");

      }

      private String label = dest;

      public String readLabel() { return label; }

    };

  }

  public static void main(String[] args) {

    Parcel10 p = new Parcel10();

    Destination d = p.destination("Tasmania", 101.395F);

  }

} /* Output:

Over budget!

*///:~


Dentro del inicializador de instancia podemos utilizar código que no podría estar en un inicializador de campo (instrucción if). Un inicializador de instancia es el constructor de una clase interna anónima. Lógicamente no se pueden sobrecargar estos inicializadores, sólo dispondremos de un constructor.
Las clases internas anónimas están limitadas en cuanto al mecanismo de herencia se refiere, ya que sólo pueden extender una clase o implementar una interfaz pero no ambas cosas al mismo tiempo. Sólo pueden implementar una interfaz, no más.

Ejercicio 12. Repite el Ejercicio 7 utilizando una clase interna anónima.
Ejercicio 13. Repite el Ejercicio 9 utilizando una clase interna anónima.
Ejercicio 14. Modifica interfaces/HorrorShow.java para implementar DangerousMonster y Vampire utilizando clases anónimas.
Ejercicio 15. Crea una clase con un constructor no predeterminado (uno que tenga argumentos) y sin ningún constructor predeterminado (es decir, un constructor sin argumentos). Crea una segunda clase que tenga un método que devuelva una referencia a un objeto de la primera clase. Crea el objeto que hay que devolver definiendo una clase interna anónima que herede de la primera clase.

Un nuevo análisis del método factoría
El ejemplo interfaces/Factories.java del tema anterior es ahora mucho más atractivo que cuando se utilizan clases internas anónimas:
//: innerclasses/Factories.java

import static net.mindview.util.Print.*;



interface Service {

  void method1();

  void method2();

}



interface ServiceFactory {

  Service getService();

}



class Implementation1 implements Service {

  private Implementation1() {}

  public void method1() {print("Implementation1 method1");}

  public void method2() {print("Implementation1 method2");}

  public static ServiceFactory factory =

    new ServiceFactory() {

      public Service getService() {

        return new Implementation1();

      }

    };

}



class Implementation2 implements Service {

  private Implementation2() {}

  public void method1() {print("Implementation2 method1");}

  public void method2() {print("Implementation2 method2");}

  public static ServiceFactory factory =

    new ServiceFactory() {

      public Service getService() {

        return new Implementation2();

      }

    };

}



public class Factories {

  public static void serviceConsumer(ServiceFactory fact) {

    Service s = fact.getService();

    s.method1();

    s.method2();

  }

  public static void main(String[] args) {

    serviceConsumer(Implementation1.factory);

    // Implementations are completely interchangeable:

    serviceConsumer(Implementation2.factory);

  }

} /* Output:

Implementation1 method1

Implementation1 method2

Implementation2 method1

Implementation2 method2

*///:~


Los constructores de Implementation1 e Implementation2 pueden ser privados. Con las variable estática factory que se crea en Implementation1 e Implementation2 no es necesario crear ninguna clase que implemente la interfaz ServiceFactory, ya que a esta variable se le asigna una clase interna ServiceFactory. La variable factory es estática porque normalmente sólo hace falta un único objeto factoría. Si comparamos un ejemplo con otro, esta sintaxis resulta más clara.
También podemos mejorar el ejemplo interfaces/Games.java del tema anterior con clases internas anónimas:
//: innerclasses/Games.java

// Using anonymous inner classes with the Game framework.

import static net.mindview.util.Print.*;



interface Game { boolean move(); }

interface GameFactory { Game getGame(); }



class Checkers implements Game {

  private Checkers() {}

  private int moves = 0;

  private static final int MOVES = 3;

  public boolean move() {

    print("Checkers move " + moves);

    return ++moves != MOVES;

  }

  public static GameFactory factory = new GameFactory() {

    public Game getGame() { return new Checkers(); }

  };

}



class Chess implements Game {

  private Chess() {}

  private int moves = 0;

  private static final int MOVES = 4;

  public boolean move() {

    print("Chess move " + moves);

    return ++moves != MOVES;

  }

  public static GameFactory factory = new GameFactory() {

    public Game getGame() { return new Chess(); }

  };

}



public class Games {

  public static void playGame(GameFactory factory) {

    Game s = factory.getGame();

    while(s.move());

  }

  public static void main(String[] args) {

    playGame(Checkers.factory);

    playGame(Chess.factory);

  }

} /* Output:

Checkers move 0

Checkers move 1

Checkers move 2

Chess move 0

Chess move 1

Chess move 2

Chess move 3

*///:~


Recordemos este consejo: Utilizar las clases con preferencia a las interfaces. Es decir, no utilizar una interfaz a menos que sea necesario, en ese caso nos daremos cuenta de esta necesidad cuando desarrollemos nuestra aplicación.

Ejercicio 16. Modifica la solución del Ejercicio 18 del Capítulo 9, Interfaces para utilizar clases internas anónimas.
Ejercicio 17. Modifica la solución del Ejercicio 19 del Capítulo 9, Interfaces para utilizar clases internas anónimas.

CLASES ANIDADAS
Si no necesitamos disponer de una conexión entre el objeto de la clase interna y el objeto de la clase externa, podemos definir la clase interna como estática, es lo que se llama una clase anidada. El objeto de una clase interna normal mantiene implícitamente una referencia al objeto de la clase contenedora que lo ha creado. Sin embargo, esto no es así cuando definimos como estática una clase interna. Una clase anidada significa que:
  1. No es necesario un objeto de la clase externa para crear un objeto de la clase anidada.
  2. No se puede acceder a un objeto no estático de la clase externa desde un objeto de la clase anidada.
Otra diferencia entre las clases internas ordinarias y las anidadas es que los campos y métodos en las clases internas normales sólo pueden encontrarse en el nivel externo de una clase, por lo que no pueden tener datos estáticos, campos estáticos o clases anidadas. Sin embargo, las clases anidadas pueden tener cualquiera de estos elementos:
//: innerclasses/Contents.java

public interface Contents {

  int value();

} ///:~



/: innerclasses/Destination.java

public interface Destination {

  String readLabel();

} ///:~



//: innerclasses/Parcel11.java

// Nested classes (static inner classes).



public class Parcel11 {

  private static class ParcelContents implements Contents {

    private int i = 11;

    public int value() { return i; }

  }

  protected static class ParcelDestination

  implements Destination {

    private String label;

    private ParcelDestination(String whereTo) {

      label = whereTo;

    }

    public String readLabel() { return label; }

    // Nested classes can contain other static elements:

    public static void f() {}

    static int x = 10;

    static class AnotherLevel {

      public static void f() {}

      static int x = 10;

    }

  }

  public static Destination destination(String s) {

    return new ParcelDestination(s);

  }

  public static Contents contents() {

    return new ParcelContents();

  }

  public static void main(String[] args) {

    Contents c = contents();

    Destination d = destination("Tasmania");

  }

} ///:~


En main() no es necesario ningún objeto de Parcel11, en su lugar utilizamos la sintaxis normal para seleccionar un miembro estático con el que invocar los métodos que devuelven referencias a Contents y Destination.
En una clase interna normal el vínculo con el objeto de la clase externa se utiliza empleando una referencia this. Una clase anidada no tiene referencia this, por lo que es análoga a un método estático.

Ejercicio 18. Crea una clase que contenga una clase anidada. En main(), crea una instancia de la clase anidada.
Ejercicio 19. Crea una clase que contenga una clase interna que a su vez contenga otra clase interna. Repite el proceso utilizando clases anidadas. Observa los nombres de los archivos .class generados por el compilador.

Clases dentro de interfaces
Una clase anidada puede ser parte de una interfaz. La clase que vaya dentro de una interfaz será pública y estática y se incluye dentro del espacio de nombres de la interfaz. Podemos incluso implementar la interfaz contenedora dentro de la clase contenedora de la forma siguiente:
//: innerclasses/ClassInInterface.java

// {main: ClassInInterface$Test}



public interface ClassInInterface {

  void howdy();

  class Test implements ClassInInterface {

    public void howdy() {

      System.out.println("Howdy!");

    }

    public static void main(String[] args) {

      new Test().howdy();

    }

  }

} /* Output:

Howdy!

*///:~


Es bastante útil anidar una clase dentro de una interfaz si queremos crear un código común que haya que emplear con todas las diferentes implementaciones de dicha interfaz.
Como sabemos, incluir un método main() en todas las clases es bueno para tener la posibilidad de probar estas clases. La desventaja es la cantidad de código adicional compilado con el que hay que trabajar. Si esto es un problema, podemos utilizar una clase anidada para incluir el código de prueba:
//: innerclasses/TestBed.java

// Putting test code in a nested class.

// {main: TestBed$Tester}



public class TestBed {

  public void f() { System.out.println("f()"); }

  public static class Tester {

    public static void main(String[] args) {

      TestBed t = new TestBed();

      t.f();

    }

  }

} /* Output:

f()

*///:~


Esto genera una clase separada denominada TestBed$Tester (para ejecutar el programa escribiríamos java TestBed$Tester). Esta clase se puede utilizar para pruebas y no es necesario incluirla en el producto final, bastará con eliminar TestBed$Tester.class antes de crear el producto definitivo.

Ejercicio 20. Crear una interfaz que contenga una clase anidada. Implementa esta interfaz y crea una instancia de la clase anidada.
Ejercicio 21. Crea una interfaz que contenga una clase anidada en la que haya un método estático que invoque los métodos de la interfaz y muestre los resultados. Implementa la interfaz y pasa al método una instancia de la implementación.

Acceso al exterior desde una clase múltiplemente anidada
Una clase anidada podrá acceder a todos los miembros de todas las clases dentro de las cuales esté anidada, vamos a verlo en un ejemplo:
//: innerclasses/MultiNestingAccess.java

// Nested classes can access all members of all

// levels of the classes they are nested within.



class MNA {

  private void f() {}

  class A {

    private void g() {}

    public class B {

      void h() {

        g();

        f();

      }

    }

  }

}



public class MultiNestingAccess {

  public static void main(String[] args) {

    MNA mna = new MNA();

    MNA.A mnaa = mna.new A();

    MNA.A.B mnaab = mnaa.new B();

    mnaab.h();

  }

} ///:~


En MNA.A.B los métodos g() y f() son invocables sin necesidad de ninguna cualificación (incluso siendo privados). En este ejemplo también vemos la sintaxis necesaria para crear objetos de clases internas múltiplemente anidadas cuando se crean objetos en una clase diferente. La sintaxis ".new" genera el ámbito correcto, por lo que no es necesario cualificar el nombre de la clase dentro de la llamada al constructor.

¿PARA QUÉ SE USAN LAS CLASES INTERNAS?
Después de todo lo visto la pregunta que nos hacemos es: ¿para qué sirven las clases internas?
Normalmente la clase interna hereda de otra clase o implementa una interfaz y manipula el objeto de la clase externa dentro del cual hubiera sido creado. Por tanto, una clase interna proporciona una especie de ventana hacia la clase externa.
Pero, si simplemente necesitamos una referencia a una interfaz, ¿por qué no hacemos simplemente que la clase externa implemente dicha interfaz? La respuesta es: "Si eso es todo lo que necesita entonces esa es la manera de hacerlo". Por tanto, ¿qué distingue una clase interna que implementa una interfaz de una clase externa que implementa la misma interfaz? La respuesta es que no siempre disponemos de la posibilidad de trabajar con interfaces sino que a veces nos vemos obligados a trabajar con implementaciones. Por tanto, la razón más evidente para utilizar clases internas es la siguiente:

Cada clase interna puede heredar de una implementación de manera independiente. Por tanto, la clase interna no está limitada por el hecho de si la clase externa ya está heredando de una implementación.

Si las clases internas no pudieran heredar de más de una clase concreta o abstracta, algunos problemas de diseño y de programación serían intratables. Así, una forma de ver las clases internas es que representan al resto de la solución del problema de la herencia múltiple. Las interfaces resuelven parte del problema, pero las clases internas permiten en la práctica una "herencia de múltiples implementaciones". En otras palabras, las clases internas nos permiten en la práctica heredar de varios elementos que no sean interfaces.
Pensemos en una situación en la que tuviéramos dos interfaces que deban de ser implementadas dentro de una clase. Debido a la flexibilidad de las interfaces tenemos dos opciones: una única clase o una clase interna.
//: innerclasses/MultiInterfaces.java

// Two ways that a class can implement multiple interfaces.

package innerclasses;



interface A {}

interface B {}



class X implements A, B {}



class Y implements A {

  B makeB() {

    // Anonymous inner class:

    return new B() {};

  }

}



public class MultiInterfaces {

  static void takesA(A a) {}

  static void takesB(B b) {}

  public static void main(String[] args) {

    X x = new X();

    Y y = new Y();

    takesA(x);

    takesA(y);

    takesB(x);

    takesB(y.makeB());

  }

} ///:~


Normalmente dispondremos de algún tipo de directriz, extraída de la propia naturaleza del problema, que nos indique si debemos utilizar una única clase o una clase interna, en ausencia de cualquier otra restricción, la técnica del ejercicio anterior no presenta muchas diferencias desde el punto de vista de la implementación. Ambas soluciones funcionan correctamente.
Sin embargo, si tenemos clases abstractas o concretas en lugar de interfaces, tendremos que utilizar clases internas si nuestra clase debe implementar de alguna forma las otras clases de las que se quiere heredar:
//: innerclasses/MultiImplementation.java

// With concrete or abstract classes, inner

// classes are the only way to produce the effect

// of "multiple implementation inheritance."

package innerclasses;



class D {}

abstract class E {}



class Z extends D {

  E makeE() { return new E() {}; }

}



public class MultiImplementation {

  static void takesD(D d) {}

  static void takesE(E e) {}

  public static void main(String[] args) {

    Z z = new Z();

    takesD(z);

    takesE(z.makeE());

  }

} ///:~


Si no necesitáramos resolver el problema de la "herencia de múltiples implementaciones", podríamos escribir el resto del programa sin necesidad de utilizar clases internas. Pero con las clases internas tenemos las siguientes características adicionales:
  1. La clase interna puede tener múltiples instancias, cada una con su propia información de estado que es independiente de la información contenida en el objeto de la clase externa.
  2. En una única clase externa podemos tener varias clases internas, cada una de las cuales implementa la misma interfaz o hereda de la misma clase en una forma diferente.
  3. El punto de creación del objeto de la clase interna no está ligado a la creación del objeto de la clase externa.
  4. No existe ninguna relación de tipo "es-un" potencialmente confusa con la clase interna, se trata de una entidad separada.
Por ejemplo, si la clase Sequence.java no utilizara clases internas, tendríamos que decir que un objeto Sequence es un objeto Selector y sólo podría existir un objeto Selector para cada objeto Sequence concreto. Sin embargo, podríamos definir un segundo método, reverseSelector() que produjera un objeto Selector que se desplazara en sentido inverso a través de la secuencia. Esta flexibilidad sólo está disponible con las clases internas.

Ejercicio 22. Implementa reverseSelector() en Sequence.java.
Ejercicio 23. Crea una interfaz U con tres métodos. Crea una clase A con un método que genere una referencia a U, definiendo una clase interna anónima. Crea una segunda clase B que contenga una matriz de U. B debe tener un método que acepte y almacene una referencia a U en la matriz, un segundo método que configure una referencia en la matriz (especificada mediante el argumento del método) con el valor null, y un tercer método que se desplace a través de la matriz e invoque los métodos de U. En main(), crea un grupo de objetos A y un único B. Rellena el objeto B con referencias a U generadas por los objetos A. Utiliza el objeto B para realizar llamadas a todos los objetos A. Elimina algunas de las referencias U del objeto B.

Cierres y retrollamada
Un cierre (closure) es un objeto invocable que retiene información acerca del ámbito en que fue creado. Teniendo en cuenta esto, podemos ver que una clase interna es un cierre orientado a objetos, ya que no contiene simplemente cada elemento de información del objeto de la clase externa (el ámbito en que fue creado), sino que almacena de manera automática una referencia que apunta al propio objeto de la clase externa, en el cual tiene permiso para manipular todos los miembros, incluso privados.
Uno de los argumentos que se proporcionaron para incluir punteros en Java era el de permitir las retrollamadas (callbacks). Con una retrollamada se proporciona a un objeto un elemento de información que le permite llamar al objeto original en un momento posterior. Es un concepto muy potente. Pero si se implementa una retrollamada utilizando un puntero, hay que confiar en que el programador se comporte correctamente y no haga un mal uso del puntero. Sin embargo, el lenguaje Java es precavido y no se han incluido punteros.
El cierre que proporciona la clase interna es una buena solución, bastante más flexible y segura que la basada en punteros. Veamos un ejemplo:
//: innerclasses/Callbacks.java

// Using inner classes for callbacks

package innerclasses;

import static net.mindview.util.Print.*;



interface Incrementable {

  void increment();

}



// Very simple to just implement the interface:

class Callee1 implements Incrementable {

  private int i = 0;

  public void increment() {

    i++;

    print(i);

  }

}



class MyIncrement {

  public void increment() { print("Other operation"); }

  static void f(MyIncrement mi) { mi.increment(); }

}



// If your class must implement increment() in

// some other way, you must use an inner class:

class Callee2 extends MyIncrement {

  private int i = 0;

  public void increment() {

    super.increment();

    i++;

    print(i);

  }

  private class Closure implements Incrementable {

    public void increment() {

      // Specify outer-class method, otherwise

      // you'd get an infinite recursion:

      Callee2.this.increment();



    }

  }

  Incrementable getCallbackReference() {

    return new Closure();

  }

}



class Caller {

  private Incrementable callbackReference;

  Caller(Incrementable cbh) { callbackReference = cbh; }

  void go() { callbackReference.increment(); }

}



public class Callbacks {

  public static void main(String[] args) {

    Callee1 c1 = new Callee1();

    Callee2 c2 = new Callee2();

    MyIncrement.f(c2);

    Caller caller1 = new Caller(c1);

    Caller caller2 = new Caller(c2.getCallbackReference());

    caller1.go();

    caller1.go();

    caller2.go();

    caller2.go();

  }

} /* Output:

Other operation

1

1

2

Other operation

2

Other operation

3

*///:~


Esto también muestra una distinción adicional entre implementar una interfaz en una clase externa y hacerlo en una clase interna. Callee1 es la solución más simple en términos de código. Callee2 hereda de MyIncrement, que ya dispone de un método increment() diferente que lleva a cabo una tarea no relacionada con la que la interfaz Incrementable espera. Cuando Callee2 hereda la clase MyIncrement, increment() no puede ser sustituido para que lo utilice la interfaz Incrementable, por lo que estamos obligados a proporcionar una implementación separada mediante una clase interna. Cuando se crea una clase interna no se añade nada a la interfaz de la clase externa ni se la modifica de ninguna manera.
Todo en Callee2 es privado salvo getCallbackReference(). Para permitir alguna conexión con el mundo exterior, la interfaz Incrementable es esencial. Este ejemplo nos muestra que las interfaces permiten una completa separación entre la interfaz y la implementación.
La clase interna Closure (cierre) implementa Incrementable y así proporciona un engarce lo suficientemente seguro con Callee2. Cualquiera que obtenga la referencia a Incrementable sólo puede invocar increment() y no hay ninguna otra posibilidad (a diferencia de un puntero que nos permitiría hacer cualquier cosa).
Caller toma una referencia a Incrementable en su constructor (esta captura de la referencia de retrollamada podría tener lugar en cualquier instante) y en un momento posterior utilizar la referencia para efectuar una retrollamada a la clase Callee.
El valor de las retrollamadas radica en su flexibilidad: podemos decidir de manera dinámica qué métodos van a ser invocados en tiempo de ejecucion.

Clases internas y marcos de control
Un ejemplo más concreto del uso de clases internas es el que podemos encontrar en un marco de control.
Un marco de trabajo de una aplicación es una clase o conjunto de clases diseñado para resolver un tipo concreto de problema. Para utilizar un marco de trabajo de una aplicación, lo que normalmente se hace es heredar de una o más clases y sustituir algunos de los métodos. Al sustituir código personalizamos la solución proporcionada por dicho marco de trabajo de la aplicación y así resolvemos los problemas específicos. Se trata de un ejemplo de patrón de diseño basado en el método de plantillas (ver Thinking in Patterns (with Java) en www.MindView.net). Este método contiene la estructura básica del algoritmo, e invoca uno o más métodos sustituibles para completar la acción que el algoritmo dictamina. Los patrones de diseño separan las cosas que no cambian de las que sí lo hacen y en este caso el método basado en plantillas es la parte que permanece invariable, y los métodos sustituibles los que se modifican.
Un marco de control es un tipo particular de marco de trabajo de aplicación, dominado por la necesidad de responder a cierto suceso. Los sistemas que se dedican principalmente a responder a sucesos se denominan sistemas dirigidos por sucesos. La interfaz gráfica de usuario (GUI) está casi completamente dirigida por sucesos. La biblioteca Swing de Java es un marco de control para las interfaces GUI que utiliza de manera intensiva las clases internas.
Las clases internas permiten crear y utilizar marcos de control, consideremos un marco de control cuyo trabajo consiste en ejecutar sucesos cada vez que dichos sucesos estén "listos". El ejemplo siguiente es un marco de control que no contiene información específica de lo que está controlando. Esta información se suministra mediante el mecanismo de herencia, cuando se implementa el método action().
Vamos a ver la interfaz que describe los sucesos de control. Es una clase abstracta, en lugar de interfaz, porque el comportamiento predeterminado consiste en llevar a cabo el control dependiendo del instante actual, así parte de la implementación se incluye aquí:
//: innerclasses/controller/Event.java

// The common methods for any control event.

package innerclasses.controller;



public abstract class Event {

  private long eventTime;

  protected final long delayTime;

  public Event(long delayTime) {

    this.delayTime = delayTime;

    start();

  }

  public void start() { // Allows restarting

    eventTime = System.nanoTime() + delayTime;

  }

  public boolean ready() {

    return System.nanoTime() >= eventTime;

  }

  public abstract void action();

} ///:~


El constructor captura el tiempo, medido desde el instante de creación del objeto, cuando se quiere ejecutar el objeto Event, y luego invoca start(), que toma el instante actual y añade el retardo necesario para generar el instante en el que el suceso tendrá lugar. El método start() es independiente, así se puede reinicializar el temporizador después de que el suceso haya caducado para que el objeto Event pueda reutilizarse. De esta forma, por ejemplo, si queremos un suceso repetitivo, invocamos start() dentro del método action().
El método ready() nos dice cuándo es el momento de ejecutar action(). ready() podría ser sustituido en una clase derivada, para basar el suceso Event en alguna otra cosa distinta del tiempo.
El siguiente archivo contiene el marco de control que gestiona y dispara los sucesos. Los objetos Event se almacenan dentro de un objeto contenedor de tipo List (lista de sucesos) que veremos en el siguiente tema. add() añade un objeto Event al final de la lista List, size() devuelve el número de elementos de List, la sintaxis foreach extrae objetos Event sucesivos de List y remove() elimina el objeto Event especificado.

//: innerclasses/controller/Controller.java

// The reusable framework for control systems.

package innerclasses.controller;

import java.util.*;



public class Controller {

  // A class from java.util to hold Event objects:

  private List eventList = new ArrayList();

  public void addEvent(Event c) { eventList.add(c); }

  public void run() {

    while(eventList.size() > 0)

      // Make a copy so you're not modifying the list

      // while you're selecting the elements in it:

      for(Event e : new ArrayList(eventList))

        if(e.ready()) {

          System.out.println(e);

          e.action();

          eventList.remove(e);

        }

  }

} ///:~


El método run() recorre mediante un bucle una copia de eventList, buscando un objeto Event listo para ser ejecutado. Si está listo imprime información sobre el objeto, invoca el método action() y luego elimina el objeto Event de la lista.
Hasta ahora no sabemos qué es lo que hace un objeto Event. Éste es el aspecto fundamental del diseño, separa las cosas que cambian de las cosas que permanecen iguales. El "vector de cambio" está compuesto por las diferentes acciones de los objetos Event y podemos expresar diferentes acciones creando distintas subclases de Event.
Aquí entran en juego las clases internas, las cuales nos permiten:
  1. La implementación de un marco de control se crea en una única clase, encapsulando así las características distintivas de dicha implementación. Las clases internas se usan para expresar los distintos tipos de acciones (action()) necesarios para resolver el problema.
  2. Las clases internas evitan que esta implementación sea confusa ya que podemos acceder a cualquiera de los miembros de la clase externa. Sin esta capacidad el código sería demasiado complejo.
Imaginemos una implementación concreta del marco de control diseñado para regular las funciones de un invernadero. Cada acción es distinta: encender y apagar las luces, iniciar y detener el riego, apagar y encender los termostatos, hacer sonar alarmas y reinicializar el sistema. El marco de control está diseñado para aislar fácilmente las distintas secciones del código. Las clases internas permiten disponer de múltiples versiones derivadas de la clase base Event, dentro de una misma clase. Para cada acción, heredamos una nueva clase interna Event y escribimos el código de control en la implementación de action().
La siguiente clase GreenhouseControls hereda de Controller:
//: innerclasses/GreenhouseControls.java

// This produces a specific application of the

// control system, all in a single class. Inner

// classes allow you to encapsulate different

// functionality for each type of event.

import innerclasses.controller.*;



public class GreenhouseControls extends Controller {

  private boolean light = false;

  public class LightOn extends Event {

    public LightOn(long delayTime) { super(delayTime); }

    public void action() {

      // Put hardware control code here to

      // physically turn on the light.

      light = true;

    }

    public String toString() { return "Light is on"; }

  }

  public class LightOff extends Event {

    public LightOff(long delayTime) { super(delayTime); }

    public void action() {

      // Put hardware control code here to

      // physically turn off the light.

      light = false;

    }

    public String toString() { return "Light is off"; }

  }

  private boolean water = false;

  public class WaterOn extends Event {

    public WaterOn(long delayTime) { super(delayTime); }

    public void action() {

      // Put hardware control code here.

      water = true;

    }

    public String toString() {

      return "Greenhouse water is on";

    }

  }

  public class WaterOff extends Event {

    public WaterOff(long delayTime) { super(delayTime); }

    public void action() {

      // Put hardware control code here.

      water = false;

    }

    public String toString() {

      return "Greenhouse water is off";

    }

  }

  private String thermostat = "Day";

  public class ThermostatNight extends Event {

    public ThermostatNight(long delayTime) {

      super(delayTime);

    }

    public void action() {

      // Put hardware control code here.

      thermostat = "Night";

    }

    public String toString() {

      return "Thermostat on night setting";

    }

  }

  public class ThermostatDay extends Event {

    public ThermostatDay(long delayTime) {

      super(delayTime);

    }

    public void action() {

      // Put hardware control code here.

      thermostat = "Day";

    }

    public String toString() {

      return "Thermostat on day setting";

    }

  }

  // An example of an action() that inserts a

  // new one of itself into the event list:

  public class Bell extends Event {

    public Bell(long delayTime) { super(delayTime); }

    public void action() {

      addEvent(new Bell(delayTime));

    }

    public String toString() { return "Bing!"; }

  }

  public class Restart extends Event {

    private Event[] eventList;

    public Restart(long delayTime, Event[] eventList) {

      super(delayTime);

      this.eventList = eventList;

      for(Event e : eventList)

        addEvent(e);

    }

    public void action() {

      for(Event e : eventList) {

        e.start(); // Rerun each event

        addEvent(e);

      }

      start(); // Rerun this Event

      addEvent(this);

    }

    public String toString() {

      return "Restarting system";

    }

  }

  public static class Terminate extends Event {

    public Terminate(long delayTime) { super(delayTime); }

    public void action() { System.exit(0); }

    public String toString() { return "Terminating";  }

  }

} ///:~


Los campos light, water y thermostat pertenecen a la clase externa GreenhouseControls, pero las clases internas pueden acceder a estos campos directamente. Los métodos action() de este ejemplo requieren algún tipo de control del hardware.
Las clases Bell y Restart son especiales. Bell hace sonar una alarma y luego añade un nuevo objeto Bell a la lista de sucesos para que vuelva a sonar posteriormente. Las clases internas casi parecen un verdadero mecanismo de herencia múltiple. Bell y Restart tienen todos los métodos de Event y también parecen tener todos los métodos de GreenhouseControls.
A Restart se le proporciona una matriz de objetos Event y se encarga de añadirla al controlador. Como Restart es otro objeto Event, se puede añadir un objeto Restart dentro de Restart.action() para que el sistema se reinicialice a sí mismo periódicamente.
La siguiente clase configura el sistema creando un objeto GreenhouseControls y añadiendo diversos tipos de objetos Event. Esto es un ejemplo de patrón de diseño Command: cada objeto de eventList es una solicitud encapsulada en forma de objeto:
//: innerclasses/GreenhouseController.java

// Configure and execute the greenhouse system.

// {Args: 5000}

import innerclasses.controller.*;



public class GreenhouseController {

  public static void main(String[] args) {

    GreenhouseControls gc = new GreenhouseControls();

    // Instead of hard-wiring, you could parse

    // configuration information from a text file here:

    gc.addEvent(gc.new Bell(900));

    Event[] eventList = {

      gc.new ThermostatNight(0),

      gc.new LightOn(200),

      gc.new LightOff(400),

      gc.new WaterOn(600),

      gc.new WaterOff(800),

      gc.new ThermostatDay(1400)

    };

    gc.addEvent(gc.new Restart(2000, eventList));

    if(args.length == 1)

      gc.addEvent(

        new GreenhouseControls.Terminate(

          new Integer(args[0])));

    gc.run();

  }

} /* Output:

Bing!

Thermostat on night setting

Light is on

Light is off

Greenhouse water is on

Greenhouse water is off

Thermostat on day setting

Restarting system

Terminating

*///:~


Esta clase inicializa el sistema para añadir los sucesos apropiados. El suceso Restart se ejecuta repetidamente y carga cada vez la lista eventList en el objeto GreenhouseControls. Si por línea de comandos proporcionamos un argumento con el número de milisegundos, Restart terminará el programa después de ese número de milisegundos.
Lógicamente resulta más flexible leer los sucesos de un archivo en lugar de leerlos en el código. Esto lo veremos más adelante.
En este ejemplo se aprecia cuál es el valor de las clases internas cuando se las usa dentro de un marco de control.

Ejercicio 24. En GreenhouseControls.java añade una serie de clases internas Event que permitan encender y apagar una serie de ventiladores. Configura GreenhouseController.java para utilizar estos nuevos objetos Event.
Ejercicio 25. Hereda de GreenhouseControls en GreenhouseControls.java para añadir clases internas Event que permitan encender y apagar una serie de vaporizadores. Escribe una nueva versión de GreenhouseController.java para utilizar estos nuevos objetos Event.

CÓMO HEREDAR DE CLASES INTERNAS
Como el constructor de la clase interna debe efectuar la asociación como una referencia al objeto de la clase contenedora, las cosas se complican si queremos heredar de una clase interna. El problema es que la referencia "secreta" al objeto de la clase contenedora debe inicializarse, a pesar de lo cual en la clase derivada no hay ningún objeto predeterminado con el que asociarse. Es necesaria una sintaxis especial para que esta asociación se haga de forma explícita:
//: innerclasses/InheritInner.java

// Inheriting an inner class.



class WithInner {

  class Inner {}

}



public class InheritInner extends WithInner.Inner {

  //! InheritInner() {} // Won't compile

  InheritInner(WithInner wi) {

    wi.super();

  }

  public static void main(String[] args) {

    WithInner wi = new WithInner();

    InheritInner ii = new InheritInner(wi);

  }

} ///:~


InheritInner sólo amplía la clase interna, no la externa. Pero al crear un constructor, el predeterminado no sirve y no podemos limitarnos a pasar referencia a un objeto contenedor. Además es necesario utilizar la sintaxis:
 enclosingClassReference.super(); 

dentro del constructor. Esto proporciona la referencia necesaria y el programa se podrá compilar.

Ejercicio 26. Crea una clase con una clase interna que tenga un constructor no predeterminado (uno que tome argumentos). Crea una segunda clase con una clase interna que herede de la primera clase interna.

¿PUEDEN SUSTITUIRSE LAS CLASES INTERNAS?
¿Qué pasa cuando creamos una clase interna, heredamos de la clase contenedora y redefinimos la clase interna? Es decir, ¿se puede sustituir la clase interna completa? El resultado es que sustituir una clase interna como si fuera otro método cualquiera de la clase externa no tiene ningún efecto:
//: innerclasses/BigEgg.java

// An inner class cannot be overriden like a method.

import static net.mindview.util.Print.*;



class Egg {

  private Yolk y;

  protected class Yolk {

    public Yolk() { print("Egg.Yolk()"); }

  }

  public Egg() {

    print("New Egg()");

    y = new Yolk();

  }

}



public class BigEgg extends Egg {

  public class Yolk {

    public Yolk() { print("BigEgg.Yolk()"); }

  }

  public static void main(String[] args) {

    new BigEgg();

  }

} /* Output:

New Egg()

Egg.Yolk()

*///:~


Podríamos pensar que al crearse un objetoBigEgg se utilizará la versión sustituida de Yolk, sin embargo, esto no es así como vemos en la salida.
En el ejemplo vemos que no hay ningún mecanismo adicional relacionado con las clases internas que entre en acción al heredar de la clase externa. Las dos clases internas son entidades separadas, cada una con su propio espacio de nombres. Sin embargo, es posible heredar explícitamente de la clase interna:
//: innerclasses/BigEgg2.java

// Proper inheritance of an inner class.

import static net.mindview.util.Print.*;



class Egg2 {

  protected class Yolk {

    public Yolk() { print("Egg2.Yolk()"); }

    public void f() { print("Egg2.Yolk.f()");}

  }

  private Yolk y = new Yolk();

  public Egg2() { print("New Egg2()"); }

  public void insertYolk(Yolk yy) { y = yy; }

  public void g() { y.f(); }

}



public class BigEgg2 extends Egg2 {

  public class Yolk extends Egg2.Yolk {

    public Yolk() { print("BigEgg2.Yolk()"); }

    public void f() { print("BigEgg2.Yolk.f()"); }

  }

  public BigEgg2() { insertYolk(new Yolk()); }

  public static void main(String[] args) {

    Egg2 e2 = new BigEgg2();

    e2.g();

  }

} /* Output:

Egg2.Yolk()

New Egg2()

Egg2.Yolk()

BigEgg2.Yolk()

BigEgg2.Yolk.f()

*///:~


Ampliamos Egg2.Yolk en la clase BigEgg2.Yolk y sustituimos los métodos. El método insertYolk() permite que BigEgg2 generalice uno de sus propios objetos Yolk a la referencia y en Egg2, por lo que g() invoca a y.f(), se utiliza la versión sustituida de f(). La segunda llamada a Egg2.Yolk() es la llamada que el constructor de la clase base hace al constructor de BigEgg2.Yolk. Como vemos cuando se llama a g() se utiliza la versión sustituida de f().

CLASES INTERNAS LOCALES
También pueden crearse clases internas dentro de bloques de código, normalmente dentro del cuerpo de un método. Una clase interna local no puede tener un especificador de acceso, ya que no forma parte de la clase externa, pero sí tiene acceso a las variables finales del bloque de código actual y a todos los miembros de la clase contenedora. Veamos un ejemplo donde se compara una clase interna local con una clase interna anónima:
//: innerclasses/LocalInnerClass.java

// Holds a sequence of Objects.

import static net.mindview.util.Print.*;



interface Counter {

  int next();

}



public class LocalInnerClass {

  private int count = 0;

  Counter getCounter(final String name) {

    // A local inner class:

    class LocalCounter implements Counter {

      public LocalCounter() {

        // Local inner class can have a constructor

        print("LocalCounter()");

      }

      public int next() {

        printnb(name); // Access local final

        return count++;

      }

    }

    return new LocalCounter();

  }

  // The same thing with an anonymous inner class:

  Counter getCounter2(final String name) {

    return new Counter() {

      // Anonymous inner class cannot have a named

      // constructor, only an instance initializer:

      {

        print("Counter()");

      }

      public int next() {

        printnb(name); // Access local final

        return count++;

      }

    };

  }

  public static void main(String[] args) {

    LocalInnerClass lic = new LocalInnerClass();

    Counter

      c1 = lic.getCounter("Local inner "),

      c2 = lic.getCounter2("Anonymous inner ");

    for(int i = 0; i < 5; i++)

      print(c1.next());

    for(int i = 0; i < 5; i++)

      print(c2.next());

  }

} /* Output:

Counter()

Local inner 0

Local inner 1

Local inner 2

Local inner 3

Local inner 4

Anonymous inner 5

Anonymous inner 6

Anonymous inner 7

Anonymous inner 8

Anonymous inner 9

*///:~


Counter devuelve el siguiente valor de una secuencia. Vemos que está implementado como una clase local y como una clase interna anónima, teniendo ambas los mismos comportamientos y capacidades. La única razón para utilizar una clase interna local en lugar de una clase interna anónima es que necesitemos un constructor nominado y/o un constructor sobrecargado, ya que una clase interna anónima sólo puede utilizar una inicialización de instancia.
Otra razón es que necesitemos tener más de un objeto de dicha clase.

IDENTIFICADORES DE UNA CLASE INTERNA
Todas las clases generan un archivo .class, que almacena la información sobre cómo crear objetos de dicho tipo, esta información genera una "metaclase" llamada objeto Class, por tanto, las clases internas también deberían producir archivos .class para almacenar la información de sus objetos Class. Los nombres de estos archivos/clases se organizan de la siguiente forma: el nombre de la clase contenedora, seguido del signo "$", seguido del nombre de la clase interna. Así, los archivos .class creados por LocalInnerClass.java son:
Counter.class

LocalInnerClass$1.class

LocalInnerClass$1LocalCounter.class

LocalInnerClass.class


Si las clases internas son anónimas, el compilador genera una serie de números que identifican esta clase. Si las clases internas están anidadas dentro de otras clases internas sus nombres se añaden después de un '$' y del identificador o identificadores de las clases externas.
Los archivos generados son automáticamente independientes de la plataforma.

8 comentarios:

  1. Hola:
    Solo agradecerte lo que haces en este blog. Aunque soy desarrollador de Front-End en los tiempos muertos entre proyecto y proyecto estoy intentando aprender Java y los recursos que ofreces me van a resultar de mucha ayuda. ¡Gracias!

    ResponderEliminar
    Respuestas
    1. Muchas gracias por tu comentario. Espero que te sea útil, por eso hago este blog, para ayudar a la gente a que aprenda Java.

      Un saludo.

      Eliminar
  2. Excelente dato sobre estas clases, no sabia de eso y ahora comprendo este técnica de declaración de clases, se agradece el aporte

    ResponderEliminar
    Respuestas
    1. Muchas gracias :-D

      Me alegro de que te haya servido para comprender mejor el tema de las clases internas.

      Un saludo.

      Eliminar
  3. Blog:
    Piensa en Java desde cero
    Comentario:
    Gracias por la información es un excelente dato sobre la declaración de las clases internas, desconocía en qué podía aplicárseles y sobre todo que con ellas podemos eficientar la encapsulación.

    ResponderEliminar
    Respuestas
    1. Muchas gracias, me alegro de que hayas servido para entender mejor las clases internas. Ahora a aplicar lo aprendido :-D

      Un saludo y suerte.

      Eliminar
  4. Programación en java, desde cero hasta avanzado

    www.javeritos.blogspot.com
    Programación java

    ResponderEliminar
  5. Muy buen post, muy interesante y muy útil la información, se valora mucho los aportes y el tiempo dedicado para realizar los mismos! Pulgar arriba ! Te esperamos por nuestro blog !

    ResponderEliminar