4

Java Concurrency in Practiceという本を読んでいます。セクション 3.2 では、内部クラスを公開しながら外部クラスをエスケープする方法について説明しています。今、それを可能にする構文を探しています。私たちが持っているとしましょう:

public class ThisEscape {

    public Integer i = 47;

    public ThisEscape(EventSource source) {
        source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
        });
    }
}

外側のクラスをエスケープすることが正しければ、何らかの形で囲んでいるクラス (この場合は) にEventSourceアクセスできると思います。次のように実装するとします。EventListenerThisEscapeEventSource

public class EventSource {

    public void registerListener(EventListener listener) {
        // How does it have access to enclosing class of the listener variable i?
    }

}

iからパブリック変数にアクセスするにはどうすればよいregisterListenerでしょうか。


ちょうどタイプミスを見つけました。EventSource「囲んでいるクラス」を「囲んでいるクラス」に置き換えましEventListenerた。幸いなことに、誰もが適切なバージョンを入手できました。

4

3 に答える 3

4

は、登録を介して をThisEscape発行します。 しかし同時に、内部クラスのインスタンスには外側のクラスへの非表示の参照が含まれているため、外側のインスタンスを暗黙的に発行します。 本で解説されています。読む。 この例は、構築中にどのようにエスケープできるかを示しています。 これを構文的に説明できる最も近いのは次のようなものです。 EventListener
ThisEscape

this

public class ThisEscape {

    public Integer i = 47;

    public ThisEscape(EventSource source) {
            source.registerListener(
                    new EventListener() {
                            ThisEscape outerRef = ThisEscape.this;//added by compiler
                            public void onEvent(Event e) {
                                    doSomething(e);
                            }
                    });
            }
    }

おそらく、ここにいる友人が、より正確で正確な技術的な構文を提供してくれるでしょう。
しかし、ご覧のように、コンパイラはいくつかの追加コードを追加して、内部クラスが外部クラスのメンバーにアクセスできるようにします。
ここでの問題は、パブリッシュされたオブジェクトが作成中であることです。つまり、完全には作成されていないということです。

于 2012-09-17T21:14:50.000 に答える
2

Javaでは、内部クラスを定義すると、デフォルトの引数なしコンストラクターを含む内部クラスのすべてのコンストラクターに親が自動的に追加され、非表示フィールドに割り当てられます。これは、内部クラスの親クラスのフィールドを参照する方法です。これは、クラスのバイトコード表現で確認できます。

public class test/ThisEscape {

  // compiled from: ThisEscape.java
  // access flags 0x0
  INNERCLASS test/ThisEscape$1 null null

  // access flags 0x1
  public Ljava/lang/Integer; i

  // access flags 0x1
  public <init>(Ltest/EventSource;)V
      L0
    LINENUMBER 8 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 6 L1
    ALOAD 0
    BIPUSH 47
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    PUTFIELD test/ThisEscape.i : Ljava/lang/Integer;
   L2
    LINENUMBER 9 L2
    ALOAD 1
    NEW com/kcp/ko/pm/ThisEscape$1
    DUP
    ALOAD 0
    INVOKESPECIAL test/ThisEscape$1.<init> (Lcom/kcp/ko/pm/ThisEscape;)V
    INVOKEVIRTUAL test/EventSource.registerListener (Lcom/kcp/ko/pm/EventListener;)V
   L3
    LINENUMBER 17 L3
    RETURN
   L4
    LOCALVARIABLE this Ltest/ThisEscape; L0 L4 0
    LOCALVARIABLE source Ltest/EventSource; L0 L4 1
    MAXSTACK = 4
    MAXLOCALS = 2
}

パブリックtest/ThisEscape$1.<init> (Ltest/ThisEscape;)Vコンストラクターに注意してください

これにより、別のクラスのコンストラクターで内部クラスを定義するときに問題が発生します(本で強調表示されています)。基本的に、部分的に初期化されたクラスへの参照を「リーク」します。

逃げる状況が少し間違っています。thisパラメータは、EventListener匿名内部クラスの定義を介してエスケープします。エクスポートされたバージョンのEventListenerクラスの変数にアクセスするiには、次の手順を実行する必要があります。

public class ThisEscape {

    public Integer i = 47;

    public ThisEscape(EventSource source) {
        source.registerListener(new ExportedEventListener(this));
    }
}

public class ExportedEventListener implements EventListener{

    private ThisEscape thisEscape;

    public ExportedEventListener(ThisEscape thisEscape){
        this.thisEscape = thisEscape;
    }

    public void onEvent(Event e) {
        System.out.println("i: " + thisEscape.i);
    }
}

しかし、これはまだスレッドセーフではありません。

関連する質問

于 2012-09-17T21:49:54.423 に答える
1

想像EventSourceすると、次のように書かれています。

public class EventSource {
    public void registerListener(EventListener listener) {
        listener.onEvent(null);
    }
}

メモリ モデルの目的上、オブジェクトに直接アクセスできないことは問題ではありませんThisEscape。(リフレクション、同じパッケージ内のいくつかのメソッド on EventListener、同じパッケージ内のいくつかのメソッドによって有効化されるなど) 重要なのは、 のフィールドがThisEscape何らかの方法でアクセスされていることであり、これはおそらくスレッドセーフではありません。

于 2012-09-17T21:15:24.087 に答える