Pull to refresh

Плохая Java или как не надо делать

Reading time 5 min
Views 66K
Во время работы мне, как, наверное, и каждому из Вас, иногда приходится замечать мелкие недочеты Java. Маленькие и редкие, но присущие. К написанию этой статьи меня подвиг один из комментариев к моему первому посту. Тема показалась мне очень интересной и я решил припомнить все то, что мне не нравится в моем любимом языке программирования. Итак, начнем:

HashSet

Не знаю почему было принято такое решение, но HashSet реализован на HashMap, да — сэкономили время на создание, но это же одна из основных коллекций, почему к ее созданию не подошли более ответственно — не понятно. Всё-таки, можно было создать HashSet более оптимально. HashMap несет излишнюю архитектуру в контексте задач HashSet. Например, внутри HashSet есть следующий код:
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

Это значит, что любое ваше значение внутри HashSet будет ассоциироваться со ссылкой на этот обьект. Это тоже самое, что:
Map.put(key, PRESENT);

Казалось бы, подумаешь — 8 байт, который будут использоваться всеми. Но не забывайте что при каждой вставке в HashSet, создается Map.Entry, в котором 4 ссылки (еще 16 лишних байт на каждый элемент). Расточительно, не находите? Почему так? Большая загадка… Спасибо хоть не унаследовались.

Default logger

Кто в проекте не использует log4j? А можете сходу назвать библиотеки, которые тоже обходятся без него? Думаю это трудные вопросы. Понимаю, java не может подстраиваться под каждую конкретную задачу, но добавили же стандартный Logger, так почему за 10 лет существования log4j, java так и не взяла лучшее из него? Представьте на сколько бы уменьшились все приложения, особенно сложные, где в конечной сборке может оказаться несколько разных версий логера.

Clonable

Все знают что интерфейс Clonable пустой, без единого метода и переменной, просто маркер для JVM. Вопрос — почему? Ведь, если наследоваться от него, то мы пытаемся определить некое поведение и следуя парадигмам ООП он должен содержать метод clone(), который и определял бы это самое поведение. Безусловно, иногда очень удобно без переопределения clone получить копию обьекта. Тем не менее, переопределять этот метод, по сути, приходится во всех классах у которых есть хотя бы одна коллекция обьектов модели, чтобы получить полную копию, а не просто ссылки на те же обьекты внутри коллекции. Я не знаю почему разработчики не определили в этом интерфейсе ни одного метода. Надеюсь у них была на это причина, хотя, наверное, просто забыли. Как следствие — метод clone() в Object.

Serializable

Ситуация аналогична Clonable, но тут по крайней мере можно возразить — в большинстве случаев
мы не определяем поведение вручную, а полагаемся на стандартную реализацию и было бы очень не удобно постоянно переопределять какие-то методы сериализации + постоянно следить за добавлением новых полей, добавлять их в методы. Ну и специально для этих целей есть Externalizable. Тем не менее, мы знаем, что можно изменить стандартное поведение сериализации предопределив writeObject() и readObject(). Как-то не по ООПшному.

Properties

Все мы так или иначе работаем с классом Properties. Открываем исходники и что мы видим?
class Properties extends Hashtable<Object,Object>

Зачем? Любой знает, что композиция лучше наследования, особенно, если это наследование не имеет логического смысла и не упрощает модель приложения. А методы load(InputStream inStream) и load0(LineReader lr)? А как же перегрузка методов? То же самое и со store0(), хорошо хоть внутри класса спрятаны. Кстати, такие методы встречаются повсеместно в core java, похоже на чей-то стиль.

Классы оболочки примитивных типов

Классы-оболочки — избыточны, гораздо удобней было бы оперировать примитивами и иметь возможность присваивать им null значение. Если простой int занимает 4 байта, то обьект Integer уже целых 16. Постоянное приведение, сравнение по equals. Зачем все это? Дальше:
Boolean.getBoolean();
Long.getLong();
Integer.getInteger()

Загадочные методы. До сих пор не могу понять какую роль они должны выполнять и почему методы которые
проверяют System.getProperty() находятся в классах оболочка, а не в классе Properties.

switch

Конечно же, каждый java разработчик знает, что оператор switch работает только с целым типом, а начиная в 7-й java и со строками. Но к чему эти ограничения? Судя по реализации switch в 7-й версии — он работает со строкой исходя из hashСode() и equals(). Тогда вопрос — почему только строки? Легко можно было реализовать оператор для любого обьекта, но почему-то этого не было сделано. Очередная загадка.

Collections

В List и Set есть прекрасный метод retainAll() — пересечение множеств, но почему-то нету метода для разницы множеств. Конечно, его легко можно реализовать самому:
List first = someList();
List second = someList();
diff = first.clone();
diff.removeAll(second);

Но хотелось бы, чтобы такая базовая операция с одним из основных типов данных была из коробки.

Дальше — одна из наиболее частых задач при работе с коллекциями — это найти нужный элемент в коллекции. Почему List имееет indexOf(Object o), а HashSet не имеет get(Object o), который бы по хеш коду и equals возвращал бы ссылку на обьект из коллекции. Да, когда хеш код не переопределен в этом нету смысла, но когда это не так, то тут уже появляются возможности для использования. Может я, конечно, слишком придираюсь, но довольно часто такая задача возникает.

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

Generics

Map<MyKeyObject, PersistedPlan> plansById = new HashSet<MyKeyObject, PersistedPlan>();

Как уже замечали, такого рода конструкции встречается повсеместно в коде — в описаниях классов, в конструкторах, в методах, везде… Они очень перегружают код и усложняют его восприятие. К счастью, в 7-й java это исправили. Не понятно только — зачем так долго ждали?

Многопоточность

Впервые, когда я прочитал про многопоточность в Erlang, я осознал — насколько же механизмы многопоточности java сложны. Все эти locks, monitors, synchronizations, deadlocks, theads, runnables, memory barriers. Зачем? Зачем так усложнять нашу жизнь, ведь java больше 20 лет, неужели трудно рассмотреть тенденции современности? Ведь основная цель java была в облегчении жизни нам — простым разработчикам.
P. S. Я тут пообщался с коллегами, есть информация, что подвижки в эту сторону идут не маленькие, так что будем надеятся.

Замыкания

Частенько возникает необходимость предать функцию в качестве параметра, правда? Сколько можно еще ждать? Мне как java разработчику очень не приятно, что одно из наиболее ожидаемых улучшений так и не сделали. Будем надеятся что хотя бы в 8-й яве появятся, больше тут добавить нечего.

Runtime and System

Не совсем понятно логическое разделение этих классов, мне кажется, вполне хватило бы и одного. С System связано еще несколько странностей — например out и in поля класса — final. Но в то же время есть методы setOut и setIn. Плохой пример, не правда ли?

String

Очень не эффективен, мало того что 2 байта занимает каждый символ, так еще и каждая строка — это обьект класса String, а это в свою очередь 8 байт на заголовок + 3 целых значения внутри + 1 ссылка на массив == лишних 24 байта на каждую строку! Всё-таки дороговато. Можно было бы хотя бы создать 2 варианта строк: дешевый и дорогой. Был бы выбор хотя бы.

Numbers

Long l = 2; //compilation error
Map<int, Object> a = new HashMap<int, Object>(); //compilation error
Map<Object, int> a = new HashMap<Object, int>(); //compilation error
Set<int> b = new HashSet<int>(); //compilation error

Зачем усложнили такие простые вещи? До сих пор не могу осознать. Мелочь, конечно, но не приятно. Дальше — все знают, что для работы с числами с плавающей точкой в яве стандартные примитивы не подходят, так как теряют точность. Хорошо, используем другой тип — BigDecimal. И вот во что превратится простая формула:
//(metric.getValue() * 100 / prevMetric.getValue()) + metric.getOffset()
BigDecimal result =BigDecimal.valueOf(metric.getValue() * 100).divide(BigDecimal.valueOf(prevMetric.getValue()).add(metric.getOffset()));

Хорошо если вычисления простые, со сложными все гораздо печальней. Почему не ввести нормально работающий с числами тип данных наравне с double и float?

Выводы

Не подумайте, что это некое осеннее обострение. Ни в коем случае. Я очень люблю java и хочу чтобы она развивалась наравне с тенденциями современности. И еще я хочу чтобы на ней можно было программировать в удовольствие, без всех этих мелочей, которые лишь досаждают и отвлекают от занимательной и интересной работы.
Tags:
Hubs:
+17
Comments 87
Comments Comments 87

Articles