Почему внутренние классы делают доступными частные методы?

Я не понимаю, почему это компилируется. f() и g() видны из внутренних классов, несмотря на то, что они закрыты. Обращаются ли с ними по-особому, потому что они являются внутренними классами?

Если A и B не являются статическими классами, это все равно.

class NotPrivate {
    private static class A {
        private void f() {
            new B().g();
        }
    }

    private static class B {
        private void g() {
            new A().f();
        }
    }
}

person mparaz    schedule 19.03.2009    source источник
comment
Как уже было сказано: причина в том, как в JLS указан модификатор private. В противном случае, если член или конструктор объявлен закрытым, то доступ разрешен тогда и только тогда, когда он [доступ] происходит в теле класса верхнего уровня [!!] (§7.6), который заключает в себе объявление члена или конструктора. . Таким образом, частное всегда относится к окружающему классу верхнего уровня. У вас всегда есть доступ ко всему в классе высшего уровня.   -  person A Dude    schedule 29.03.2018
comment
Здесь нет внутренних классов. Есть статические вложенные классы.   -  person user207421    schedule 23.04.2019


Ответы (4)


(Редактировать: расширенный ответ, чтобы ответить на некоторые комментарии)

Компилятор берет внутренние классы и превращает их в классы верхнего уровня. Поскольку частные методы доступны только для внутреннего класса, компилятор должен добавить новые «синтетические» методы, которые имеют доступ на уровне пакета, чтобы классы верхнего уровня имели к ним доступ.

Примерно так (знаки $ добавляются компилятором):

class A 
{
    private void f() 
    {
        final B b;

        b = new B();

        // call changed by the compiler
        b.$g();
    }

    // method generated by the compiler - visible by classes in the same package
    void $f()
    {
        f();
    }
}

class B
{
    private void g() 
    {
        final A a;

        a = new A();

        // call changed by the compiler
        a.$f();
    }

    // method generated by the compiler - visible by classes in the same package
    void $g()
    {
        g();
    }
}

Нестатические классы такие же, но в них добавлена ​​ссылка на внешний класс, так что методы могут быть вызваны для него.

Причина, по которой Java делает это таким образом, заключается в том, что они не хотели требовать изменений виртуальной машины для поддержки внутренних классов, поэтому все изменения должны были быть на уровне компилятора.

Компилятор берет внутренний класс и превращает его в класс верхнего уровня (таким образом, на уровне виртуальной машины нет такого понятия, как внутренний класс). Затем компилятор также должен сгенерировать новые методы «пересылки». Они создаются на уровне пакетов (не общедоступных), чтобы гарантировать, что только классы в одном пакете могут получить к ним доступ. Компилятор также обновил вызовы частных методов для сгенерированных методов «пересылки».

Вы можете избежать того, чтобы компилятор генерировал метод, объявляя методы как «пакет» (отсутствие общедоступных, частных и защищенных). Недостатком этого является то, что любой класс в пакете может вызывать методы.

Редактировать:

Да, вы можете вызвать сгенерированный (синтетический) метод, но НЕ ДЕЛАЙТЕ ЭТОГО!:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Main
{
    public static void main(final String[] argv)
        throws Exception
    {
        final Class<?> clazz;

        clazz = Class.forName("NotPrivate$A");        

        for(final Method method : clazz.getDeclaredMethods())
        {
            if(method.isSynthetic())
            {
                final Constructor constructor;
                final Object instance;

                constructor = clazz.getDeclaredConstructor(new Class[0]);
                constructor.setAccessible(true);
                instance = constructor.newInstance();
                method.setAccessible(true);
                method.invoke(null, instance);
            }
        }
    }
}
person TofuBeer    schedule 19.03.2009
comment
Итак, вы объявляете метод закрытым, а компилятор делает его общедоступным вместо того, чтобы сгенерировать ошибку? - person cbrulak; 19.03.2009
comment
ну, это не общедоступно, это пакет. И да, это то, что он делает. Это связано с тем, что виртуальная машина не знает внутренних классов... поэтому компилятор выполняет всю работу вместо того, чтобы заставлять виртуальную машину выполнять специальные действия. - person TofuBeer; 19.03.2009
comment
package является классом, который содержит внутренний класс? - person cbrulak; 19.03.2009
comment
доступ к пакету -- доступен для всех классов в одном пакете (пространстве имен). Он не меняет ваш, он добавляет еще один метод, который вызывает ваш с $ в нем. Невозможно вызвать его с помощью Java, но его можно вызвать с помощью сгенерированного вами байт-кода или, возможно, отражения, но я не уверен в этом. - person Lou Franco; 19.03.2009
comment
Этот ответ говорит что-то о том, как компилятор, возможно, делает это для удовлетворения спецификации jvm, но не о том, почему и о чем просили. - person A Dude; 29.03.2018
comment
И нет, у вас нет доступа на уровне пакета к этим частным внутренним методам. Если у вас есть отдельный класс, определенный в пакете, вы не можете получить доступ к закрытым методам вложенных классов из тела отдельного класса. - person A Dude; 29.03.2018
comment
Чувак, почему он компилируется, требуется, как генерируется код. Как только вы узнаете, как генерируется код, вы поймете, почему он компилируется. Он компилируется, потому что компилятор делает все, что ему нужно, чтобы убедиться, что он компилируется. - person TofuBeer; 30.03.2018
comment
Чувак, я обновил свой ответ, чтобы показать, что да, у тебя есть доступ к сгенерированным (синтетическим) методам. Вы просто должны сделать это с отражением. - person TofuBeer; 30.03.2018
comment
Ты прав. Есть доступ через рефлексию, но это не помогает понять ответ на заданный вопрос. Отражение — это способ обойти модификаторы доступа. Вопрос в том, почему у вас есть доступ даже на уровне языка java, почему компилятор java не выдает ошибку в описанном случае. - person A Dude; 04.04.2018
comment
Весь ответ на заданный вопрос просто таков: модификатор доступа «private» (согласно спецификации) относится к классу уровня TOP, а не к самому внутреннему классу. Это «почему». - person A Dude; 04.04.2018
comment
Я отвечал на И нет, у вас нет доступа на уровне пакета к этим частным внутренним методам, очевидно, у вас есть. - person TofuBeer; 06.04.2018

Я думаю, что эта цитата прекрасно подводит итог:

...внутренние классы могут получить доступ ко всем членам объявляющего класса, даже к закрытым членам. Фактически, сам внутренний класс считается членом класса; поэтому, следуя правилам объектно-ориентированного проектирования, он должен иметь доступ ко всем членам класса.

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

person Eric Petroelje    schedule 19.03.2009
comment
На самом деле, когда ваш класс скомпилирован, все внутренние классы фактически «свернуты», чтобы стать частью содержащего класса. -- Что ты этим имеешь ввиду? - person Michael Myers; 19.03.2009
comment
Я почти уверен, что это неправда. Обычно создаются классы вида Outer$Inner. Вы не можете сделать то, что сказали, потому что у вас может быть разное количество объектов внутреннего и внешнего класса. - person Lou Franco; 19.03.2009
comment
Вы правы, это было немного неправильно. Удалил эту часть из моего ответа. - person Eric Petroelje; 19.03.2009
comment
@Eric, и теперь текущая версия не объясняет, почему у них будет доступ - это родственные классы, а не содержащиеся один в другом - person eglasius; 19.03.2009
comment
@Freddy Это родственные классы, но оба в одном содержащем классе и, следовательно, в одной области. Итак, идея состоит в том, что они могут обращаться друг к другу так же, как один частный метод в классе может вызывать другой частный метод в том же классе. - person Eric Petroelje; 19.03.2009

Java компилируется в специальные методы доступа с $ в них. Таким образом, вы не можете написать Java, которая обращается к закрытым методам. Объясняется здесь:

http://www.retrologic.com/innerclasses.doc7.html

Есть еще одна категория элементов, созданных компилятором. Частный член m класса C может использоваться другим классом D, если один класс включает другой или если они заключены в общий класс. Поскольку виртуальная машина не знает о такой группировке, компилятор создает локальный протокол методов доступа в C, чтобы позволить D читать, записывать или вызывать элемент m. Эти методы имеют имена вида access$0, access$1 и т. д. Они никогда не являются общедоступными. Методы доступа уникальны тем, что их можно добавлять к охватывающим классам, а не только к внутренним классам.

person Lou Franco    schedule 19.03.2009

Как объяснил пользователь «Чувак» в комментариях к принятому ответу:

Он компилируется, потому что он должен работать таким образом по спецификации языка, т.е. Спецификация Java Lang говорит так:

6.6.1 Determining Accessibility (at least since JLS6)

«В противном случае, если член или конструктор объявлен закрытым, доступ разрешен тогда и только тогда, когда он происходит в теле класса верхнего уровня (§7.6), который заключает в себе объявление члена или конструктора».

т.е. «область доступа» частного члена: везде в пределах лексических границ тела класса верхнего уровня.

Это означает: ко всем закрытым членам, которые определены в теле самого внешнего класса, можно получить доступ из любого места в этом теле класса.

Например, к частному методу внутреннего класса можно получить доступ из методов внешнего класса или из любого метода другого внутреннего класса внешнего класса.

person EternalBeginner    schedule 05.03.2018