【问题标题】:Odd situation for "cannot reference this before supertype constructor has been called"“在调用超类型构造函数之前无法引用 this”的奇怪情况
【发布时间】:2010-08-01 19:40:06
【问题描述】:

为什么这段代码不能编译?

public class A {
    public class B extends A {
        public B(A a) { }
    }
    void foo() {
        A a = new A();
        new B(a) { };
    }
}

A.java:[7,17] cannot reference this before supertype constructor has been called

如果进行了以下任一更改,则编译成功:

  • B 是私有的而不是公共的
  • 第 7 行读取 new B(A); 而不是 new B(A) { }

使用javac版本:1.6.0_20

【问题讨论】:

  • 你认为new B(a) {} 会做什么?
  • new B(a) {} 应该实例化 B 的匿名子类,其默认构造函数调用 super(a)
  • 显然,我正在尝试编写的实际代码在该块中有覆盖方法 - new B(a) { /* stuff goes here */} - 但我删除了不必要的代码,目的是简化这个问题。
  • 它在 Joshua Bloch 的 Java Puzzlers 中排名第 90。在其示例章节中作为 #9 提供:javapuzzlers.com/java-puzzlers-sampler.pdf。就个人而言,即使在阅读解决方案后我也无法理解。
  • 我还要注意 Intellij IDEA 不同意每个人 - 它认为所有这些代码都很好。

标签: java


【解决方案1】:

应该注意的是,Eclipse、javac 和 Intellij IDEA 在这些 sn-ps 方面表现出不同的行为。 javacJava Puzzlers 行为在此讨论中用作参考。

我能够将 sn-p 缩减为以下内容:

public class A {
    class B extends A {
    }
    void foo() {
        new B() { }; // DOES NOT COMPILE!!
    }
}

Java Puzzlers 中讨论了这种情况,谜题 90:这很荒谬,很痛苦,它是超类!

给出的sn-p如下:

public class Outer {                   // "A"
    class Inner1 extends Outer  {}     // "B"
    class Inner2 extends Inner1 {}     // "B" anonymous
}
// DOES NOT COMPILE!!

问题在于,由于默认构造函数的定义方式,我们确实有以下内容:

// Same as above but with default constructor included explicitly
public class Outer {
    class Inner1 extends Outer  { 
        Inner1() { super(); }
    }
    class Inner2 extends Inner1 {
        Inner2() { super(); }    
    }
}
// STILL DOES NOT COMPILE!!

问题是Inner2 的超类本身就是一个内部类Inner1,因此Inner2 的默认构造函数是非法的,因为它需要为构造函数提供一个封闭实例。

解决问题的“蛮力”方法是明确地提供一个合格的-this 表达式:

// "brute-force" fix
public class Outer {
    class Inner1 extends Outer  { 
        Inner1() { super(); }
    }
    class Inner2 extends Inner1 {
        Inner2() { Outer.this.super(); }    
    }
}
// NOW COMPILES!

然而,这个谜题规定,首先最好避免这种复杂的情况。以下是一些引用:

这可以编译,但它非常复杂。有一个更好的解决方案:每当你写一个成员类时,问问自己,这个类真的需要一个封闭的实例吗?如果答案是否定的,请将其设为static。内部类有时很有用,但它们很容易引入使程序难以理解的复杂性。它们与泛型(谜题 89)、反射(谜题 80)和继承(本谜题)有着复杂的交互。如果您将Inner1 声明为static,问题就消失了。如果您还将Inner2 声明为static,您实际上可以理解该程序的作用:确实是一个不错的奖励。

总之,一个类既是内部类又是另一个类的子类很少适合。更一般地说,扩展内部类很少合适。如果必须,请仔细考虑封闭的实例。此外,与非static 相比,更喜欢static 嵌套类。大多数成员类可以并且应该声明为static

【讨论】:

  • axtavt 链接的文档进一步简化了这一点。只使用命名类: public class A { class B extends A { } class C extends B { } } 这一次,即使是 IDEA 也注意到了问题:“在调用超类型构造函数之前无法引用 C.this”
  • 很好的答案 - 这在技术上是有意义的,而且它的结论是绝对正确的:我最终遇到了麻烦,因为 B 真的应该首先是静态的。我最终使我的真实代码中的“B”成为顶级类。
【解决方案2】:

不确定目标到底是什么,但试试这个。请注意,我还将 A 作为参数传递为它们已经链接的非静态类。我包含了引用外部类“this”的语法,因为内部类可能会覆盖外部类字段/方法

public class A {

    protected String field = "a";

    public class B extends A {
        protected String field = "b";

        public B() {
            System.out.println("" + A.this.field + " " + this.field);
        }

        @Override
        void foo() {
            System.out.println("b.foo()");
        }
    }

    void foo() {
        A a = new A();
        a.field = "a2";
        B b = a.new B() {
            @Override
            void foo() {
                System.out.println("b.anon.foo()");
            }
        };

        b.foo();
    }

    public static void main(String[] args) {
        new A().foo();
    }
}

运行会输出:

a2 b
b.anon.foo()

希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多