LINUX.ORG.RU

Java вопрос по synthetic's и Class.getDeclaredConstructors()

 


0

2

Здравствуйте!

В Java у класса Class есть метод getDeclaredConstructors(). В JavaDoc'е к этому методу есть комментарий:

This method returns an array of length 0 if this Class object represents an interface, a primitive type, an array class, or void.

Звучит вполне логично. Проверяем:

System.out.println("Interface: " + Runnable.class.getDeclaredConstructors().length);
System.out.println("Primitive: " + int.class.getDeclaredConstructors().length);
System.out.println("Array:     " + int[].class.getDeclaredConstructors().length);
System.out.println("void:      " + void.class.getDeclaredConstructors().length);

Выдает:

Interface: 0
Array:     0
Primitive: 0
void:      0

Все ок.

На Хабре недавно был список задач по Джаве и один из вопросов как раз был «Может ли класс в Java не иметь конструкторов?».

В качестве решения было предложено:

public class Main {

  static class Nested {
    private Nested() {}
  }

  public static void main(String[] args) throws ClassNotFoundException {

    new Nested();
    System.out.println("  Main$1:    " + Class.forName("Main$1").getDeclaredConstructors().length);
  }
}

Выдает 0.

Почему так?

$1 как бы намекает, что это анонимный класс.
Но, во-первых, про анонимные классы в JavaDoc'е ни слова.
Во-вторых, проверим:

System.out.println("  Serializable: " + new Serializable(){}.getClass().getDeclaredConstructors().length + 
                   ", isAnonymousClass(): " + new Serializable(){}.getClass().isAnonymousClass());

// печатает 'Serializable: 1, isAnonymousClass(): true'

Т.е. наш анонимный класс имеет 1 конструктор.

Проверим для решения из Хабра, является ли тот класс анонимным:

System.out.println("  Main$1:    " + Class.forName("Main$1").getDeclaredConstructors().length + 
              ", isAnonymousClass(): " + Class.forName("Main$1").isAnonymousClass());

// печатает 'Main$1:    0, isAnonymousClass(): true'
Т.е. класс анонимный, но конструктора нет.

Копаем дальше:

    System.out.println("  Main$1:    "          + Class.forName("Main$1").getDeclaredConstructors().length +
                       ", isAnonymousClass(): " + Class.forName("Main$1").isAnonymousClass() +
                       ", isInterface(): "      + Class.forName("Main$1").isInterface() +
                       ", isPrimitive(): "      + Class.forName("Main$1").isPrimitive() +
                       ", isArray(): "          + Class.forName("Main$1").isArray() +
                       ", isEnum(): "           + Class.forName("Main$1").isEnum() +
                       ", isLocalClass(): "     + Class.forName("Main$1").isLocalClass() +
                       ", isAnnotation(): "     + Class.forName("Main$1").isAnnotation() +
                       ", isSynthetic(): "      + Class.forName("Main$1").isSynthetic() +
                       ", isMemberClass(): "    + Class.forName("Main$1").isMemberClass());

Выдает:

Main$1:    0, 
isAnonymousClass(): true, 
isInterface():      false, 
isPrimitive():      false,
isArray():          false,
isEnum():           false,
isLocalClass():     false,
isAnnotation():     false,
isSynthetic():      true,
isMemberClass():    false

Ага, видим, что класс синтетический:

indicates that this class or interface was generated by a compiler and does not appear in source code.

Т.е. синтетические - это классы/методы/конструкторы, которые компилятор создает сам, которых нет в исходниках.
Как известно, компилятор знает только о top-level классах. Он ничего не знает про nested, inner и тп.
Для их взаимодействия, например, он и создает синтетические классы, методы и конструкторы.

Все встает на свои места.

Но, если мы выполним:

public class Main {

  static class Nested {
    private Nested() {}
  }

  public static void main(String[] args) throws ClassNotFoundException {

    new Nested();
    System.out.println("  Main$1:       "       + Class.forName("Main$1").getDeclaredConstructors().length +
                       ", isAnonymousClass(): " + Class.forName("Main$1").isAnonymousClass() +
                       ", isSynthetic(): "      + Class.forName("Main$1").isSynthetic());

    System.out.println("  Main$Nested:  "  + Class.forName("Main$Nested").getDeclaredConstructors().length +
                       ", isAnonymousClass(): " + Class.forName("Main$Nested").isAnonymousClass() +
                       ", isSynthetic(): "      + Class.forName("Main$Nested").isSynthetic());

    System.out.println("  Serializable: " + new Serializable(){}.getClass().getDeclaredConstructors().length +
                       ", isAnonymousClass(): " + new Serializable(){}.getClass().isAnonymousClass() +
                       ", isSynthetic(): " + new Serializable(){}.getClass().isSynthetic());
  }
}

То получим:

  Main$1:       1, isAnonymousClass(): true,  isSynthetic(): false
  Main$Nested:  2, isAnonymousClass(): false, isSynthetic(): false
  Serializable: 1, isAnonymousClass(): true,  isSynthetic(): false

Если добавляю Main$2, Main$3, то они равны Main$1 (вангую, что это три анонимных Serializable).
Main$4 класса нет. Кидает эксепшн.

Вопрос 1: Куда делся синтетический класс без конструкторов? Nested класс же остался.
Вопрос 2: Почему Main$Nested имеет 2 конструктора? Один - default. А второй?
Вопрос 3: Можете еще привести примеры synthetic's? Нагуглил что-то про switch-statement, но воспроизвести не удалось.

Извините за простыню. Спасибо.

★★★★★

Последнее исправление: kovrik (всего исправлений: 2)

Вопрос 2: Можете еще привести примеры synthetic's? Нагуглил что-то про switch-statement, но воспроизвести не удалось.

switch по строке генерирует синтетический класс.

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

А по int'у, например?
У меня не получилось с int'ом воспроизвести, но, возможно, не так делал.
Можешь пример написать?

kovrik ★★★★★
() автор топика

Вопрос 1: Куда делся синтетический класс без конструкторов? Nested класс же остался.

результат компиляции зависит от версии джавы, в восьмерке например Main$1 вообще будет отсутствовать в решении из хабра.

Вопрос 2: Почему Main$Nested имеет 2 конструктора? Один - default. А второй?

для восьмерки

javap Main\$Nested.class 
Compiled from "Main.java"
class Main$Nested {
  Main$Nested(Main$Nested);
}
для семерки
javap Main\$Nested.class 
Compiled from "Main.java"
class Main$Nested {
  Main$Nested(Main$1);
}
Подозреваю, что синтетический класс используется для того чтобы можно было вызывать вроде бы приватный конструктор.

maloi ★★★★★
()
Ответ на: комментарий от maloi

Не понял ответ на второй вопрос. Можешь подробнее написать?
И не совсем понял, куда пропал синтетический класс в моем примере. Версия Джавы используется одна и та же.

Подозреваю, что синтетический класс используется для того чтобы можно было вызывать вроде бы приватный конструктор.

Да, это так:
https://javax0.wordpress.com/2014/02/26/syntethic-and-bridge-methods/

kovrik ★★★★★
() автор топика
Ответ на: комментарий от kovrik

Не понял ответ на второй вопрос. Можешь подробнее написать?

второй (синтетический) конструктор принимает в качестве параметра объект синтетического класса (в седьмой джаве). В восьмой - самого себя.

И не совсем понял, куда пропал синтетический класс в моем примере.

Если ты посмотришь байткод, то увидишь, что вместо него в качестве парметра синтетического конструктора используется Main$1 - т.е. просто первый попавшийся анонимный класс. В примере с хабра анонимных классов не было, поэтому пришлось нагенерить синтетический.

Версия Джавы используется одна и та же.

одна и та же - это какая?

maloi ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.