11. Clases internas (2)

Resumen del capítulo

  • Por qué se necesitan clases internas
    • Cierres y devoluciones de llamada
    • Clases internas y marco de control.
  • Heredar clase interna
  • ¿Se pueden anular las clases internas?
  • clase interna local
  • identificador de clase interna

Por qué se necesitan clases internas

Hasta ahora hemos visto mucha sintaxis y semántica para describir clases internas, pero esto no responde a la pregunta "¿Por qué se necesitan las clases internas?" Entonces, ¿por qué los diseñadores de Java se tomaron tantas molestias para agregar esta característica básica del lenguaje?

En términos generales, una clase interna hereda de una determinada clase o implementa una interfaz, y el código de la clase interna opera el objeto de la clase externa que la creó. Por lo tanto, puede pensar que las clases internas proporcionan algún tipo de ventana a sus clases externas.

Una pregunta que las clases internas deben responder es: si solo necesita una referencia a una interfaz, ¿por qué no implementar esa interfaz a través de la clase externa? La respuesta es: "Si esto cumple con los requisitos, entonces debería hacerlo." Entonces, ¿cuál es la diferencia entre una clase interna que implementa una interfaz y una clase externa que implementa esta interfaz? La respuesta es: este último no siempre puede disfrutar de la comodidad que brinda la interfaz y, a veces, necesita utilizar la implementación de la interfaz. Entonces, las razones más atractivas para utilizar clases internas son:

Cada clase interna puede heredar independientemente de una implementación (de la interfaz), por lo que si la clase externa ya hereda o no una implementación (de la interfaz) no tiene ningún efecto en la clase interna.

Algunos problemas de diseño y programación son difíciles de resolver sin la capacidad que brindan las clases internas de heredar de múltiples clases concretas o abstractas. Desde esta perspectiva, las clases internas completan la solución a la herencia múltiple. Las interfaces resuelven parte del problema, mientras que las clases internas implementan efectivamente la "herencia múltiple". Es decir, una clase interna permite la herencia de múltiples tipos que no son de interfaz (Anotación: clase o clase abstracta).

Para verlo con más detalle, consideremos una situación en la que dos interfaces deben implementarse de alguna manera en una clase. Debido a la flexibilidad de las interfaces, tiene dos opciones: usar una sola clase o usar una clase interna:

// innerclasses/mui/MultiInterfaces.java
// Two ways a class can implement multiple interfaces
// {java innerclasses.mui.MultiInterfaces}
package innerclasses.mui;
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());
    }
}

Por supuesto, esto supone que la estructura del código en ambos sentidos tiene sentido lógico. Sin embargo, cuando encuentra un problema, generalmente el problema en sí puede brindarle alguna orientación, indicándole si debe usar una clase única o una clase interna. Pero sin otras restricciones, desde el punto de vista de la implementación no hay diferencia entre los ejemplos anteriores y todos funcionan bien.

Si tiene una clase abstracta o una clase concreta en lugar de una interfaz, solo puede usar clases internas para lograr la herencia múltiple:

// innerclasses/MultiImplementation.java
// For concrete or abstract classes, inner classes
// produce "multiple implementation inheritance"
// {java innerclasses.MultiImplementation}
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 necesita resolver el problema de la "herencia múltiple", naturalmente puede codificarlo de otras maneras sin utilizar clases internas. Pero si usa clases internas, puede obtener otras características:

  1. Una clase interna puede tener múltiples instancias, cada instancia tiene su propia información de estado y es independiente de la información de su objeto de clase externa.
  2. Dentro de una única clase externa, puede hacer que varias clases internas implementen la misma interfaz de diferentes maneras o hereden la misma clase.
  3. El momento en que se crea el objeto de clase interna no depende de la creación del objeto de clase externa
  4. Una clase interna no tiene la confusa relación "es-un", es una entidad independiente.

Por ejemplo, si Sequence.java no usa clases internas, debes declarar " La secuencia es un selector ". Solo puede haber un selector para una secuencia específica . Sin embargo, usando clases internas es fácil tener otro método que lo use. Genera un Selector que recorre la secuencia en dirección inversa , solo las clases internas tienen esta flexibilidad.reverseSelector()

Cierres y devoluciones de llamada

Un cierre es un objeto invocable que registra cierta información del ámbito en el que fue creado. A través de esta definición, podemos ver que la clase interna es un cierre orientado a objetos, porque no solo contiene información sobre el objeto de la clase externa (el alcance donde se crea la clase interna), sino que también tiene automáticamente una referencia a esta clase externa. objeto, dentro de este alcance, la clase interna tiene derecho a operar a todos los miembros, incluidos los miembros privados .

Antes de Java 8, las clases internas eran la única forma de implementar cierres. En Java 8, podemos usar expresiones lambda para implementar el comportamiento de cierre y la sintaxis es más elegante y concisa. Aprenderá los detalles relevantes en el capítulo de programación funcional. Aunque es posible que prefiera implementar cierres usando expresiones lambda en lugar de clases internas, verá y necesitará comprender el código que implementa cierres a través de clases internas antes de Java 8, por lo que aún es necesario comprenderlo de esta manera.

Uno de los temas más controvertidos en Java es la creencia de que Java debería incluir algún tipo de mecanismo similar a un puntero para permitir devoluciones de llamadas. A través de devoluciones de llamada, un objeto puede transportar información que le permita llamar al objeto original en un momento posterior. Más adelante veremos que este es un concepto muy útil. Si la devolución de llamada se implementa a través de un puntero, entonces sólo podemos esperar que el programador no haga un mal uso del puntero. Sin embargo, como el lector debería haber aprendido, Java es más cuidadoso y no incluye punteros en el lenguaje.

Proporcionar funciones de cierre a través de clases internas es una solución excelente, que es más flexible y segura que los punteros. Vea el ejemplo a continuación:

// innerclasses/Callbacks.java
// Using inner classes for callbacks
// {java innerclasses.Callbacks}
package innerclasses;
interface Incrementable {
    
    
    void increment();
}
// Very simple to just implement the interface:
class Callee1 implements Incrementable {
    
    
    private int i = 0;
    @Override
    public void increment() {
    
    
        i++;
        System.out.println(i);
    }
}
class MyIncrement {
    
    
    public void increment() {
    
    
        System.out.println("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;
    @Override
    public void increment() {
    
    
        super.increment();
        i++;
        System.out.println(i);
    }
    private class Closure implements Incrementable {
    
    
        @Override
        public void increment() {
    
    
            // Specify outer-class method, otherwise
            // you'll 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();
    }
}

La salida es:

Insertar descripción de la imagen aquí

Este ejemplo demuestra además la diferencia entre una clase externa que implementa una interfaz y una clase interna que implementa esta interfaz. En términos de código, Callee1 es la solución más sencilla. Callee2 hereda de MyIncrement , que ya tiene un método diferente increment()y no tiene ninguna relación con el método esperado por la interfaz Incrementable . increment()Entonces, si Callee2 hereda MyIncrement , no puede anular el método con el propósito de Incrementable , por lo que solo puede usar la clase interna para implementar Incrementableincrement() de forma independiente . También tenga en cuenta que cuando se crea una clase interna, no se agrega a la interfaz de la externa. class.cosas, ni modifica la interfaz de la clase externa.

Tenga en cuenta que en Callee2 , a excepción getCallbackReference()de, otros miembros son privados . Para establecer cualquier conexión con el mundo exterior, se requiere la interfaz Incrementable . Aquí puede ver cómo la interfaz permite que la interfaz sea completamente independiente de la implementación de la interfaz.
La clase interna Cierre implementa Incrementable para proporcionar un "gancho" que devuelve Callee2 , y además seguro. Quien obtenga una referencia a este Incrementable solo podrá llamarlo increment()y no tiene ninguna otra funcionalidad más allá de eso (a diferencia de los punteros, que le permiten hacer muchas cosas).

El constructor de Caller requiere una referencia Incrementable como parámetro (aunque la referencia de devolución de llamada se puede capturar en cualquier momento), y luego, en algún momento posterior, el objeto Caller puede usar esta referencia para volver a llamar a la clase Callee .

El valor de las devoluciones de llamada es su flexibilidad: puede decidir dinámicamente en tiempo de ejecución qué métodos deben llamarse. Por ejemplo, al implementar funciones GUI en una interfaz gráfica, las devoluciones de llamada se utilizan en todas partes.

Clases internas y marco de control.

En el marco de control que se presentará, puede ver ejemplos más específicos del uso de clases internas.

Un marco de aplicación es una clase o un grupo de clases diseñadas para resolver un tipo específico de problema. Para utilizar un marco de aplicación, normalmente se hereda una o más clases y se anulan ciertos métodos. El código que escribe en el método anulado personaliza la solución general proporcionada por el marco de la aplicación para resolver su problema específico. Este es un ejemplo de un método de plantilla en un patrón de diseño. Un método de plantilla contiene la estructura básica de un algoritmo y llama a uno o más métodos reemplazables para completar la operación del algoritmo. Los patrones de diseño siempre separan las cosas que cambian de las que permanecen iguales. En este patrón, los métodos de plantilla son las cosas que permanecen igual y los métodos reemplazables son las cosas que cambian.

Los marcos de control son una clase especial de marcos de aplicaciones que abordan la necesidad de responder a eventos. Los sistemas que responden principalmente a eventos se denominan sistemas impulsados ​​por eventos. Uno de los problemas comunes en el diseño de aplicaciones es la interfaz gráfica de usuario (GUI), que es casi en su totalidad un sistema controlado por eventos.

Para comprender cómo las clases internas permiten la creación y el uso simples de marcos de control, considere un marco de control cuyo trabajo es ejecutar ready()eventos cuando estén "listos". Si bien "listo" puede significar cualquier cosa, en este caso significa un evento desencadenado basado en el tiempo. A continuación se muestra un marco de control, que no contiene información de control específica. action()Esa información se proporciona mediante herencia (cuando se implementan partes del algoritmo ).

Aquí está la interfaz que describe todos los eventos de control. La razón por la que se utilizan clases abstractas en lugar de interfaces reales es porque los comportamientos predeterminados se controlan en función del tiempo. Por ello, se incluyen algunas implementaciones específicas:

// innerclasses/controller/Event.java
// The common methods for any control event
package innerclasses.controller;
import java.time.*; // Java 8 time classes
public abstract class Event {
    
    
    private Instant eventTime;
    protected final Duration delayTime;
    public Event(long millisecondDelay) {
    
    
        delayTime = Duration.ofMillis(millisecondDelay);
        start();
    }
    public void start() {
    
     // Allows restarting
        eventTime = Instant.now().plus(delayTime);
    }
    public boolean ready() {
    
    
        return Instant.now().isAfter(eventTime);
    }
    public abstract void action();
}

Cuando desee ejecutar el Evento y luego llamar start(), entonces el constructor capturará el tiempo (desde el momento en que se crea el objeto), que se obtiene de esta manera: start()obtenga el tiempo actual y luego agregue un tiempo de retraso, para que el El tiempo para desencadenar el evento es el tiempo generado. start()Es un método independiente, no incluido en el constructor, porque puede reiniciar el temporizador después de que se ejecuta el evento, es decir, el objeto Evento se puede reutilizar. Por ejemplo, si desea repetir un evento, simplemente action()llame start()al método en .

ready()Le indica cuándo es el momento de ejecutar action()el método. Por supuesto, puede anular el método en una clase derivada ready()para que el evento pueda desencadenarse en función de otros factores además del tiempo.

El siguiente archivo contiene el marco de control real utilizado para administrar y desencadenar eventos. Los objetos de evento se almacenan en objetos contenedores de tipo Lista <Evento> (pronunciado "lista de Eventos"), que se detallan en colecciones. En la actualidad, los lectores solo necesitan saber que el método se usa para agregar un Evento al final de la Lista , el método se usa para obtener el número de elementos en la Lista , la sintaxis foreach se usa para obtener continuamente los Eventos en el List y el método se utiliza para eliminar el evento especificado de List Event .add()size()remove()

import java.util.*;

public class Controller {
    
    
    // A class from java.util to hold Event objects:
    private List<Event> 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);
                }
            }
        }
    }
}

run()El método recorre la lista de eventos y busca los objetos Ready() y Eventready() que se ejecutarán . Para cada evento ready() encontrado , imprima su información usando el método del objeto , luego elimine el evento de la lista .ready()toString()action()

Tenga en cuenta que en el diseño actual no sabe qué hace el evento . Ésta es la clave de este diseño: "separar las cosas que cambian de las que no cambian". En mis palabras, "vector de cambio" son los diferentes comportamientos de varios objetos de Evento , y usted crea diferentes subclases de Evento para expresar diferentes comportamientos.

Esto es exactamente lo que hacen las clases internas, las clases internas permiten:

  1. La implementación completa del marco de control se crea a partir de una única clase, lo que permite encapsular los detalles de la implementación. Las clases internas se utilizan para representar los distintos componentes necesarios para resolver un problema action().
  2. La clase interna puede acceder fácilmente a cualquier miembro de la clase externa, por lo que se puede evitar que esta implementación se vuelva difícil de manejar. Sin esta capacidad, el código se volverá tan molesto que definitivamente elegirás hacer otra cosa.

Considere una implementación específica de este marco de control, como controlar el funcionamiento de un invernadero: controlar las luces, el agua, los interruptores del termostato y hacer sonar y reiniciar el sistema, cada comportamiento es completamente diferente. El diseño del marco de control hace que sea muy fácil separar estos códigos diferentes. Usando clases internas, puede generar múltiples versiones derivadas del mismo evento de clase base en una sola clase . Para cada comportamiento del sistema de invernadero, se hereda una nueva clase interna de Evento y action()se escribe en el código de control a implementar.

Como marco de aplicación típico, la 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); 
        }
        @Override
        public void action() {
    
    
            // Put hardware control code here to
            // physically turn on the light.
            light = true;
        }
        @Override
        public String toString() {
    
    
            return "Light is on";
        }
    }
    public class LightOff extends Event {
    
    
        public LightOff(long delayTime) {
    
    
            super(delayTime);
        }
        @Override
        public void action() {
    
    
            // Put hardware control code here to
            // physically turn off the light.
            light = false;
        }
        @Override
        public String toString() {
    
    
            return "Light is off";
        }
    }
    private boolean water = false;
    public class WaterOn extends Event {
    
    
        public WaterOn(long delayTime) {
    
    
            super(delayTime);
        }
        @Override
        public void action() {
    
    
            // Put hardware control code here.
            water = true;
        }
        @Override
        public String toString() {
    
    
            return "Greenhouse water is on";
        }
    }
    public class WaterOff extends Event {
    
    
        public WaterOff(long delayTime) {
    
    
            super(delayTime);
        }
        @Override
        public void action() {
    
    
            // Put hardware control code here.
            water = false;
        }
        @Override
        public String toString() {
    
    
            return "Greenhouse water is off";
        }
    }
    private String thermostat = "Day";
    public class ThermostatNight extends Event {
    
    
        public ThermostatNight(long delayTime) {
    
    
            super(delayTime);
        }
        @Override
        public void action() {
    
    
            // Put hardware control code here.
            thermostat = "Night";
        }
        @Override
        public String toString() {
    
    
            return "Thermostat on night setting";
        }
    }
    public class ThermostatDay extends Event {
    
    
        public ThermostatDay(long delayTime) {
    
    
            super(delayTime);
        }
        @Override
        public void action() {
    
    
            // Put hardware control code here.
            thermostat = "Day";
        }
        @Override
        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);
        }
        @Override
        public void action() {
    
    
            addEvent(new Bell(delayTime.toMillis()));
        }
        @Override
        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);
        }
        @Override
        public void action() {
    
    
            for(Event e : eventList) {
    
    
                e.start(); // Rerun each event
                addEvent(e);
            }
            start(); // Rerun this Event
            addEvent(this);
        }
        @Override
        public String toString() {
    
    
            return "Restarting system";
        }
    }
    public static class Terminate extends Event {
    
    
        public Terminate(long delayTime) {
    
    
            super(delayTime);
        }
        @Override
        public void action() {
    
     System.exit(0); }
        @Override
        public String toString() {
    
    
            return "Terminating";
        }
    }
}

Tenga en cuenta que la luz , el agua y el termostato pertenecen a la clase externa GreenhouseControls , y estas clases internas pueden acceder libremente a esos campos sin calificaciones ni permisos especiales. Además, action()los métodos suelen implicar controlar algún tipo de hardware.

La mayoría de las clases de eventos son similares, pero Bell y Restart son especiales. Bell controla el timbre y luego agrega un objeto Bell a la lista de eventos para que pueda volver a sonar después de un tiempo. Los lectores habrán notado cómo la clase interna se parece a la herencia múltiple: Bell y Restart tienen todos los métodos de Event y parecen tener todos los métodos de la clase externa GreenhouseContrlos .

Se pasa una matriz de objetos de evento a Reiniciar para agregarlos al controlador. Dado que Restart()también es un objeto de evento , también se puede agregar el objeto ReiniciarRestart.action() para que el sistema pueda reiniciarse regularmente.

La siguiente clase configura el sistema creando un objeto GreenhouseControls y agregando varios objetos Event . Este es un ejemplo del patrón de diseño de comandos : cada objeto en eventList se encapsula como una solicitud para el objeto:

// innerclasses/GreenhouseController.java
// Configure and execute the greenhouse system
import innerclasses.controller.*;
public class GreenhouseController {
    
    
    public static void main(String[] args) {
    
    
        GreenhouseControls gc = new GreenhouseControls();
        // Instead of using code, you could parse
        // configuration information from a text file:
        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));
        gc.addEvent(new GreenhouseControls.Terminate(5000));
        gc.run();
    }
}

La salida es:

Insertar descripción de la imagen aquí

La función de esta clase es inicializar el sistema, por lo que agrega todos los eventos correspondientes. El evento Restart se ejecuta repetidamente y carga eventList en el objeto GreenhouseControls cada vez. Si se proporciona un argumento de línea de comando, se usa como el número de milisegundos al decidir cuándo finalizar el programa (esto se usa al probar el programa).

Por supuesto, un enfoque más flexible es evitar eventos codificados.

Este ejemplo debería dar a los lectores una mejor idea del valor de las clases internas, especialmente cuando se usan en un marco de control.

Heredar clase interna

Las cosas se vuelven un poco más complicadas cuando se hereda de una clase interna porque el constructor de la clase interna debe estar vinculado a una referencia al objeto de su clase externa. El problema es que esa referencia "secreta" al objeto de clase externa debe inicializarse y ya no hay un objeto predeterminado vinculable en la clase derivada. Para solucionar este problema, se debe utilizar una sintaxis especial para indicar explícitamente la relación entre ellos:

// 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);
    }
}

Como puede ver, InheritInner solo hereda de clases internas, no de clases externas. Pero cuando se trata de generar un constructor, el constructor predeterminado no es bueno y no se puede simplemente pasar una referencia al objeto de clase externa. Además, se debe utilizar la siguiente sintaxis dentro del constructor:

enclosingClassReference.super();

De esta forma se proporcionan las referencias necesarias y luego se puede compilar el programa.

¿Se pueden anular las clases internas?

¿Qué sucede si crea una clase interna, luego hereda de su clase externa y redefine esta clase interna? Es decir, ¿se pueden anular las clases internas? Esto puede parecer una idea útil, pero "anular" la clase interna como si fuera un método de la clase externa en realidad no hace nada:

// innerclasses/BigEgg.java
// An inner class cannot be overridden like a method
class Egg {
    
    
    private Yolk y;
    protected class Yolk {
    
    
        public Yolk() {
    
    
            System.out.println("Egg.Yolk()");
        }
    }
    Egg() {
    
    
        System.out.println("New Egg()");
        y = new Yolk();
    }
}
public class BigEgg extends Egg {
    
    
    public class Yolk {
    
    
        public Yolk() {
    
    
            System.out.println("BigEgg.Yolk()");
        }
    }
    public static void main(String[] args) {
    
    
        new BigEgg();
    }
}

La salida es:

New Egg()
Egg.Yolk()

El compilador genera automáticamente el constructor predeterminado sin argumentos; aquí está el constructor predeterminado para llamar a la clase base. Podría pensar que desde que se crea el objeto BigEgg , se debe usar la versión "reescrita" de Yolk , pero puede ver en el resultado que este no es el caso.

Este ejemplo muestra que al heredar una clase externa, la clase interna no sufre ningún cambio mágico especial. Estas dos clases internas son dos entidades completamente independientes, cada una en su propio espacio de nombres. Por supuesto, también es posible heredar explícitamente una clase interna:

// innerclasses/BigEgg2.java
// Proper inheritance of an inner class
class Egg2 {
    
    
    protected class Yolk {
    
    
        public Yolk() {
    
    
            System.out.println("Egg2.Yolk()");
        }
        public void f() {
    
    
            System.out.println("Egg2.Yolk.f()");
        }
    }
    private Yolk y = new Yolk();
    Egg2() {
    
     System.out.println("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() {
    
    
            System.out.println("BigEgg2.Yolk()");
        }
        @Override
        public void f() {
    
    
            System.out.println("BigEgg2.Yolk.f()");
        }
    }
    public BigEgg2() {
    
     insertYolk(new Yolk()); }
    public static void main(String[] args) {
    
    
        Egg2 e2 = new BigEgg2();
        e2.g();
    }
}

La salida es:

Insertar descripción de la imagen aquí

Ahora BigEgg2.Yolk hereda explícitamente esta clase interna al extender Egg2.Yolk y anula los métodos que contiene. insertYolk()El método permite a BigEgg2 convertir su propio objeto Yolk a una referencia y en Egg2 . Entonces, cuando se llama , se ejecuta la nueva versión reescrita de . La segunda llamada da como resultado que el constructor de BigEgg2.Yolk llame al constructor de su clase base. Puedes ver que al llamar , se llama a la nueva versión de .g()y.f()f()Egg2.Yolk()g()f()

clase interna local

Como se mencionó anteriormente, las clases internas se pueden crear dentro de un bloque de código, generalmente dentro del cuerpo de un método. Una clase interna local no puede tener un especificador de acceso porque no es parte de la clase externa; pero puede acceder a constantes dentro del bloque de código actual, así como a todos los miembros de la clase externa. El siguiente ejemplo compara la creación de clases internas locales con clases internas anónimas.

interface Counter {
    
    
    int next();
}

public class LocalInnerClass {
    
    
    private int count = 0;

    Counter getCounter(final String name) {
    
    
        // A local inner class:
        class LocalCounter implements Counter {
    
    
            LocalCounter() {
    
    
                // Local inner class can have a constructor
                System.out.println("LocalCounter()");
            }

            @Override
            public int next() {
    
    
                System.out.print(name); // Access local final
                return count++;
            }
        }
        return new LocalCounter();
    }

    // Repeat, but 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:
            {
    
    
                System.out.println("Counter()");
            }

            @Override
            public int next() {
    
    
                System.out.print(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++) {
    
    
            System.out.println(c1.next());
        }
        for (int i = 0; i < 5; i++) {
    
    
            System.out.println(c2.next());
        }
    }
}

La salida es:

Insertar descripción de la imagen aquí

El contador devuelve el siguiente valor de la secuencia. Logramos esto usando clases internas locales y anónimas respectivamente, tienen el mismo comportamiento y capacidades, ya que el nombre de la clase interna local no es visible fuera del método, ¿por qué seguimos usando clases internas locales en lugar de clases internas anónimas? La única razón es que necesitamos un constructor con nombre, o necesitamos sobrecargar el constructor, y las clases internas anónimas solo se pueden inicializar con instancias.

Entonces, otra razón para usar clases internas locales en lugar de clases internas anónimas es que necesita más de un objeto de la clase interna.

identificador de clase interna

Porque cada clase generará un archivo .class después de la compilación , que contiene toda la información sobre cómo crear un objeto de este tipo (esta información genera una "metaclase", llamada objeto Clase ).

Como habrás adivinado, las clases internas también deben generar un archivo .class para contener la información del objeto Clase . Existen reglas estrictas para nombrar estos archivos de clase: el nombre de la clase externa, más "$" y el nombre de la clase interna. Por ejemplo, el archivo .class generado por LocalInnerClass.java incluye:

Counter.class
LocalInnerClass$1.class
LocalInnerClass$1LocalCounter.class
LocalInnerClass.class

Si la clase interna es anónima, el compilador simplemente generará un número como identificador. Si la clase interna está anidada en otras clases internas, simplemente agregue sus nombres directamente después del identificador de clase externa y "$" .

Si bien este formato de denominación es simple y directo, es lo suficientemente sólido como para manejar la gran mayoría de los casos. Debido a que esta es la convención de nomenclatura estándar de Java, los archivos resultantes son automáticamente independientes de la plataforma. (Tenga en cuenta que el compilador de Java hará todo lo posible para convertir sus clases internas para garantizar que funcionen).

Supongo que te gusta

Origin blog.csdn.net/GXL_1012/article/details/132481240
Recomendado
Clasificación