32

これがコンパイルされる理由がわかりません。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

4 に答える 4

32

(編集:いくつかのコメントに答えるために答えを拡張しました)

コンパイラは内部クラスを受け取り、それらを最上位クラスに変換します。プライベート メソッドは内部クラスでのみ使用できるため、コンパイラはパッケージ レベルのアクセス権を持つ新しい「合成」メソッドを追加して、最上位クラスがアクセスできるようにする必要があります。

このようなもの ($ のものはコンパイラによって追加されます):

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 がこのようにする理由は、内部クラスをサポートするために VM の変更を必要としたくなかったためです。そのため、すべての変更をコンパイラ レベルで行う必要がありました。

コンパイラは内部クラスを受け取り、それを最上位クラスに変換します (したがって、VM レベルには内部クラスなどはありません)。次に、コンパイラは新しい「転送」メソッドも生成する必要があります。同じパッケージ内のクラスのみがアクセスできるように、パッケージ レベル (パブリックではない) で作成されます。コンパイラは、プライベート メソッドへのメソッド呼び出しも、生成された「転送」メソッドに更新しました。

メソッドを「パッケージ」として宣言することで、コンパイラにメソッドを生成させることを避けることができます (public、private、および protected の不在)。その欠点は、パッケージ内のどのクラスでもメソッドを呼び出すことができることです。

編集:

はい、生成された (合成) メソッドを呼び出すことはできますが、これは行わないでください!:

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);
            }
        }
    }
}
于 2009-03-19T17:24:35.420 に答える
14

この引用はそれをうまく要約していると思います:

...内部クラスは、宣言クラスのすべてのメンバー (プライベート メンバーも含む) にアクセスできます。実際、内部クラス自体はクラスのメンバーであると言われています。したがって、オブジェクト指向エンジニアリングの規則に従って、クラスのすべてのメンバーにアクセスできる必要があります。

それに続いて、両方の内部クラスは実際には包含クラスの一部であるため、お互いのプライベート メンバーにもアクセスできる必要があります。

于 2009-03-19T17:09:46.407 に答える
4

Java は、$ を含む特別なアクセサーでコンパイルします。したがって、プライベート メソッドにアクセスする Java を作成することはできません。ここで説明:

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

コンパイラによって生成されたメンバーのカテゴリがもう 1 つあります。クラス C のプライベート メンバー m は、あるクラスが別のクラスを囲んでいる場合、またはそれらが共通のクラスによって囲まれている場合、別のクラス D によって使用される可能性があります。仮想マシンはこの種のグループ化を認識しないため、コンパイラは C でアクセス メソッドのローカル プロトコルを作成し、D がメンバー m の読み取り、書き込み、または呼び出しを行えるようにします。これらのメソッドには、access$0、access$1 などの形式の名前が付けられています。公開されることはありません。アクセス メソッドは、内部クラスだけでなく、外側のクラスにも追加できるという点で独特です。

于 2009-03-19T17:06:38.520 に答える