Почему этот статический внутренний класс не может использовать нестатический метод для внешнего класса?

Я сейчас читаю "Эффективную Java" Джошуа Блоха, и мне это нравится! Но на стр. 112 (Пункт 24) Блох пишет:

Статический класс-член - это самый простой тип вложенного класса. Его лучше всего воспринимать как обычный класс, который, как считается, объявляется внутри другого класса и имеет доступ ко всем членам входящих классов, даже к тем, кто объявлен частным.

И это меня действительно смущает. Я бы сказал:

Статический класс-член - это самый простой тип вложенного класса. Это лучше всего воспринимается как обычный класс, который, как считается, объявляется внутри другого класса и имеет доступ ко всем включенным классам статическим членам, даже объявленным частным.

Вот фрагмент, который иллюстрирует мое понимание цитаты:

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

    }
}

Вы можете видеть, что метод InnerClass sayHello не имеет доступа к методу printMessage OuterClass, поскольку он объявлен в статическом внутреннем классе, тогда как метод printMessage является методом экземпляра. Похоже, автор предлагает, чтобы статический класс-член мог получить доступ к нестатическим полям окружающего класса. Я убежден, что в своем последнем предложении я что-то не понял, но я не могу понять, что. Любая помощь будет оценена!

edit: Я изменил видимость двух методов, потому что это не имеет отношения к моему вопросу. Меня интересуют статические члены, а не частные члены.

Ответы

Ответ 1

Просто потому, что InnerClass является static, не означает, что он не мог получить ссылку на экземпляр OuterClass помощью других средств, чаще всего как параметр, например

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
        }

    }
}

Если InnerClass не был вложен в OuterClass, у него не было бы доступа к 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
    }

}

Ответ 2

Обратите внимание на сообщение об ошибке. Это не говорит, что у вас нет доступа. Он говорит, что метод не может быть вызван. Методы экземпляра ничего не значат, если экземпляр не вызывает их. Сообщение об ошибке сообщает вам, что у вас нет этого экземпляра.

Что Блох говорит вам, так это то, что если бы этот экземпляр существовал, код во внутреннем классе мог бы вызвать на нем методы частного экземпляра.

Скажем, у нас есть следующий класс:

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

Если мы попытаемся вызвать эти частные методы из некоторого случайного класса, мы не сможем:

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
  }
}

Обратите внимание, что эти сообщения об ошибках говорят о закрытом доступе.

Если мы добавим метод OuterClass к OuterClass, мы можем вызвать эти методы:

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

Или добавим статический внутренний класс:

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
    }
  }
}

Если мы добавим нестатический внутренний класс, похоже, что мы можем делать магию:

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

Однако здесь происходит обман: нестатический внутренний класс всегда связан с экземпляром внешнего класса, и то, что вы действительно смотрите:

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

Здесь OuterClass.this является специальным синтаксисом для доступа к этому внешнему экземпляру. Но вам это нужно только в том случае, если оно неоднозначно, например, если внешний и внутренний классы имеют методы с тем же именем.

Заметим также, что нестатический класс все еще может делать то, что может сделать статический:

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

Короче говоря, public и private всегда имеют доступ. То, что делает Блох, заключается в том, что внутренние классы имеют доступ к другим классам. Но никакая доступность не позволяет вам вызвать метод экземпляра, не сообщая компилятору, к какому экземпляру вы хотите его называть.

Ответ 3

То, как вы показали это, требует наследования. Но методы и поля могут быть доступны таким образом:

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!"); 
    }

  }
}

Ответ 4

Но, что статический внутренний класс не имеет доступа к функции printMessage, не связан с тем, что он является внутренним классом, но является статичным и не может вызывать нестатический метод. Я думаю, что использование слова "статические", которое вы предложили, было явным в первом предложении. То, что он указывает или предпочитает подчеркнуть, заключается только в том, что внутренний класс все же может получить доступ к закрытым методам своего родительского класса. Возможно, он просто хотел, чтобы в статическом/нестатическом различии в одном и том же предложении было излишним или запутанным.

Ответ 5

Как я это вижу, текст абсолютно прав. Статические классы-члены могут обращаться к закрытым членам закрывающих классов (вроде). Позвольте мне показать вам пример:

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