Унаследованные классы (Legacy Classes) в Java - Вопросы

Всего: 7 вопросов

1. 

Что такое унаследованные классы (legacy classes) в Java? Какие классы к ним относятся и помечены ли они как @Deprecated?

Унаследованные классы (legacy classes) — это классы и интерфейс пакета java.util из Java 1.0, появившиеся до Collection Framework (Java 1.2): Vector, Stack, Dictionary, Hashtable, Properties и интерфейс Enumeration. Общей основы у них не было: у каждого свои имена методов (addElement(), elementAt(), keys()), а перебор шёл через Enumeration.

Их оставили ради обратной совместимости. В Java 1.2 их встроили в новую иерархию (Vector реализует List, Hashtable — Map), в Java 5 большинство сделали обобщёнными (кроме Properties, который наследует Hashtable<Object, Object>).

Ни один из них не помечен @Deprecated: компилятор не выдаёт предупреждений, удалять их из JDK не планируют. Об устаревании говорит только документация: Javadoc Stack рекомендует Deque, Javadoc Dictionary называет класс obsolete. В новом коде вместо них используют ArrayList, ArrayDeque, HashMap, ConcurrentHashMap и Iterator; Properties по-прежнему применяют для настроек.

2. 

Чем legacy-класс отличается от утилитарного (служебного) класса? Является ли Collections legacy-классом?

Это характеристики разного рода. Слово «legacy» говорит о возрасте класса: он появился в Java 1.0, до Collection Framework, и сохранён ради совместимости (Vector, Hashtable, Enumeration). Слово «утилитарный» говорит об устройстве: класс содержит только статические методы, и его объект не создают (Collections, Arrays, Objects, Math).

Collections — утилитарный класс, но не legacy: он появился в Java 1.2 вместе с новым каркасом коллекций и устаревшим не считается. Путать их не стоит: legacy-классы вроде Vector — это обычные классы, объекты которых создают через new.

3. 

Чем Vector отличается от ArrayList? Делает ли синхронизация Vector его потокобезопасным?

Vector — динамический массив из Java 1.0, с Java 1.2 он наследует AbstractList и реализует List. Отличия от ArrayList:

  • все методы синхронизированы, поэтому в однопоточном коде он платит за ненужные блокировки;
  • сохранились старые методы: addElement(), elementAt(), setElementAt(obj, index) и insertElementAt(obj, index) (сначала элемент, потом индекс — обратный порядок по сравнению с set() и add(index, e)), elements(), возвращающий Enumeration;
  • ёмкость по умолчанию 10, при заполнении массив удваивается; конструктор Vector(initialCapacity, capacityIncrement) задаёт фиксированный шаг роста, а capacity() показывает длину внутреннего массива. ArrayList растёт в 1,5 раза, такого параметра и метода у него нет.

Синхронизированный — не значит потокобезопасный: синхронизирован каждый метод по отдельности, но не их сочетание. Код if (!v.contains(x)) v.add(x); из двух потоков может добавить x дважды, а перебор нужно вручную заключать в synchronized (v) { ... }. Для многопоточного кода используют классы из java.util.concurrent, например CopyOnWriteArrayList.

4. 

Что такое Dictionary и Hashtable, как они связаны с интерфейсом Map и чем Hashtable отличается от HashMap?

Dictionary<K, V> — абстрактный класс из Java 1.0 для пар «ключ–значение» с одними абстрактными методами (put(), get(), remove(), size(), isEmpty(), keys(), elements()). Это класс, а не интерфейс, поэтому наследник не может расширять что-то ещё — одна из причин появления интерфейса Map в Java 1.2. Javadoc называет Dictionary obsolete, но аннотации @Deprecated нет.

Hashtable<K, V> расширяет Dictionary и с Java 1.2 реализует Map. От HashMap он отличается тем, что все методы синхронизированы, а ни ключ, ни значение не могут быть null — put() бросает NullPointerException (HashMap допускает один null-ключ и любые null-значения).

Ловушка: старый метод contains(value) ищет среди значений, то есть равен containsValue(), а не containsKey(). Перебор по-старому идёт через keys() и elements(), которые возвращают не fail-fast Enumeration.

5. 

Почему после props.put("port", 8080) вызов props.getProperty("port") возвращает null? Как правильно работать с Properties?

Properties объявлен как public class Properties extends Hashtable<Object, Object> и в Java 5 не стал обобщённым. Унаследованные put() и putAll() принимают любой объект, а методы самого Properties рассчитаны только на строки: getProperty() возвращает null для значения, которое не является String (здесь это Integer), а store() на таком значении бросит ClassCastException. Поэтому записывайте настройки через setProperty(String, String), а читайте через getProperty().

  • getProperty(key, defaultValue) возвращает значение по умолчанию вместо null, но не сохраняет его.
  • Конструктор new Properties(defaults) подключает набор defaults: недостающие ключи ищутся там, но не копируются в объект, и size() их не считает.
  • load(InputStream) читает файл в ISO-8859-1, и кириллица без экранирования испортится; читайте через Reader в UTF-8: Files.newBufferedReader(Path.of("app.properties")).

Это единственный legacy-класс, которым пользуются в новом коде; с Java 9 данные внутри хранятся в ConcurrentHashMap.

6. 

Чем интерфейс Enumeration отличается от Iterator и как перейти от одного к другому?

Enumeration<E> — интерфейс перебора из Java 1.0 с методами hasMoreElements() и nextElement() (при отсутствии элементов — NoSuchElementException). Его заменил Iterator (Java 1.2) с методами hasNext() и next(). Отличия:

  • Enumeration не умеет удалять элементы, у Iterator есть remove();
  • перечисления Vector.elements() и Hashtable.keys()/elements() не fail-fast: если удалить элемент во время перебора, следующий элемент может быть тихо пропущен, тогда как итератор бросит ConcurrentModificationException.

Enumeration до сих пор возвращают legacy-классы, StringTokenizer, ClassLoader.getResources(), NetworkInterface.getNetworkInterfaces(). Переход между API: en.asIterator() (Java 9) превращает его в Iterator (его remove() бросает UnsupportedOperationException), Collections.list(en) копирует в ArrayList, а Collections.enumeration(collection) делает обратное преобразование для старого API.

7. 

Чем заменить каждый legacy-класс в новом коде и какие отличия в поведении нужно проверить при переходе?

  • Vector → ArrayList (в многопоточном коде — CopyOnWriteArrayList или Collections.synchronizedList()). Проверить вызовы addElement(), elementAt(), setElementAt(): порядок аргументов в set() обратный.
  • Stack → ArrayDeque с типом переменной Deque (для потоков — ConcurrentLinkedDeque, LinkedBlockingDeque). peek() на пустом стеке вернёт null вместо EmptyStackException, pop() бросит NoSuchElementException, null-элементы запрещены, аналога search() нет.
  • Hashtable → HashMap или ConcurrentHashMap. HashMap принимает null: код, который ловил NullPointerException, начнёт молча сохранять null; contains() → containsValue().
  • Dictionary → интерфейс Map (ConcurrentMap): свои наследники переписывают на implements Map или extends AbstractMap.
  • Enumeration → Iterator и for-each; чужие API оборачивают через asIterator().
  • Properties остаётся: прямой замены для файлов настроек в JDK нет, но put()/get() меняют на setProperty()/getProperty().

Переменную объявляйте типом интерфейса — List<String> names = new ArrayList<>();, Deque<Integer> stack = new ArrayDeque<>(); — тогда реализацию можно сменить одной строкой.

Страница 1 из 1