Почему внутренние классы делают приватные методы доступными? - PullRequest
27 голосов
/ 19 марта 2009

Я не понимаю, почему это компилируется. 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();
        }
    }
}

Ответы [ 4 ]

28 голосов
/ 19 марта 2009

(Правка: расширен ответ на некоторые комментарии)

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

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

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 делает это таким образом, заключается в том, что они не хотели требовать изменения виртуальной машины для поддержки внутренних классов, поэтому все изменения должны были выполняться на уровне компилятора.

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

Вы можете избежать генерации метода компилятором, который объявит методы как "package" (отсутствие public, private и protected). Недостатком является то, что любой класс в пакете может вызывать методы.

Edit:

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

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);
            }
        }
    }
}
14 голосов
/ 19 марта 2009

Я думаю эта цитата хорошо подводит итог:

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

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

4 голосов
/ 19 марта 2009

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

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

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

0 голосов
/ 05 марта 2018

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

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

6.6.1 Определение доступности (по крайней мере, начиная с JLS6)

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

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

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

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

...