Унаследованные классы (Legacy Classes) в Java - Вопросы
Всего: 7 вопросов
1. Что такое унаследованные классы (legacy classes) в Java? Какие классы к ним относятся и помечены ли они как @Deprecated?
Что такое унаследованные классы (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-класс отличается от утилитарного (служебного) класса? Является ли 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 отличается от 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 и 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?
Почему после 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 отличается от 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-класс в новом коде и какие отличия в поведении нужно проверить при переходе?
Чем заменить каждый 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<>(); — тогда реализацию можно сменить одной строкой.