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以某种方式访问EventListener​​封闭类(在这种情况下ThisEscape)。假设我们实现EventSource如下:

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 匿名内部类的定义进行转义。要访问iEventListener 类的导出版本中的变量,您需要执行以下操作:

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对象并不重要。(它可能 - 反射,EventListener在同一个包中的某些方法,由同一个包中的某些方法启用,等等。)重要的ThisEscape是正在以某种方式访问​​的字段,这可能是线程不安全的。

于 2012-09-17T21:15:24.087 回答