Проблема с конструкторами вложенного класса


Этот вопрос касается интересного поведения Java: он производит дополнительный (не стандартный) конструктор для вложенных классов в некоторых ситуации.

Этот вопрос также касается странного анонимного класса, который Java производит с помощью этого странного конструктора.


Рассмотрим следующий код:

package a;

import java.lang.reflect.Constructor;

public class TestNested {    
    class A {    
        A() {
        }   

        A(int a) {
        }
    }    

    public static void main(String[] args) {
        Class<A> aClass = A.class;
        for (Constructor c : aClass.getDeclaredConstructors()) {
            System.out.println(c);
        }

    }
}

Это напечатает:

a.TestNested$A(a.TestNested)
a.TestNested$A(a.TestNested,int)

Ok. Затем создадим конструктор A(int a) private:

    private A(int a) {
    }

Запустите программу еще раз. Прием:

a.TestNested$A(a.TestNested)
private a.TestNested$A(a.TestNested,int)

Это тоже нормально. Но теперь давайте модифицируем метод main() таким образом (добавление нового экземпляра класса A):

public static void main(String[] args) {
    Class<A> aClass = A.class;
    for (Constructor c : aClass.getDeclaredConstructors()) {
        System.out.println(c);
    }

    A a = new TestNested().new A(123);  // new line of code
}

Затем ввод будет:

a.TestNested$A(a.TestNested)
private a.TestNested$A(a.TestNested,int)
a.TestNested$A(a.TestNested,int,a.TestNested$1) 

Что это такое: a.TestNested $A (a.TestNested, int, a.TestNested $1) < < ---??

Хорошо, снова создадим конструктор A(int a) package local:

    A(int a) {
    }

Повторить программу снова (мы не удаляем строку с экземпляром A creation!), вывод как в первый раз:

a.TestNested$A(a.TestNested)
a.TestNested$A(a.TestNested,int)

Вопросы:

1) Как это можно объяснить?

2) Что это за третий странный конструктор?


ОБНОВЛЕНИЕ: Исследование показано ниже.

1) Попробуем вызвать этот странный конструктор, используя отражение от другого класса. Мы не сможем это сделать, потому что нет никакого способа создать экземпляр этого странного класса TestNested$1.

2) Хорошо. Давайте сделаем трюк. Давайте добавим к классу TestNested такое статическое поле:

public static Object object = new Object() {
    public void print() {
        System.out.println("sss");
    }
};

Ну? Итак, теперь мы могли бы назвать этот третий странный конструктор из другого класса:

    TestNested tn = new TestNested();
    TestNested.A a = (TestNested.A)TestNested.A.class.getDeclaredConstructors()[2].newInstance(tn, 123, TestNested.object);

Извините, но я этого абсолютно не понимаю.


ОБНОВЛЕНИЕ-2: Дополнительные вопросы:

3) Почему Java использует специальный анонимный внутренний класс для типа аргумента для этого третьего синтетического конструктора? Почему не просто тип Object, конструктора со специальным именем?

4). Какая Java может использовать уже определенный анонимный внутренний класс для этих целей? Разве это не какое-то нарушение безопасности?

Ответ 1

Прежде всего, спасибо за этот интересный вопрос. Я был настолько заинтригован, что не мог удержаться от взгляда на бат-код. Это байт-код TestNested:

Compiled from "TestNested.java"
  public class a.TestNested {
    public a.TestNested();
      Code:
         0: aload_0       
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return        

    public static void main(java.lang.String[]);
      Code:
         0: ldc_w         #2                  // class a/TestNested$A
         3: astore_1      
         4: aload_1       
         5: invokevirtual #3                  // Method java/lang/Class.getDeclaredConstructors:()[Ljava/lang/reflect/Constructor;
         8: astore_2      
         9: aload_2       
        10: arraylength   
        11: istore_3      
        12: iconst_0      
        13: istore        4
        15: iload         4
        17: iload_3       
        18: if_icmpge     41
        21: aload_2       
        22: iload         4
        24: aaload        
        25: astore        5
        27: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        30: aload         5
        32: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        35: iinc          4, 1
        38: goto          15
        41: new           #2                  // class a/TestNested$A
        44: dup           
        45: new           #6                  // class a/TestNested
        48: dup           
        49: invokespecial #7                  // Method "<init>":()V
        52: dup           
        53: invokevirtual #8                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
        56: pop           
        57: bipush        123
        59: aconst_null   
        60: invokespecial #9                  // Method a/TestNested$A."<init>":(La/TestNested;ILa/TestNested$1;)V
        63: astore_2      
        64: return        
  }

Как вы можете видеть, конструктор a.TestNested$A(a.TestNested,int,a.TestNested$1) вызывается из вашего метода main. Кроме того, null передается как значение параметра a.TestNested$1.

Итак, посмотрим на таинственный анонимный класс a.TestNested$1:

Compiled from "TestNested.java"
class a.TestNested$1 {
}

Странно - я бы ожидал, что этот класс действительно что-то сделает. Чтобы понять это, взгляните на конструкторы в a.TestNested$A:   class a.TestNested $A {     final a.TestNested this $0;

  a.TestNested$A(a.TestNested);
    Code:
       0: aload_0       
       1: aload_1       
       2: putfield      #2                  // Field this$0:La/TestNested;
       5: aload_0       
       6: invokespecial #3                  // Method java/lang/Object."<init>":()V
       9: return        

  private a.TestNested$A(a.TestNested, int);
    Code:
       0: aload_0       
       1: aload_1       
       2: putfield      #2                  // Field this$0:La/TestNested;
       5: aload_0       
       6: invokespecial #3                  // Method java/lang/Object."<init>":()V
       9: return        

  a.TestNested$A(a.TestNested, int, a.TestNested$1);
    Code:
       0: aload_0       
       1: aload_1       
       2: iload_2       
       3: invokespecial #1                  // Method "<init>":(La/TestNested;I)V
       6: return        
}

Посмотрев на конструктор, видимый в пакете a.TestNested$A(a.TestNested, int, a.TestNested$1), мы увидим, что третий аргумент игнорируется.

Теперь мы можем объяснить конструктор и анонимный внутренний класс. Дополнительный конструктор необходим, чтобы обойти ограничение видимости для частного конструктора. Этот дополнительный конструктор просто делегирует частный конструктор. Тем не менее, он не может иметь такую ​​же подпись, что и частный конструктор. Из-за этого добавляется анонимный внутренний класс, чтобы обеспечить уникальную подпись, не сталкиваясь с другими возможными перегруженными конструкторами, такими как конструктор с сигнатурой (int,int) или (int,Object). Поскольку этот анонимный внутренний класс необходим только для создания уникальной подписи, его не нужно создавать и не нужно иметь контент.

Ответ 2

Третий конструктор - это синтетический конструктор, сгенерированный компилятором, чтобы разрешить доступ к частному конструктору из внешнего класса. Это связано с тем, что внутренние классы (и доступ к их закрытым классам для своих частных членов) существуют только для языка Java, а не для JVM, поэтому компилятор должен устранить пробел за кулисами.

Отражение скажет вам, является ли элемент синтетическим:

for (Constructor c : aClass.getDeclaredConstructors()) {
    System.out.println(c + " " + c.isSynthetic());
}

Отпечатки:

a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$1) true

Смотрите это сообщение для дальнейшего обсуждения: Предупреждение Eclipse об синтетическом accessor для частных статических вложенных классов в Java?

EDIT: интересно, компилятор eclipse делает это иначе, чем javac. При использовании eclipse он добавляет аргумент типа самого внутреннего класса:

a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$A) true

Я попытался отключить его, обнажив этот конструктор раньше времени:

class A {    
    A() {
    }   

    private A(int a) {
    }

    A(int a, A another) { }
}

Это касалось этого, просто добавив еще один аргумент в синтетический конструктор:

a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$A) false
a.TestNested$A(a.TestNested,int,a.TestNested$A,a.TestNested$A) true