Эта статья — для тех, кто успешно использует enum'ы и хочет сэкономить память. Для тех, кто не знает, что это, есть статья «Перечисления (enums)».
Несомненно, enum'ы круты и приносят очень много радости. Но вот с чем я столкнулся.
Мусорные объекты
В Java мы порой создаём объекты с очень коротким сроком жизни. Например, в конструкции for (T t : someIterable) создаётся итератор, который становится мусором, когда цикл заканчивается. Если someIterable instanceof List && someIterable instanceof RandomAccess, то итератор не нужен.
Есть множество ситуаций, в которых используют защитное копирование, например, метод List.toArray() вернёт новый массив. И это зачастую оправдано.
Обо всём этом мусоре, несомненно, заботится Generational GC в HotSpot и Concurrent GC в Android Runtime, но всё же стоит соблюдать осторожность. Например, переопределяя метод View.onDraw(), лучше обходить списки, не создавая итератор, как это делают for-each loop в Java и Kotlin, а по числовым индексам.
Мы же говорили про Enum'ы?
Если нужно сохранить значение enum'а для длительного хранения, например, в базу данных, можно сохранить его строчное представление (someEnumValue.name()). И, наоборот, получить значение enum'а по его строчному представлению (SomeEnum.valueOf(someEnumName)).
Если нужно сохранить значение enum'а для кратковременного и очень быстрого транспорта, например, реализовывая интерфейс Parcelable, лучше использовать порядковый номер (someEnumValue.ordinal()): в пределах одной запущенной виртуальной машины он не может измениться.
Чтобы выполнить обратное преобразование, можно написать SomeEnum.values()[someEnumOrdinal]. К сожалению, в Java нет неизменяемых массивов. Поэтому генерируемый компилятором метод values() каждый раз возвращает новый клон массива, взятого из синтетического, скрытого от посторонних глаз статического поля $VALUES. Конечно, никто не будет держать в массиве тысячи вариантов, так что клонирование массива происходит быстро, но сам по себе факт доступа за O(n) вызывает уныние. Да и у такого кода будут проблемы:
for (int i = 0; i < SomeEnum.values().length; i++) {
if (SomeEnum.values()[i].someValue == 0) {
System.out.println(SomeEnum.values()[i] + ".someValue равно нулю");
} else if (SomeEnum.values()[i].someValue == 1) {
System.out.println(SomeEnum.values()[i] + ".someValue равно единице");
} else {
System.out.println(SomeEnum.values()[i] + ".someValue = " + SomeEnum.values()[i].someValue);
}
}
Конечно, пример утрирован, но похожие конструкции с беспорядочным выделением новых массивов в цикле мне встречались.
Решение
При экстремальной нехватке памяти я решал проблему, скопировав и закешировав массив единожды:
enum SomeEnum {
SomeValue(0), AnotherValue(1), OneMoreValue(2);
private static final SomeEnum[] VALS = values();
public static SomeEnum valueOf(int ordinal) {
return VALS[ordinal];
}
…
}
Другой способ, который приходит на ум, заключается в непосредственном использовании значения синтетического поля $VALUES, но для этого нужно либо договариваться с компилятором, либо использовать reflections, что в данной ситуации неоправдано.
Общедоступный список значений
Ещё одно решение заключается в создании неизменяемого списка из массива:
enum SomeEnum {
SomeValue(0), AnotherValue(1), OneMoreValue(2);
public static final List VALUES =
Collections.unmodifiableList(Arrays.asList(values()));
…
}
Грязный хак
Задолго после публикации этой статьи нашёл ещё один способ — получиение общего массива значений с помощью sun.misc.SharedSecrets.
Вывод
Конечно, нужно экономить память. Нужно ли запариваться этим так, как я? Решайте сами.