Clase anidada estática en Java, ¿por qué?


217

Estaba buscando en el código Java para LinkedListy se dio cuenta de que hacía uso de una clase anidada estática, Entry.

public class LinkedList<E> ... {
...

 private static class Entry<E> { ... }

}

¿Cuál es la razón para usar una clase anidada estática, en lugar de una clase interna normal?

La única razón por la que se me ocurrió fue que Entry no tiene acceso a las variables de instancia, por lo que desde el punto de vista de OOP tiene una mejor encapsulación.

Pero pensé que podría haber otras razones, tal vez el rendimiento. ¿Qué puede ser?

Nota. Espero tener mis términos correctos, lo habría llamado una clase interna estática, pero creo que esto está mal: http://java.sun.com/docs/books/tutorial/java/javaOO/nested.html


Respuestas:


271

La página de Sun a la que se vincula tiene algunas diferencias clave entre los dos:

Una clase anidada es miembro de su clase adjunta. Las clases anidadas no estáticas (clases internas) tienen acceso a otros miembros de la clase adjunta, incluso si se declaran privadas. Las clases anidadas estáticas no tienen acceso a otros miembros de la clase adjunta.
...

Nota: Una clase anidada estática interactúa con los miembros de la instancia de su clase externa (y otras clases) al igual que cualquier otra clase de nivel superior. En efecto, una clase anidada estática es conductualmente una clase de nivel superior que se ha anidado en otra clase de nivel superior por conveniencia de empaquetado.

No es necesario LinkedList.Entryser una clase de nivel superior, ya que solo la utilizan LinkedList(hay algunas otras interfaces que también tienen clases anidadas estáticas denominadas Entry, como Map.Entry- mismo concepto). Y dado que no necesita acceso a los miembros de LinkedList, tiene sentido que sea estático: es un enfoque mucho más limpio.

Como señala Jon Skeet , creo que es una mejor idea si está utilizando una clase anidada para comenzar siendo estática, y luego decidir si realmente necesita ser no estática en función de su uso.


Bah, parece que no puedo obtener un enlace de anclaje al comentario para que funcione, pero es este comentario:#comment113712_253507
Zach Lysobey el

1
@matt b Si una clase anidada estática no tiene acceso a los miembros de la instancia de la clase Outer, ¿cómo interactúa con los miembros de la instancia de la clase Outer?
Geek

1
@mattb Pero cómo @Geek lo ha notado, la página de Sun es contradictoria: A static nested class interacts with the instance members of its outer class (and other classes) just like any other top-level class ¿Cómo es eso posible si solo un párrafo antes de los documentos dice que: Static nested classes do not have access to other members of the enclosing class Tal vez les gustaría decir: A nested (non-static) class interacts with the instance members of its outer class (and other classes) just like any other top-level class
tonix

1
@DavidS ¡Gracias por el enlace! Sí, estaba equivocado, leyendo mi comentario ahora veo que mi reformulación fue incorrecta. Como dijiste:, An inner class interacts with the instance members through an implicit reference to its enclosing classy esto señala otra propiedad interesante de non-static inner classesas anonymous inner classeso or local classes defined inside a block: no todos pueden tener un no-argconstructor porque el compilador antepondrá implícitamente la secuencia arg de cada constructor para pasar una referencia de una instancia del cerramiento clase. Bastante simple.
tonix

1
puede usar la clase interna estática para crear instancias de la clase externa que solo tiene un constructor privado. Esto se usa en el patrón de construcción. No puedes hacer lo mismo con la clase interna.
seenimurugan

47

En mi opinión, la pregunta debería ser al revés cada vez que veas una clase interna: ¿ realmente necesita ser una clase interna, con la complejidad adicional y la referencia implícita (en lugar de explícita y más clara, IMO) a una instancia de la clase que lo contiene?

Eso sí, soy parcial como un fanático de C #: C # no tiene el equivalente de las clases internas, aunque sí tiene tipos anidados. No puedo decir que me haya perdido las clases internas todavía :)


44
Podría estar equivocado, pero eso me parece un ejemplo de una clase anidada estática, no una clase interna. Incluso especifican en el ejemplo que no tienen acceso a las variables de instancia en la clase circundante en la clase anidada.
ColinD

Sí, Colin tiene razón: C # no tiene clases internas, tiene clases anidadas. Eso sí, una clase anidada estática en C # no es lo mismo que una clase anidada estática en Java :)
Jon Skeet

2
Los tipos anidados son una de esas áreas donde C # lo hizo extremadamente correcto en comparación con Java. Siempre me maravillo de su corrección semántica / lógica ..
nawfal

3
@nawfal: Sí, salvo algunos inconvenientes, estoy asombrado de lo bien que se ha diseñado (y especificado) el lenguaje C #.
Jon Skeet

1
@ JonSkeet, ¿tienes un artículo o blog sobre cuáles son esas tonterías? Me encantaría ir a través de lo que encuentras como "críticas" :)
nawfal

27

Hay problemas de retención de memoria no obvios a tener en cuenta aquí. Dado que una clase interna no estática mantiene una referencia implícita a su clase 'externa', si una instancia de la clase interna está fuertemente referenciada, entonces la instancia externa también está fuertemente referenciada. Esto puede conducir a rascarse la cabeza cuando la clase externa no se recolecta basura, aunque parece que nada hace referencia a ella.


Si la clase 'externa' es final y, por lo tanto, no se puede instanciar en absoluto, ¿tiene sentido este argumento en ese caso? Porque tener / mantener una referencia a una clase externa es inútil, si esta última es definitiva.
getsadzeg

10

Bueno, por un lado, las clases internas no estáticas tienen un campo oculto adicional que apunta a la instancia de la clase externa. Entonces, si la clase Entry no fuera estática, además de tener acceso que no necesita, llevaría alrededor de cuatro punteros en lugar de tres.

Como regla, diría que si define una clase que básicamente está ahí para actuar como una colección de miembros de datos, como una "estructura" en C, considere hacerlo estático.


8

La clase interna estática se usa en el patrón de construcción. La clase interna estática puede crear una instancia de su clase externa que solo tiene un constructor privado. Por lo tanto, puede usar la clase interna estática para crear instancias de la clase externa que solo tiene un constructor privado. No puede hacer lo mismo con la clase interna ya que necesita tener un objeto de la clase externa creado antes de acceder a la clase interna.

class OuterClass {
    private OuterClass(int x) {
        System.out.println("x: " + x);
    }

    static class InnerClass {
        public static void test() {
            OuterClass outer = new OuterClass(1);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        OuterClass.InnerClass.test();
        // OuterClass outer = new OuterClass(1); // It is not possible to create outer instance from outside.
    }
}

Esto generará x: 1


7

La clase anidada estática es como cualquier otra clase externa, ya que no tiene acceso a los miembros de la clase externa.

Solo por conveniencia de empaque podemos agrupar las clases anidadas estáticas en una clase externa para fines de legibilidad. Aparte de esto, no hay otro caso de uso de clase anidada estática.

Ejemplo para este tipo de uso, puede encontrarlo en el archivo Android R.java (recursos). La carpeta Res de Android contiene diseños (que contienen diseños de pantalla), carpeta dibujable (que contiene imágenes utilizadas para el proyecto), carpeta de valores (que contiene constantes de cadena), etc.

Como todas las carpetas son parte de la carpeta Res, la herramienta de Android genera un archivo R.java (recursos) que contiene internamente muchas clases anidadas estáticas para cada una de sus carpetas internas.

Aquí está la apariencia del archivo R.java generado en Android: aquí se usan solo para la comodidad del empaque.

/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found.  It
 * should not be modified by hand.
 */

package com.techpalle.b17_testthird;

public final class R {
    public static final class drawable {
        public static final int ic_launcher=0x7f020000;
    }
    public static final class layout {
        public static final int activity_main=0x7f030000;
    }
    public static final class menu {
        public static final int main=0x7f070000;
    }
    public static final class string {
        public static final int action_settings=0x7f050001;
        public static final int app_name=0x7f050000;
        public static final int hello_world=0x7f050002;
    }
}


4

Ejemplo simple:

package test;

public class UpperClass {
public static class StaticInnerClass {}

public class InnerClass {}

public static void main(String[] args) {
    // works
    StaticInnerClass stat = new StaticInnerClass();
    // doesn't compile
    InnerClass inner = new InnerClass();
}
}

Si no es estática, la clase no se puede instanciar exept en una instancia de la clase superior (así que no en el ejemplo donde main es una función estática)


su StaticInnerClass no es, de hecho, una clase estática anidada / interna. Es una clase estática de nivel superior.
theRiley

2

Una de las razones para estática vs. normal tiene que ver con la carga de clases. No puede crear una instancia de una clase interna en el constructor de su padre.

PD: Siempre he entendido que 'anidado' e 'interno' son intercambiables. Puede haber sutiles matices en los términos, pero la mayoría de los desarrolladores de Java lo entenderían.


1

Las clases internas no estáticas pueden provocar pérdidas de memoria, mientras que la clase interna estática protegerá contra ellas. Si la clase externa contiene datos considerables, puede reducir el rendimiento de la aplicación.


'estático interno' es una contradicción en los términos.
Marqués de Lorne

1
@EJP, sheesh ... la gente realmente se despega al señalar esto cada vez que alguien menciona "clases internas estáticas" ...
Sakiboy

0

No sé acerca de la diferencia de rendimiento, pero como usted dice, la clase anidada estática no es parte de una instancia de la clase adjunta. Parece simplemente más simple crear una clase anidada estática a menos que realmente necesites que sea una clase interna.

Es un poco como por qué siempre hago que mis variables sean definitivas en Java: si no son definitivas, sé que hay algo divertido que sucede con ellas. Si usa una clase interna en lugar de una clase anidada estática, debería haber una buena razón.


Una clase interna tampoco es 'parte de una instancia de la clase que lo encierra'.
Marqués de Lorne

una clase interna depende existencialmente de la clase envolvente y tiene acceso íntimo a los miembros de la clase envolvente, por lo que en realidad es parte de la clase envolvente. De hecho, es un miembro.
theRiley

0

El uso de una clase anidada estática en lugar de una no estática puede ahorrar espacios en algunos casos. Por ejemplo: implementar un Comparatordentro de una clase, por ejemplo, Estudiante.

public class Student {
  public static final Comparator<Student> BY_NAME = new ByName();
  private final String name;
  ...
  private static class ByName implements Comparator<Student> {
    public int compare() {...}
  }
}

Luego, staticgarantiza que la clase de Alumno tenga un solo Comparador, en lugar de crear una instancia nueva cada vez que se crea una nueva instancia de alumno.


-1

Una ventaja de la clase interna

  1. un solo uso
  2. apoya y mejora la encapsulación
  3. legibilidad
  4. acceso de campo privado

Sin existir de clase externa, la clase interna no existirá.

class car{
    class wheel{

    }
}

Hay cuatro tipos de clase interna.

  1. clase interna normal
  2. Método Clase interna local
  3. Clase interna anónima
  4. clase interna estática

punto ---

  1. desde la clase interna estática, solo podemos acceder al miembro estático de la clase externa.
  2. Dentro de la clase interna no podemos declarar miembro estático.
  3. para invocar la clase interna normal en el área estática de la clase externa.

    Outer 0=new Outer(); Outer.Inner i= O.new Inner();

  4. para invocar la clase interna normal en el área de instancia de la clase externa.

    Inner i=new Inner();

  5. para invocar la clase interna normal fuera de la clase externa.

    Outer 0=new Outer(); Outer.Inner i= O.new Inner();

  6. inside Inner class Este puntero a la clase interna.

    this.member-current inner class outerclassname.this--outer class

  7. para el modificador aplicable de la clase interna es - public, default,

    final,abstract,strictfp,+private,protected,static

  8. external $ inner es el nombre del nombre de la clase interna.

  9. clase interna dentro del método de instancia, entonces podemos acceder al campo estático y de instancia de clase externa

10. clase interna dentro del método estático, entonces solo podemos acceder al campo estático de

clase exterior

class outer{

    int x=10;
    static int y-20;

    public void m1() {
        int i=30;
        final j=40;

        class inner{

            public void m2() {
                // have accees x,y and j
            }
        }
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.