суббота, 21 апреля 2012 г.

Java object initializers

После перехода на Java очень не хватает различного синтаксического сахара, присущего C#, периодически даже можно впасть в тоску. В этот раз я хочу сказать кое-что о инициализаторах объектов.

Типичное создание объектов в Java:

 Person joe = new Person();  
 joe.setFirstName("Joe");  
 joe.setLastName("Doe");  
 joe.setAge(33);

В то время как в C# это выглядит так:

 var joe = new Person { FirstName = "Joe", LastName = "Doe", Age = 33 };  

Чуть проще, не правда ли?

Недавно в исходниках на работе я наткнулся на интересную конструкцию, которая привлекла моё внимание:

 Person joe = new Person() {{ setFirstName("Joe"); setLastName("Doe"); setAge(33); }};  

Хм, странно, конечно, двойные фигурные скобочки? Но уж очень мне это напоминает код на C#.

Я обрадовался — для меня это выглядит получше четырёхстрочной инициализации. Однако, немного разобравшись в смысле этой конструкции, моя радость быстро превратилась в... что-то вроде подозрения.

Что на самом деле происходит за кулисами?


В Java есть концепция анонимных классов, позволяющая создавать уникальные безымянные классы в месте вызова и тут же их использовать. Это используется почти повсеместно, в основном для выполнения функций, аналогичных делегатам в C# — например, в качестве обратного вызова или обработчика события.

Описание анонимных классов имеет следующий синтаксис:

 Runnable anonym = new Runnable() {  
  public void run() {  
   // do stuff  
  }  
 };  

Отметим так же, что Runnable является интерфейсом (описывающим паттерн Command), а не классом. То есть мы можем реализовать даже интерфейс где нам угодно и дальше манипулировать им как любой другой переменной.

Помимо этого каждый  класс в Java может иметь два блока инициализации: статический и экземплярный (в C# есть статические конструкторы), например:

 public class Test {  
   static { staticField = " 22"; }   // static initialization  
   { field = " asd"; }         // instance initialization  
   static String staticField;  
   String field;  
 }  

Я думаю, уже потихоньку становится понятно, что же на самом деле происходит в примере с инициализацией в начале поста.

Мы объявляем анонимный класс-наследник Person и в блоке инициализации экземпляра вызываем нужные нам сеттеры. То есть, мы реально создаем новый класс, а не инициализируем уже имеющийся.

К сожалению, не могу сказать, каждый ли раз при вызове одного и того же кода с такой инициализацией создается новый класс (не экземпляр, а класс), но в любом случае злоупотребление таким методом создания может привести к ошибке OutOfMemoryException: PermGen space, которая возникает, когда в JVM заканчивается область памяти в которой хранятся описания классов не входящих в язык (Permanent Generation, по умолчанию 64 мб).

Спасибо!
Хорошего дня!