Динамическое построение анонимной путаницы классов - PullRequest
15 голосов
/ 17 августа 2011

Я пытаюсь создавать экземпляры анонимных классов, используя рефлексию. Но иногда я видел странное поведение во время мгновенного действия.

Пожалуйста, посмотрите на эти похожие фрагменты кода

public class HideAndSeek {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws IllegalAccessException, InstantiationException{

        final String finalString = "I'm final :)";

        Object object2 = new Object(){

            {
                System.out.println("Instance initializing block");
                System.out.println(finalString);
            }           

            private void hiddenMethod() {
                System.out.println("Use reflection to find me :)");
            }
        };

        Object tmp = object2.getClass().newInstance();
    }

}

Этот код работает хорошо, и ожидаемый вывод

Instance initializing block
I'm final :)
Instance initializing block
I'm final :)

После этого я решил изменить код простым способом (только что добавил java.util.Calendar)

import java.util.Calendar;

    public class HideAndSeek {

        @SuppressWarnings("unchecked")
        public static void main(String[] args) throws IllegalAccessException, InstantiationException{

            final String finalString = "I'm final :)";

            final Calendar calendar = Calendar.getInstance();
            System.out.println(calendar.getTime().toString()); //works well

            Object object2 = new Object(){

                {
                    System.out.println("Instance initializing block");
                    System.out.println(finalString);

                    //simply added this line
                    System.out.println(calendar.getTime().toString());
                }           

                private void hiddenMethod() {
                    System.out.println("Use reflection to find me :)");
                }
            };

            Object tmp = object2.getClass().newInstance();
        }

    }

А вот что у меня получилось:

Wed Aug 17 02:08:47 EEST 2011
Instance initializing block
I'm final :)
Wed Aug 17 02:08:47 EEST 2011
Exception in thread "main" java.lang.InstantiationException: HideAndSeek$1
    at java.lang.Class.newInstance0(Unknown Source)
    at java.lang.Class.newInstance(Unknown Source)
    at HideAndSeek.main(HideAndSeek.java:29)

Как видите, новый экземпляр не был создан.

Может ли кто-нибудь объяснить мне причину таких изменений?

Спасибо

Ответы [ 2 ]

21 голосов
/ 17 августа 2011

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

Глядя на исходный код, где возникает исключение в Class (я не уверен, почему ваша трассировка стека не дает номера строк в Class):

try
{
  Class[] empty = {};
  final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
  // removed some code that was not relevant
}
catch (NoSuchMethodException e)
{
  throw new InstantiationException(getName());
}

вы видите, что NoSuchMethodException перебрасывается как InstantiationException.Это означает, что для типа класса object2 нет конструктора без аргументов.

Во-первых, какой тип object2?С кодом

System.out.println("object2 class: " + object2.getClass());

мы видим, что

object2 class: class junk.NewMain $ 1

, что правильно (я запускаю пример кода впакет мусора, класс NewMain).

Что же тогда являются конструкторами junk.NewMain$1?

Class obj2Class = object2.getClass();
try
{
  Constructor[] ctors = obj2Class.getDeclaredConstructors();
  for (Constructor cc : ctors)
  {
    System.out.println("my ctor is " + cc.toString());
  }
}
catch (Exception ex)
{
  ex.printStackTrace();
}

, что дает нам

мой ctor - мусор. NewMain$ 1 (java.util.Calendar)

Таким образом, ваш анонимный класс ищет для передачи Calendar. Это будет работать для вас:

Object newObj = ctors[0].newInstance(Calendar.getInstance());

Еслиу вас есть что-то вроде этого:

final String finalString = "I'm final :)";
final Integer finalInteger = new Integer(30);
final Calendar calendar = Calendar.getInstance();
Object object2 = new Object()
{
  {
    System.out.println("Instance initializing block");
    System.out.println(finalString);
    System.out.println("My integer is " + finalInteger);
    System.out.println(calendar.getTime().toString());
  }
  private void hiddenMethod()
  {
    System.out.println("Use reflection to find me :)");
  }
};

тогда мой вызов newInstance не будет работать, потому что в ctor недостаточно аргументов, потому что теперь он хочет:

мой ctor - мусор. NewMain $ 1 (java.lang.Integer, java.util.Calendar)

Если я тогда создаю это с

Object newObj = ctors[0].newInstance(new Integer(25), Calendar.getInstance());

, заглянем внутрь с помощью отладчикапоказывает, что finalInteger равно 25, а не конечное значение 30.

Все немного сложнее, потому что вы делаете все вышеперечисленное в статическом контексте.Если вы возьмете весь приведенный выше код и переместите его в нестатический метод, например, так (помните, мой класс - нежелательный. NewMain):

public static void main(String[] args)
{
  NewMain nm = new NewMain();
  nm.doIt();
}

public void doIt()
{
  final String finalString = "I'm final :)";
  // etc etc
}

вы обнаружите, что ctor для вашего внутреннего класса теперь(удаляя мою добавленную целочисленную ссылку):

мой ctor является ненужным. NewMain $ 1 (junk.NewMain, java.util.Calendar)

язык JavaСпецификация , раздел 15.9.3 объясняет это следующим образом:

Если C является анонимным классом, а прямой суперкласс C, S является внутренним классом,затем:

  • Если S является локальным классом и S встречается в статическом контексте, то аргументы в списке аргументов, если они есть, являются аргументами конструктора в том порядке, в котором они отображаются ввыражение.
  • В противном случае немедленно включающий экземпляр i по отношению к S является первым аргументом конструктора, за которым следуют аргументы в списке аргументов выражения создания экземпляра класса, если оно есть, в порядкеони появляются в выражении.

Почему анонимный конструктор вообще принимает аргументы?

Поскольку вы не можете создать конструктор для анонимного внутреннего класса, блок инициализатора экземпляра служит этой цели (помните,у вас есть только один экземпляр этого анонимного внутреннего класса).Виртуальная машина не знает внутреннего класса, так как компилятор выделяет все как отдельные классы (например, junk.NewMain $ 1).Ctor для этого класса содержит содержимое инициализатора экземпляра.

Это выполняется JLS 15.9.5.1 Анонимные конструкторы :

... анонимныйКонструктор имеет один формальный параметр для каждого фактического аргумента в выражении создания экземпляра класса, в котором объявлен C.

Ваш инициализатор экземпляра имеет ссылку на Calendar объект.Как еще компилятор собирается получить это значение времени выполнения в ваш внутренний класс (который создается как просто класс для ВМ), кроме как через конструктор?

Наконец (ура), ответ на последний актуальный вопрос,Почему конструктор не требует String?Последний бит JLS 3.10.5 объясняет, что:

Строки, вычисленные с помощью константных выражений, вычисляются во время компиляции, а затем обрабатываются так, как если бы они были литералами.

Другими словами, ваше значение String известно во время компиляции, потому что оно является литералом, поэтому оно не обязательно должно быть частью анонимного конструктора.Чтобы доказать это, мы протестируем следующий оператор в JLS 3.10.5:

Строки, вычисленные путем конкатенации во время выполнения, создаются заново и поэтому различаются.

Измените ваш код следующим образом:

String str1 = "I'm";
String str2 = " final!";
final String finalString = str1 + str2

и вы обнаружите, что ваш ctor сейчас (в нестатическом контексте):

мой ctor - мусорный. NewMain $ 1 (junk.NewMain, java.lang.String, java.util.Calendar)

Фу.Я надеюсь, что это имеет смысл и было полезно.Я многому научился, это точно!

4 голосов
/ 17 августа 2011

Потому что во втором случае больше нет конструктора по умолчанию.

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

Во втором случае анонимный внутренний класс должен принять экземпляр Calendar в свой конструктор для захвата состояния. Вы можете легко подтвердить, что делаете это:

Object tmp = object2.getClass().getDeclaredConstructor(Calendar.class).newInstance(calendar);
...