Нечетная ситуация для "не может ссылаться на это до того, как конструктор супертипа был вызван"

Почему этот код не компилируется?

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

Ответы

Ответ 1

Следует отметить, что Eclipse, javac и Intellij IDEA демонстрируют различия в поведении в отношении этих фрагментов. javac и поведение Java Puzzlers используется для справки в этом обсуждении.

Мне удалось вырезать фрагмент следующим образом:

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

Этот сценарий обсуждается в Java Puzzlers, головоломка 90: это абсурд, это боль, это суперкласс!

Ниже приведен фрагмент:

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.

Ответ 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()

Надеюсь, это поможет!