Buscar..


Introducción

Usando Java, los desarrolladores tienen la capacidad de definir una clase dentro de otra clase. Tal clase se llama una clase anidada . Las clases anidadas se denominan clases internas si se declararon como no estáticas; de lo contrario, simplemente se denominan clases anidadas estáticas. Esta página es para documentar y proporcionar detalles con ejemplos sobre cómo usar clases anidadas e internas de Java.

Sintaxis

  • clase pública OuterClass {clase pública InnerClass {}} // las clases internas también pueden ser privadas
  • clase pública OuterClass {clase estática pública StaticNestedClass {}} // Las clases anidadas estáticas también pueden ser privadas
  • método de anulación público () {clase privada LocalClass {}} // Las clases locales son siempre privadas
  • SomeClass anonymousClassInstance = new SomeClass () {}; // Las clases internas anónimas no pueden ser nombradas, por lo tanto el acceso es discutible. Si 'SomeClass ()' es abstracto, el cuerpo debe implementar todos los métodos abstractos.
  • SomeInterface anonymousClassInstance = new SomeInterface () {}; // El cuerpo debe implementar todos los métodos de interfaz.

Observaciones

Terminologia y clasificacion

La especificación de lenguaje Java (JLS) clasifica los diferentes tipos de clases de Java de la siguiente manera:

Una clase de nivel superior es una clase que no es una clase anidada.

Una clase anidada es cualquier clase cuya declaración ocurre dentro del cuerpo de otra clase o interfaz.

Una clase interna es una clase anidada que no se declara explícita o implícitamente como estática.

Una clase interna puede ser una clase miembro no estática , una clase local o una clase anónima . Una clase miembro de una interfaz es implícitamente estática, por lo que nunca se considera una clase interna.

En la práctica, los programadores se refieren a una clase de nivel superior que contiene una clase interna como la "clase externa". Además, hay una tendencia a usar "clase anidada" para referirse solo a clases anidadas estáticas (explícita o implícitamente).

Tenga en cuenta que existe una relación estrecha entre las clases internas anónimas y las lambdas, pero las lambdas son clases.

Diferencias semanticas

  • Las clases de nivel superior son el "caso base". Son visibles a otras partes de un programa sujeto a reglas de visibilidad normales basadas en la semántica del modificador de acceso. Si no son abstractos, se pueden crear instancias de cualquier código en el que los constructores relevantes sean visibles según los modificadores de acceso.

  • Las clases anidadas estáticas siguen las mismas reglas de acceso e instanciación que las clases de nivel superior, con dos excepciones:

    • Una clase anidada se puede declarar como private , lo que la hace inaccesible fuera de su clase de nivel superior.
    • Una clase anidada tiene acceso a los miembros private de la clase adjunta de nivel superior y toda su clase probada.

    Esto hace que las clases anidadas estáticas sean útiles cuando necesita representar múltiples "tipos de entidades" dentro de un límite de abstracción estricto; por ejemplo, cuando las clases anidadas se utilizan para ocultar "detalles de implementación".

  • Las clases internas agregan la capacidad de acceder a variables no estáticas declaradas en los ámbitos adjuntos:

    • Una clase miembro no estática puede referirse a variables de instancia.
    • Una clase local (declarada dentro de un método) también puede referirse a las variables locales del método, siempre que sean final . (Para Java 8 y versiones posteriores, pueden ser efectivamente definitivas ).
    • Una clase interna anónima se puede declarar dentro de una clase o un método, y puede acceder a las variables de acuerdo con las mismas reglas.

    El hecho de que una instancia de clase interna pueda referirse a variables en una instancia de clase adjunta tiene implicaciones para la creación de instancias. Específicamente, se debe proporcionar una instancia adjunta, ya sea implícita o explícitamente, cuando se crea una instancia de una clase interna.

Una pila simple usando una clase anidada

public class IntStack {

    private IntStackNode head;

    // IntStackNode is the inner class of the class IntStack
    // Each instance of this inner class functions as one link in the
    // Overall stack that it helps to represent
    private static class IntStackNode {

        private int val;
        private IntStackNode next;

        private IntStackNode(int v, IntStackNode n) {
            val = v;
            next = n;
        }
    }

    public IntStack push(int v) {
        head = new IntStackNode(v, head);
        return this;
    }

    public int pop() {
        int x = head.val;
        head = head.next;
        return x;
    }
}

Y su uso, que (en particular) no reconoce en absoluto la existencia de la clase anidada.

public class Main {
    public static void main(String[] args) {
 
        IntStack s = new IntStack();
        s.push(4).push(3).push(2).push(1).push(0);

        //prints: 0, 1, 2, 3, 4, 
        for(int i = 0; i < 5; i++) {
            System.out.print(s.pop() + ", ");
        }            
    }
}

Clases anidadas estáticas y no estáticas

Al crear una clase anidada, tiene la opción de tener esa clase estática anidada:

public class OuterClass1 {

    private static class StaticNestedClass {

    }

}

O no estático:

public class OuterClass2 {

    private class NestedClass {

    }

}

En su núcleo, las clases anidadas estáticas no tienen una instancia circundante de la clase externa, mientras que las clases anidadas no estáticas sí las tienen. Esto afecta a dónde y cuándo se permite a una instancia crear una clase anidada, y a qué instancias de esas clases anidadas se les permite acceder. Añadiendo al ejemplo anterior:

public class OuterClass1 {

    private int aField;
    public void aMethod(){}

    private static class StaticNestedClass {
        private int innerField;

        private StaticNestedClass() {
             innerField = aField; //Illegal, can't access aField from static context 
             aMethod();           //Illegal, can't call aMethod from static context 
        }

        private StaticNestedClass(OuterClass1 instance) {
             innerField = instance.aField; //Legal
        }

    }

    public static void aStaticMethod() {
        StaticNestedClass s = new StaticNestedClass(); //Legal, able to construct in static context
        //Do stuff involving s...
    }

}

public class OuterClass2 {

    private int aField;

    public void aMethod() {}

    private class NestedClass {
        private int innerField;

        private NestedClass() {
             innerField = aField; //Legal   
             aMethod(); //Legal
        }
    }

    public void aNonStaticMethod() {
        NestedClass s = new NestedClass(); //Legal
    }

    public static void aStaticMethod() {
        NestedClass s = new NestedClass(); //Illegal. Can't construct without surrounding OuterClass2 instance.
                                         //As this is a static context, there is no surrounding OuterClass2 instance
    }
}

Por lo tanto, su decisión de estática frente a no estática depende principalmente de si necesita poder acceder directamente a los campos y métodos de la clase externa, aunque también tiene consecuencias sobre cuándo y dónde puede construir la clase anidada.

Como regla general, haga que sus clases anidadas sean estáticas a menos que necesite acceder a los campos y métodos de la clase externa. Al igual que hacer que sus campos sean privados, a menos que los necesite, esto disminuye la visibilidad disponible para la clase anidada (al no permitir el acceso a una instancia externa), lo que reduce la posibilidad de error.

Modificadores de Acceso para Clases Internas

Una explicación completa de los modificadores de acceso en Java se puede encontrar aquí . ¿Pero cómo interactúan con las clases internas?

public , como de costumbre, da acceso sin restricciones a cualquier ámbito capaz de acceder al tipo.

public class OuterClass {

    public class InnerClass {

        public int x = 5;

    }

    public InnerClass createInner() {
        return new InnerClass();
    }
}

public class SomeOtherClass {

    public static void main(String[] args) {
        int x = new OuterClass().createInner().x; //Direct field access is legal
    }
}

tanto protected como el modificador predeterminado (de nada) se comportan como se espera también, de la misma manera que lo hacen para las clases no anidadas.

private , curiosamente, no se limita a la clase a la que pertenece. Más bien, se restringe a la unidad de compilación: el archivo .java. Esto significa que las clases externas tienen acceso total a los campos y métodos de la clase interna, incluso si están marcados como private .

public class OuterClass {

    public class InnerClass {

        private int x;
        private void anInnerMethod() {}
    }

    public InnerClass aMethod() {
        InnerClass a = new InnerClass();
        a.x = 5; //Legal
        a.anInnerMethod(); //Legal
        return a;
    }
}

La Clase Interna en sí misma puede tener una visibilidad que no sea public . Al marcarlo como private u otro modificador de acceso restringido, otras clases (externas) no podrán importar y asignar el tipo. Sin embargo, aún pueden obtener referencias a objetos de ese tipo.

public class OuterClass {

    private class InnerClass{}

    public InnerClass makeInnerClass() {
        return new InnerClass();
    }
}

public class AnotherClass {

    public static void main(String[] args) {
        OuterClass o = new OuterClass();
     
        InnerClass x = o.makeInnerClass(); //Illegal, can't find type
        OuterClass.InnerClass x = o.makeInnerClass(); //Illegal, InnerClass has visibility private
        Object x = o.makeInnerClass(); //Legal
    }
}

Clases internas anónimas

Una clase interna anónima es una forma de clase interna que se declara y crea una instancia con una sola declaración. Como consecuencia, no hay un nombre para la clase que pueda usarse en otra parte del programa; Es decir, es anónimo.

Las clases anónimas se utilizan normalmente en situaciones en las que es necesario poder crear una clase de peso ligero que se pase como parámetro. Esto normalmente se hace con una interfaz. Por ejemplo:

public static Comparator<String> CASE_INSENSITIVE =
        new Comparator<String>() {
            @Override
            public int compare(String string1, String string2) {
                return string1.toUpperCase().compareTo(string2.toUpperCase());
            }
        };

Esta clase anónima define un objeto Comparator<String> ( CASE_INSENSITIVE ) que compara dos cadenas ignorando las diferencias en el caso.

Otras interfaces que se implementan e Runnable frecuencia usando clases anónimas son Runnable y Callable . Por ejemplo:

// An anonymous Runnable class is used to provide an instance that the Thread
// will run when started.
Thread t = new Thread(new Runnable() {
        @Override 
        public void run() {
              System.out.println("Hello world");
        }
    });
t.start();  // Prints "Hello world"

Las clases internas anónimas también pueden basarse en clases. En este caso, la clase anónima extends implícitamente la clase existente. Si la clase que se está extendiendo es abstracta, entonces la clase anónima debe implementar todos los métodos abstractos. También puede anular métodos no abstractos.

Constructores

Una clase anónima no puede tener un constructor explícito. En su lugar, se define un constructor implícito que usa super(...) para pasar cualquier parámetro a un constructor en la clase que se está extendiendo. Por ejemplo:

SomeClass anon = new SomeClass(1, "happiness") {
            @Override
            public int someMethod(int arg) {
                // do something
            }
        };

El constructor implícito para nuestra subclase anónima de SomeClass llamará a un constructor de SomeClass que coincida con la firma de llamada SomeClass(int, String) . Si no hay ningún constructor disponible, obtendrá un error de compilación. Cualquier excepción lanzada por el constructor emparejado también es lanzada por el constructor implícito.

Naturalmente, esto no funciona cuando se extiende una interfaz. Cuando creas una clase anónima desde una interfaz, la clase superclase es java.lang.Object que solo tiene un constructor sin argumentos.

Método de clases internas locales

Una clase escrita dentro de un método llamado método clase interna local . En ese caso, el alcance de la clase interna está restringido dentro del método.

Una clase interna de método local puede instanciarse solo dentro del método donde se define la clase interna.

El ejemplo de usar el método local de clase interna:

public class OuterClass {
    private void outerMethod() {
       final int outerInt = 1;
        // Method Local Inner Class
        class MethodLocalInnerClass {
            private void print() {
                System.out.println("Method local inner class " + outerInt);
            }
        }
        // Accessing the inner class
        MethodLocalInnerClass inner = new MethodLocalInnerClass();
        inner.print();
    }

    public static void main(String args[]) {
        OuterClass outer = new OuterClass();
        outer.outerMethod();
    }
}

La ejecución dará una salida: Method local inner class 1 .

Acceso a la clase externa desde una clase interna no estática

La referencia a la clase externa usa el nombre de la clase y this

public class OuterClass {
    public class InnerClass {
        public void method() {
            System.out.println("I can access my enclosing class: " + OuterClass.this);
        }
    }
}

Puede acceder a los campos y métodos de la clase externa directamente.

public class OuterClass {
    private int counter;

    public class InnerClass {
        public void method() {
            System.out.println("I can access " + counter);
        }
    }
}

Pero en caso de colisión de nombres, puede utilizar la referencia de clase externa.

public class OuterClass {
    private int counter;

    public class InnerClass {
        private int counter;
        
        public void method() {
            System.out.println("My counter: " + counter);
            System.out.println("Outer counter: " + OuterClass.this.counter);
            
            // updating my counter
            counter = OuterClass.this.counter;
        }
    }
}

Crear instancia de clase interna no estática desde el exterior.

También se puede crear una clase interna que sea visible para cualquier clase externa a partir de esta clase.

La clase interna depende de la clase externa y requiere una referencia a una instancia de ella. Para crear una instancia de la clase interna, el new operador solo necesita ser llamado en una instancia de la clase externa.

class OuterClass {

    class InnerClass {
    }
}

class OutsideClass {

    OuterClass outer = new OuterClass();
    
    OuterClass.InnerClass createInner() {
        return outer.new InnerClass();
    }
}

Tenga en cuenta el uso como outer.new .



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow