Java Core
Базовые темы Java для Android-разработки: равенство объектов, коллекции, обобщения, доступ, приведения типов и время жизни singleton.
Объекты и равенство
Класс Object
Object - корень иерархии классов Java. Обычные классы без явно указанного родителя наследуют его неявно; массивы тоже являются объектами. Примитивы не являются объектами, а интерфейсы не наследуют Object как суперкласс.
Полезные методы: toString(), equals(), hashCode() и getClass(). Метод Object.clone() выполняет поверхностное копирование и обычно требует Cloneable. Когда важно владение данными, предпочитай явное копирование.
Для wait(), notify() и notifyAll() поток должен владеть монитором объекта; см. Java Concurrency. Не полагайся на finalize() для освобождения ресурсов. Используй явное закрытие или try-with-resources; см. Java Exceptions.
Контракт equals() / hashCode()
Для ссылок Java == проверяет идентичность объектов. Стандартный Object.equals() делает то же самое; классы вроде String переопределяют его для сравнения значений. Для сравнения с учётом null используй Objects.equals(a, b). Содержимое массивов сравнивают через Arrays.equals() или Arrays.deepEquals().
Корректный equals() рефлексивен, симметричен, транзитивен и даёт согласованные результаты, пока значимое состояние не меняется; ненулевой объект не равен null.
Равные объекты обязаны иметь одинаковый хэш. У неравных объектов хэши могут совпадать. При определении равенства по значению реализуй equals() и hashCode() вместе, используя согласованный набор данных.
Класс как ключ HashMap
Переопределение этих методов нужно для собственного равенства по значению, а не для любого класса ключа: унаследованное сравнение по идентичности корректно, если именно оно требуется.
HashMap сужает поиск по хэшу, затем определяет подходящий ключ по идентичности или равенству. Пока ключ хранится в коллекции, состояние, участвующее в равенстве и вычислении хэша, должно оставаться стабильным. Иначе поиск или удаление могут не сработать даже с тем же объектом.
Предпочитай неизменяемые идентификаторы. Kotlin data class генерирует равенство и хэш по свойствам первичного конструктора, но изменяемые свойства или вложенные объекты всё равно могут сделать ключ ненадёжным.
Обобщения и коллекции
Обобщения и примитивы
Аргументами типов Java могут быть только ссылочные типы: List<Integer>, но не List<int>. Boxing преобразует примитив в обёртку, unboxing выполняет обратное преобразование. Распаковка null вызывает NullPointerException, а == между двумя обёртками сравнивает идентичность, а не числовое значение.
Стирание типов заменяет параметр типа его первой верхней границей или Object, если граница не задана; компилятор вставляет необходимые приведения. Обобщённые сигнатуры могут сохраняться в метаданных, но обычный список не несёт проверяемый во время выполнения аргумент типа элементов.
List<Integer> не является подтипом List<Number>. Wildcards задают допустимое использование:
List<? extends Number>: можно читать элементы какNumber, но нельзя безопасно добавить ненулевой элемент.List<? super Integer>: можно добавлятьInteger, но результат чтения имеет типObject.
Избегай сырых типов и непроверяемых приведений. Вариантность и стирание подробнее описаны в Generics.
Коллекции обёрток могут потреблять больше памяти и создавать больше объектов, чем int[] или специализированные структуры. Boxing не обязательно создаёт новый объект каждый раз; оптимизируй участки, значимость которых подтверждена измерениями.
Iterator и Iterable
Iterable.iterator() создаёт итератор; hasNext() проверяет наличие элемента, а next() возвращает его либо выбрасывает NoSuchElementException, если элементы закончились. Цикл Java for-each поддерживает и Iterable, и массивы; сами массивы не реализуют Iterable.
Если итератор поддерживает удаление, вызывай remove() после next(), не более одного раза на возвращённый элемент. Иначе возможны UnsupportedOperationException или IllegalStateException.
Структурные изменения в обход обычного fail-fast итератора могут вызвать ConcurrentModificationException даже в одном потоке. Обнаружение не гарантировано и не обеспечивает потокобезопасность. У конкурентных коллекций собственные правила обхода.
Интерфейсы и реализации коллекций
| Интерфейс | Назначение | Типичная реализация |
|---|---|---|
List |
Индексированная последовательность, повторы разрешены | ArrayList |
Set |
Уникальные элементы | HashSet |
Queue |
Очередь обработки; порядок зависит от реализации | PriorityQueue |
Deque |
Очередь с операциями на обоих концах; расширяет Queue |
ArrayDeque |
Map |
Уникальные ключи и соответствующие значения; не является Collection |
HashMap |
Collection расширяет Iterable; Map предоставляет представления коллекций через keySet(), values() и entrySet().
HashMap не гарантирует порядок обхода и обеспечивает ожидаемое постоянное время базового поиска при хорошем распределении хэшей. LinkedHashMap обычно сохраняет порядок вставки и может использовать порядок доступа. TreeMap сортирует ключи по естественному порядку или компаратору и обеспечивает логарифмический поиск; нулевой результат сравнения определяет эквивалентность ключей.
Коллекция с запрещёнными изменениями не обязательно является неизменяемым снимком: обёртка может отражать изменения исходной коллекции. См. Collections.
Типы и доступ
Модификаторы доступа
Для членов класса:
public: доступ везде, где доступен объявляющий тип, с учётом границ модулей, если они применимы.private: доступ внутри тела охватывающего класса верхнего уровня, включая его вложенные классы.- Без модификатора: package-private, доступ в том же пакете. Подпакеты являются отдельными пакетами.
protected: доступ в том же пакете и по правилам доступа из подклассов за его пределами.
Вне пакета подкласс не может обратиться к защищённому члену экземпляра через произвольный экземпляр родителя: ссылка должна иметь тип этого подкласса или его потомка. Классы верхнего уровня допускают public или package-private, но не private и не protected. У членов интерфейса другие неявные модификаторы.
Проверка и приведение типов: instanceof
instanceof проверяет совместимость типов во время выполнения и возвращает false для null. Приведение меняет тип ссылки, известный компилятору, а не сам объект. Несовместимое приведение ссылочного типа вызывает ClassCastException; приведение null к ссылочному типу даёт null.
static void printText(Object value) {
if (value instanceof String) {
String text = (String) value;
System.out.println(text.length());
}
}
Если инструменты сборки и уровень языка поддерживают сопоставление с образцом:
static void printText(Object value) {
if (value instanceof String text) {
System.out.println(text.length());
}
}
В Android различай JDK, запускающий Gradle, настроенный уровень языка Java и доступные API среды выполнения. Установка нового JDK сама по себе не включает все возможности языка и библиотек. Предпочитай полиморфизм, если повторные проверки типов дублируют поведение подклассов.
Реализация и время жизни singleton
public final class MySingleton {
private static final MySingleton INSTANCE = new MySingleton();
private MySingleton() {}
public static MySingleton getInstance() {
return INSTANCE;
}
}
Экземпляр создаётся при инициализации класса, не обязательно при запуске приложения. Гарантии инициализации обеспечивают безопасную публикацию объекта, но не потокобезопасность последующих изменений. Вложенный holder-класс позволяет отложить создание до вызова метода доступа. Другой вариант - enum; double-checked locking требует volatile и корректной синхронизации.
Статический экземпляр принадлежит конкретному загруженному классу и не является общим для всех процессов приложения. После смерти Android-процесса его состояние теряется. Не удерживай Activity или View в долгоживущих объектах; внедрение зависимостей помогает явно задать владельца и упростить замену в тестах. См. DI Basics.