LINUX.ORG.RU

[java]Построение иммутабельных объектов

 


0

1

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

Но вот в Java нету средств инициализации таких объектов. Т.е. допустим, есть

class Nut {
  final Protein protein;
  final Fat fat;
  final Carbohydrate carbohydrate;
}
Как такое инициализировать? Казалось бы просто, есть два способа:
//через конструктор
class Nut {
  final Protein protein;
  final Fat fat;
  final Carbohydrate carbohydrate;
  Nut(Protein protein, Fat fat, Carbohydrate carbohydrate) {
    this.protein = protein;
    this.fat = fat;
    this.carbohydrate = carbohydrate;
  }
}

//паттерном строитель

class Nut {
  public class Builder {
    Protein protein;
    Fat fat;
    Carbohydrate carbohydrate;

    Builder protein(Protein protein) {
        this.protein = protein;
        return this;
    }

    Builder fat(Fat fat) {
        this.fat = fat;
        return this;
    }

    Builder carbohydrate(Carbohydrate carbohydrate){
        this.carbohydrate = carbohydrate;
        return this;
    }

    Nut build() {
        return new Nut(protein, fat, carbohydrate);
    }
  }

  final Protein protein;
  final Fat fat;
  final Carbohydrate carbohydrate;

  Nut(Protein protein, Fat fat, Carbohydrate carbohydrate) {
    this.protein = protein;
    this.fat = fat;
    this.carbohydrate = carbohydrate;
  }
}
И что мы видим? Видим что первый способ неудобен и подвержен ошибкам (если аргументы одного типа то легко их перепутать) и не допускает автоматической инициализации, например из Hibernate, хотя кое какие вещи позволяют инициализацию таких объектов, но при отсутсвии в жабке именованных параметров это все равно криво.

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

Итак, чтоже остается правильному программисту, подскажите? Можно например делать не final поля, помечать их аннотациями и инициализировать через reflection, однако, при минимуме дублирующегося кода, и всех неоспоримых преимуществах, это требует отсутствия final, что очень плохо.


Ответ на: комментарий от dizza

Генератор костылей? А проблему автоматической инициализации это не решит, придется писать еще билдер для автоматической инициализации объектов — целый магазин костылей на один класс.

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

Издеваешься? В String одно свойство кое содержит массив char, там нечего даже инициализировать автоматически.

kifer
() автор топика

Белка, ну когда же тебя отпустит? Неужели всем жаба-программистам приходится заниматься таким психоделом?

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

Не всем, их есть два вида — одни молчат но кодят, другие не молчат и не кодят.

kifer
() автор топика

Сеттерами инициализируй, строчек много, но это не является реальной проблемой, в джава во многих местах много избыточного мусора, ничего не поделаешь. Это некрасиво, но это не проблема.

Или пиши на C#, там это продумано лучше.

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

Почему костылей? Нормальное решение. Можешь взять готовую генерилку, например Google Protocol Buffers.

Кстати, есть еще один вариант, про него ты забыл. Объект делается с гетерами и сетерами и есть внутренний флажок + метод заморозки. Так вот вначале заполняешь объект, потом вызываешь «заморозить» и вуаля - на любой вызов сеттера будет эксепшн.

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

эта же хрень делается через reflection, одна, нестыковочка - объект не будет меть финальных полей

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

Я имел в виду что то вроде

public void setName(String name) {
    if (name == null) {
        throw new IllegalArgumentException("name");
    }
    if (this.name != null) {
        throw new IllegalStateException("name is already initialized");
    }
    this.name = name;
}

А final поля или нет - на результате скорее всего никак не отражается, это просто указка компилятору, чтобы ругался на присваивания. Иммутабельность это свойство интерфейса, а не внутренней реализации.

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

Впрочем ты видимо подобный вариант уже рассматривал, но вариант с рефлекшном имхо лучше выкинуть, если это то, о чём я подумал.

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

тоже кода многовато но впринципе можно сократить до одной строки check(Object o); кой будет кидать экспешн, но тут еще косяк в том что это порождает противоречие - сеттер есть а пользоваться им нельзя.

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

Nut nut = build(Nut.class, «protein», myProtein, «fat», fat, «carbohydrate», myCarbohidrate);

<T> T build(Class<T> clazz, Object ... initList) { ... }

про что то подобное. В любом случае рефлекшн это имена свойств в кавычках, тормоза, проблемы при переименовании свойств, ошибки в рантайме, которые должны вылавливаться при компиляции, и прочие радости динамической типизации. Впрочем некоторым даже нравится.

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

Несовершенный язык заставляет самому совершенствоваться.

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

> Да, точно

познавательно

рефлексия в «надежной» жабе стала тем же или еще хуже, чем в с++ арифметика с указателями

Once we have made the object available to another thread, we should not change final fields using reflection. The result would not be predictable.

я уже предлагал в флейме про скалу передавать параметры через анонимный класс ; но без генерилки Mutable.Nut видимо не обойтись

Nut n = new Nut( new Mutable.Nut() {{ fat=20; protein=2*fat; carbohydrate=93-fat-protein; }} );

в самом же классе Nut можно не устраивать бойлерплейт, а через рефлексию скопировать к себе поля из Mutable.Nut

сеттерами как приведено в примере выше не сделать

www_linux_org_ru ★★★★★
()

petrosjan mode on

Если в джаве есть уборщик мусора, то почему он не удаляет программы во время их исполнения?

anonymous
()
Ответ на: комментарий от www_linux_org_ru

В жабке покуда такой иснтакси не разрешен, тоже так не сделать а в конструктор можно из через Map паарметры передавать, тогда и final будет и лишний код ненужен, но оверхед из-за хешмапа будет

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

Вот тут кстати я вспомнил побочный эффект - поведение класса если значение свойства должно быть null.

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

Синтаксис вполне разрешён. Правда внутри этого «инициализатора» можно будет использовать только final переменные.

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

В жабке покуда такой иснтакси не разрешен, тоже так не сделать

с разморозкой!

у нас сейчас 2011 год, случилось много интересного, в частности, такой синтаксис разрешили кажись в 2000 году

class Mutable {
    static class Nut {
        int protein;
        int fat;
        int carbohydrate;
    };
};

class Nut {
    final int protein;
    final int fat;
    final int carbohydrate;
    Nut( Mutable.Nut n ) { protein=n.protein; fat=n.fat; carbohydrate=n.carbohydrate; }
};

public class AnonClassTestW
{
    public static void main(String[] args)
    {
        Nut n = new Nut( new Mutable.Nut() {{ fat=12; protein=2*fat; }} );
        System.out.println( n.fat );
        System.out.println( n.protein );
    }
};

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

у нас сейчас 2011 год, случилось много интересного, в частности, такой синтаксис разрешили кажись в 2000 году

Ага, чтобы создать один несчастный объект, надо создать два.

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

>у нас сейчас 2011 год, случилось много интересного, в частности, такой синтаксис разрешили кажись в 2000 году

Если хочешь до человека донести чтото то достаточно было сказать Double Brace Initialization, которые я в данном случае не заметил просто, обратив внимание лишь на подобие именованых параметров.

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

Builder-ы так и работают, этож ява, сделать что-то культурно тут невозможно

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

> Если хочешь до человека донести чтото то достаточно было сказать Double Brace Initialization

ты думаешь, я сам знал, как оно называется?

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

не, ну если что-то юзаешь, то обычно знаешь как оно зовется, тем более его побочные эффекты

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

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

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

А как же основная скрижаль явы: не устрели ногу свою? Насколько я помню они именно сим объясняли наличие только final полей в замыканиях анонимных классов.

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

че-то я не помню, как ее можно отстелить; напомнишь?

З.Ы. в любом случае можно чисто синтаксически это транслировать в

        final int[] f = new int[1]; f[0]=333;
        Nut n = new Nut( new Mutable.Nut() {{ fat=f[0]=11; protein=2*fat; }} );
www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

>че-то я не помню, как ее можно отстелить; напомнишь?

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

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

Это не очевидное преобразование, и поэтому оно не должно быть по умолчанию. Имхо, нужна некая аннотация перед объявлением подобной переменной, что-нибудь вроде @Closed, без которой компилятор выдавал бы ворнинги, что мол implicit closing of variable f. Чтобы человек, читая код, сразу понимал, что эта переменная на самом деле боксится, без просмотра всех её вхождений.

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

мой код с final int[] f вполне себе компилируется и исполняется; при чем здесь отстрел ноги — не ясно, а вот лень разрабов ясно видна

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

Всё же если я объявляю int f, то я подразумеваю именно простые 4 байта и ничего больше. И если у меня крутится цикл, в котором 5 таких int-ов, то мне хочется быть уверенным, что это будут простые int-ы, а не внезапно сгенерированная куча объектов, которые будут триггерить GC.

Кстати по похожей причине я считаю автобоксинг неудачной идеей :) Хотя вряд ли предложу что то лучшее.

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

Если тебе не ясно то это лишь означает что надо еще подумать.

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

Что значит отказаться? Джава никогда не сломает совместимость со старыми версиями, а если ломать, то нафиг вообще надо её выкинуть и какую-нибудь scala брать.

Автобоксинг это компромисс между нежеланием писать Integer.valueOf постоянно, и сохранением совместимости со старым кодом.

Какой-нибудь синтаксис, или что то вроде "(Integer) f" было бы лучше, наверное.

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

>Джава никогда не сломает совместимость со старыми версиями

Перефразируя - джава не может эволюционировать.

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