¿Por qué esta clase interna estática no puede llamar a un método no estático en su clase externa?


Actualmente estoy leyendo Efectiva Java por Joshua Bloch y me encanta! Pero en la página 112 (Ítem 24) Bloch escribe:

Una clase miembro estática es el tipo más simple de clase anidada. Es lo mejor pensado como una clase ordinaria que pasa a ser declarada dentro otra clase y tiene acceso a todos los miembros de la clase que la encierra, incluso los declarados privados.

Y eso realmente me confunde. Yo diría más bien:

Una clase de miembro estático es el tipo más simple de clase anidada. Es lo mejor pensado como una clase ordinaria que pasa a ser declarada dentro otra clase y tiene acceso a todos los miembros estáticos de la clase que encierra, incluso los declarados privados.

Aquí hay un fragmento que ilustra mi comprensión de la cita:

public class OuterClass {

    public void printMessage(String message) {
        System.out.println(message);
    }

    private static class InnerClass {

        public void sayHello() {
            printMessage("Hello world!"); //error: Cannot make a static reference to the non-static method printMessage(String)
        }

    }
}

Puede ver que el método sayHello de InnerClass no tiene acceso al método printMessage de OuterClass, ya que se declara en una clase interna estática mientras que el método printMessage es un método de instancia. Parece que el autor sugiere que una clase miembro estática puede acceder a campos no estáticos de la clase que encierra. Estoy convencido de que he malinterpretado algo en su última frase, pero no puedo averiguar qué. Cualquier ayuda será apreciada!

Editar: Cambié la visibilidad de los dos métodos porque es irrelevante para mi pregunta. Me interesan los miembros estáticos, no los miembros privados.

Author: David Moles, 2018-04-10

5 answers

Solo porque InnerClass es static, no significa que no pueda obtener una referencia a una instancia de OuterClass a través de otros medios, más comúnmente como un parámetro, por ejemplo,

public class OuterClass {

    private void printMessage(String message) {
        System.out.println(message);
    }

    private static class InnerClass {

        private void sayHello(OuterClass outer) {
            outer.printMessage("Hello world!"); // allowed
        }

    }
}

Si InnerClass no hubiera sido anidado dentro de OuterClass, no habría tenido acceso al método private.

public class OuterClass {

    private void printMessage(String message) {
        System.out.println(message);
    }

}

class InnerClass {

    private void sayHello(OuterClass outer) {
        outer.printMessage("Hello world!"); // ERROR: The method printMessage(String) from the type OuterClass is not visible
    }

}
 46
Author: Andreas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-10 20:52:56

Tenga en cuenta el mensaje de error. No está diciendo que no tengas acceso. Está diciendo que el método no se puede llamar. Los métodos de instancia no significan nada sin una instancia para llámalos. Lo que el mensaje de error te está diciendo es que no tienes esa instancia.

Lo que Bloch le está diciendo es que si esa instancia existiera, el código en la clase interna podría llamar métodos de instancia privada en ella.

Digamos que tenemos lo siguiente clase:

public class OuterClass {
  public void publicInstanceMethod() {}
  public static void publicClassMethod() {}
  private void privateInstanceMethod() {}
  private static void privateClassMethod() {}
}

Si tratamos de llamar a esos métodos privados desde alguna clase aleatoria, no podemos:{[16]]}

class SomeOtherClass {
  void doTheThing() {
    OuterClass.publicClassMethod();
    OuterClass.privateClassMethod(); // Error: privateClassMethod() has private access in OuterClass
  }
  void doTheThingWithTheThing(OuterClass oc) {
    oc.publicInstanceMethod();
    oc.privateInstanceMethod();      // Error: privateInstanceMethod() has private access in OuterClass
  }
}

Tenga en cuenta que esos mensajes de error dicen acceso privado.

Si agregamos un método a OuterClass, podemos llamar a esos métodos:

public class OuterClass {
  // ...declarations etc.
  private void doAThing() {
    publicInstanceMethod();  // OK; same as this.publicInstanceMethod();
    privateInstanceMethod(); // OK; same as this.privateInstanceMethod();
    publicClassMethod();
    privateClassMethod();
  }
}

O si añadimos una clase interna estática:

public class OuterClass {
  // ...declarations etc.
  private static class StaticInnerClass {
    private void doTheThingWithTheThing(OuterClass oc) {
      publicClassMethod();  // OK
      privateClassMethod(); // OK, because we're "inside"
      oc.publicInstanceMethod();  // OK, because we have an instance
      oc.privateInstanceMethod(); // OK, because we have an instance
      publicInstanceMethod();  // no instance -> Error: non-static method publicInstanceMethod() cannot be referenced from a static context
      privateInstanceMethod(); // no instance -> Error: java: non-static method privateInstanceMethod() cannot be referenced from a static context
    }
  }
}

Si agregamos una clase interna no estática, parece que podemos hacer magia: {[16]]}

public class OuterClass {
  // ...declarations etc.
  private class NonStaticInnerClass {
    private void doTheThing() {
      publicClassMethod();     // OK
      privateClassMethod();    // OK
      publicInstanceMethod();  // OK
      privateInstanceMethod(); // OK
    }
  }
}

Sin embargo, hay trucos aquí: un no estático interno la clase está siempre asociada con una instancia de la clase externa, y lo que realmente estás viendo es:{[16]]}

  private class NonStaticInnerClass {
    private void doTheThing() {
      publicClassMethod();     // OK
      privateClassMethod();    // OK
      OuterClass.this.publicInstanceMethod();  // still OK
      OuterClass.this.privateInstanceMethod(); // still OK
    }
  }

Aquí, OuterClass.this es una sintaxis especial para acceder a esa instancia externa. Pero solo lo necesita si es ambiguo, por ejemplo, si las clases externa e interna tienen métodos con el mismo nombre.

Tenga en cuenta también que la clase no estática todavía puede hacer las cosas que la estática puede hacer:

  private class NonStaticInnerClass {
    private void doTheThingWithTheThing(OuterClass oc) {
      // 'oc' does *not* have to be the same instance as 'OuterClass.this'
      oc.publicInstanceMethod();
      oc.privateInstanceMethod();
    }
  }

En resumen: public y private son siempre sobre acceso. Punto Bloch está haciendo que las clases internas tengan acceso que otras clases no. Pero ninguna cantidad de acceso le permite llamar a un método de instancia sin decirle al compilador en qué instancia desea llamarlo.

 8
Author: David Moles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-10 22:03:43

La forma en que lo mostraste requiere herencia. Pero los métodos y campos podrían ser accedidos de esta manera:

public class OuterClass {

  private void printMessage(String message) {
    System.out.println(message);
  }

  private static class InnerClass {

    private void sayHello() {
        OuterClass outer = new OuterClass();
        outer.printMessage("Hello world!"); 
    }

  }
}
 6
Author: Ivan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-10 20:52:39

Pero, que la clase interna estática no tiene acceso a la función printMessage no tiene que ver con que es una clase interna, sino que es estática y no puede invocar un método no estático. Creo que el uso de la palabra "estática" que usted propuso estaba implícito en la primera frase. Lo que él está señalando, o eligió enfatizar, es solo que la clase interna todavía puede acceder a los métodos privados de su clase padre. Podría haber pensado que era innecesario o confuso hacer que la distinción estática / no estática en la misma oración, también.

 4
Author: Kris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-10 20:52:40

Como yo lo veo, el texto es absolutamente correcto. Las clases de miembros estáticos pueden acceder a los miembros privados de las clases que encierran (tipo de). Permítanme mostrarles un ejemplo:

public class OuterClass {
    String _name;
    int _age;
    public OuterClass(String name) {
        _name = name;
    }
    public static OuterClass CreateOuterClass(String name, int age) {
        OuterClass instance = new OuterClass(name);
        instance._age = age; // Notice that the private field "_age" of the enclosing class is visible/accessible inside this static method (as it would also be inside of a static member class).
        return instance;
    }
}
 0
Author: Vinicius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-11 05:45:34