Skip to content
Michael Bely edited this page Apr 29, 2024 · 84 revisions

Important

ВНИМАНИЕ!
ЭТОТ РАЗДЕЛ БОЛЬШЕ НЕ ПОДДЕРЖИВАЕТСЯ!
РОАДМАП ПЕРЕЕХАЛ В NOTION

Собеседование по Java — типы данных, переменные, операторы, циклы, массивы (вопросы и ответы)
Собеседование по Java — ООП (вопросы и ответы). Часть 1
Собеседование по Java — ООП (вопросы и ответы). Часть 2
Собеседование по Java — ООП (вопросы и ответы). Часть 3


native
Реализация данного метода выполнена на другом языке (C, C++, ассемблер). Пример - метод hashCode у Object

Basic types
Примитивные типы. Есть 8 типов, деляется на 4 группы
• целые числа - byte, short, int, long
• числа с плавающей точкой (иначе вещественные) - float, double
• логический тип данных - boolean
• символьный тип данных - char

Reference types
Ссылочные типы: Byte, Short, Integer, Long, Float, Double, Boolean, Character. У классов-оберток есть ряд характеристик, не свойственных примитивам. У каждого примитивного типа данных есть свой класс-обертка. Ссылочный тип данных, который “оборачивает” своего примитивного младшего брата в Java-объект. Ниже приведены примитивные типы данных и соответствующие им классы обертки
• это классы: при работе с классам-обертками мы работаем с объектами
• данные объекты могут быть null
• предоставляют ряд констант и методов, упрощающих работу с определенным типом данных

Object

Разбираемся с hashCode() и equals()
Сравнение объектов: теория

Object
Все классы являются наследниками суперкласса Object. Это не нужно указывать явно. Объект Object может ссылаться на объект любого другого класса

Methods
equals(Object) – сравнивает объекты
hashCode() – возвращает хэш-код (битовая строка фиксированной длины, целочисленный результат работы метода, которому как входной параметр передан объект)
toString() – возвращает строковое представление объекта
clone() – клонирование объекта. Объявлен как protected, нужно переопределить, чтобы воспользоваться. Для этого нужно реализовать интерфейс Cloneable, чтобы соблюсти контракт
finalize() – вызывается сборщиком мусора, когда ссылок на объект больше нет
getClass() – возвращает в рантайме класс данного объекта
notify() – просыпается один поток, который ждет на “мониторе” данного объекта
notifyAll() – просыпаются все потоки, которые ждут на “мониторе” данного объекта
wait() – приводит данный поток в ожидание, пока другой поток не вызовет notify() или notifyAll() методы для этого объекта
wait(long timeout) – поток переходит в режим ожидания в течение указанного времени
wait(long timeout, int nanos) – приводит данный поток в ожидание, пока другой поток не вызовет notify() или notifyAll() для этого метода, или пока не истечет указанный промежуток времени

hashCode()
Это число, битовая строка фиксированной длины, полученная из массива произвольной длины (то есть из объекта), целочисленный результат работы метода, которому в качестве входного параметра передан объект. Вычисление нативное (на C++), используется алгоритм Park Miller, в основе которого random. При каждом запуске приложения у объекта будет разный хеш-код • для одного и того же входного объекта хеш-код всегда будет одинаковым
• если хеш-коды разные, то и входные объекты гарантированно разные
• если хеш-коды равны, то входные объекты не всегда равны
• количество хеш-кодов ограничено типом int, поэтому хеш-коды разных объектов могут совпадать
• коллизия - ситуация, когда у разных объектов одинаковые хеш-коды. Вероятность возникновения коллизии зависит от используемого алгоритма генерации хеш-кода. Коллизии неизбежны, так как вариантов хешей меньше, чем потенциальных объектов
• какие поля использовать для посчета хеш-кода? - те, которые используются при определении метода equals
• генерируется один раз для каждого объекта при первом вызове метода hashCode, после чего хранится в объекте для последующих вызовов
• число значений hashcode равно диапазону типа int — 2^32

equals(Object)
Эквивалентность. Сравнивает содержимое объектов. Стандартная реализация метода equals в Object вернет true лишь в одном случае — когда ссылки указывают на один и тот же объект (адрес объекта в памяти). Содержимое полей не учитывается. Если у двух объектов одного и того же класса содержимое одинаковое, то и хеш-коды должны быть одинаковые. Поэтому, при создании пользовательского класса, принято переопределять методы hashCode() и equals() таким образом, чтобы учитывались поля объекта
• одинаковые объекты — это объекты одного класса с одинаковым содержимым полей
• реализация этого метода для нового класса ложится на плечи разработчика
• при переопределении equals обязательно нужно переопределить hashCode (это даст равенство хеш-кодов для равных обектов, распределено полученное значение будет точно так же, как и исходные данные)
• равные объекты должны возвращать одинаковые хеш-коды • если переопределить equals без hashCode - классы и методы будут некорректно работать. У объекта HashMap пара, помещенная в Map возможно не будет найдена в ней при обращении, если используется новый экземпляр ключа
• реализация equals должна подчиняться правилам рефлексивности, симметрии, транзитивности и непротиворечивости

Strings

Собеседование по Java — работа со строками (String in Java) (вопросы и ответы)

String
Последовательность символов. Все строковые классы — final (от них нельзя унаследоваться). String - иммутабельный final class - созданный объект класса String не может быть изменен

String name = "Bob";

StringBuffer
Строки являются неизменными, поэтому частая их модификация приводит к созданию новых объектов, что в свою очередь расходует память. Для решения этой проблемы был создан класс StringBuffer, который позволяет более эффективно работать над модификацией строки. Класс является mutable, то есть изменяемым — используйте его, если хотите изменять содержимое строки. StringBuffer может быть использован в многопоточных средах, так как все необходимые методы являются синхронизированными

StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("abc").append("def");

StringBuilder
StringBuilder — класс, что представляет изменяемую последовательность символов. Класс был введен в Java 5 и имеет полностью идентичный API с StringBuffer. Единственное отличие — StringBuilder не синхронизирован. Это означает, что его использование в многопоточных средах нежелательно. Следовательно, если вы работаете с многопоточностью, Вам идеально подходит StringBuffer, иначе используйте StringBuilder, который работает намного быстрее в большинстве реализаций

StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abc").append("def");

Arrays

Используются для хранения нескольких значений в одной переменной вместо объявления отдельных переменных для каждого значения. Доступ по индексу. Имеют фиксированный размер, не изменить после объявления. Лучше по производительности

String[] people = { "Bob", "Alice", "John" };
int[] numbers = new int[10];

Collections

Собеседование по Java — коллекции (Collections) (вопросы и ответы)
Java собеседование. Коллекции
Структуры данных в картинках. HashMap
Структуры данных в картинках. LinkedHashMap
Структуры данных в картинках. ArrayList
Структуры данных в картинках. LinkedList
Внутренняя работа HashMap в Java

Collection
Базовый интерфейс для всех коллекций. Коллекции/контейнеры - классы, хранящие набор элементов. Коллекции могут хранить любые ссылочные типы данных. Преимущества коллекций перед массивами - массивы имеют конечный размер (необходимость следить за ним) и индексную адресацию, ограничивающую добавление и удаление объектов

List
Упорядоченный список. Объекты хранятся в порядке их добавления. Доступ к элементам осуществляется по индексу. Наследуется от Collection

List<String> list = List.of(); // только для чтения
List<String> list = new ArrayList<>();

ArrayList
Реализован внутри в виде обычного массива. Поэтому при вставке элемента в середину, приходится сначала сдвигать на один все элементы после него, а уже затем в освободившееся место вставлять новый элемент. Минусы в скорости вставки/удаления элементов находящихся не в конце списка, так как при этой операции все элементы правее добавляемого/удаляемого сдвигаются. Размер по умолчанию - 10
• начальный размер (capacity) равен 10, при добавлении размер увеличивается в среднем в 1.5 раз и элементы копируются, старый удаляется gc
• доступ по индексу выполняется за константное время
• вставка в конец списка и удаление его выполняется за константное время
• вставка в середину списка приводит к перезаписи всех элементов правее (линейное время)
• при удалении элемента размер массива не уменьшается, capacity не меняется (есть метод trimToSize())
• если известно примерное количество элементов, указать capacity, чтобы не тратить ресурсы на расширение списка

ArrayList<String> list = new ArrayList<>();
List<String> unmodifiableList = Collections.unmodifiableList(list); // только для чтения

LinkedList
Реализован в виде связного списка: набора отдельных элементов, каждый из которых хранит ссылку на следующий и предыдущий элементы. Чтобы вставить элемент в середину списка, достаточно поменять ссылки его будущих соседей. А вот чтобы получить элемент с номером 130, нужно пройтись последовательно по всем объектам от 0 до 130. Другими словами операции set и get очень медленные. Если необходимо вставлять (или удалять) в середину коллекции много элементов и гарантировать время этих операций, то лучше использовать LinkedList, в остальных случаях – ArrayList. LinkedList требует больше памяти для хранения такого же количества элементов, потому что кроме самого элемента хранятся еще указатели на соседей, тогда как в ArrayList элементы просто идут по порядку. LinkedList - вагоны поезда, сцепленные последовательно. Операции доступа по индексу производятся перебором с начала или конца (смотря что ближе) до нужного элемента. Как в LinkedList вставить новый элемент в середину списка: сначала проверяем наш capacity сдвигаем все что справа элементы и вставляем туда новый. если места не хватает выделяется память в 2 раза больше нынешнего capacity создается новый массив и все элементы копируются. чтобы оптимизировать память нужно задавать размер списка capacity изначально
• LinkedList не синхронизирован
• позволяет хранить любые объекты, в том числе null и повторяющиеся
• добавление и удаление элементов выполняется за константное время
• доступ к first и last элементам выполняется за константное время, к остальным за линейное

LinkedList<String> list = new LinkedList<>();

Set
Множество неповторяющихся уникальных объектов. В коллекции этого типа разрешено наличие только одной ссылки типа null. Наследуется от Collection

Set set = new HashSet();

HashSet
Реализован на основе хэш-таблицы. Набор объектов или хеш-множество, где каждый элемент имеет ключ - уникальный хеш-код. Название Hash происходит от понятия хэш-функция. Хэш-функция — это функция, сужающая множество значений объекта до некоторого подмножества целых чисел. Класс Object имеет метод hashCode(), который используется классом HashSet для эффективного размещения объектов, заносимых в коллекцию. В классах объектов, заносимых в HashSet, этот метод должен быть переопределен. HashSet гораздо быстрее чем TreeSet (константное время против логарифмического для большинства операций, таких как add, remove, contains). Предоставляет константное время для add(), remove(), contains() и size(). Производительность итерации по контейнеру зависит от емкости и «коэффициента загрузки»
• у элементов hashset должны быть переопределены equals и hashcode
• если переопределить equals без hashCode или оба метода неправильно, то есть вероятность, что два объекта попадут в одно подмножество в HashSet
• если переопределен hashcode без equals возвращаемое значение equals является критерием равенства в неупорядоченных коллекциях, – два равных по содержимому объекта гарантированно не будут признаны равными ни в HashSet, ни при поиске по ключу в HashMap. А, следовательно, ошибку найти проще
• порядок элементов в контейнере может меняться

HashSet<String> set = new HashSet<>();
LinkedHashSet<String> set = new LinkedHashSet<>();

TreeSet
Реализован на основе бинарного дерева (красно-черное). Гарантирует упорядоченночть объектов. Время для базовых операций add(), remove(), contains() — log(n). Не предоставляет каких-либо параметров для настройки производительности. Предоставляет дополнительные методы для упорядоченного списка: first(), last(), headSet(), tailSet() и т.д. • гарантирует порядок элементов
• подход как в hashmap, с разницей, что ключом служит сам элемент

TreeSet<String> set = new TreeSet<>();

SortedSet
Реализации этого интерфейса, помимо того что следят за уникальностью хранимых объектов, поддерживают их в порядке возрастания. Отношение порядка между объектами может быть определено, как с помощью метода compareTo. Наследуется от Set

SortedSet<String> set = new TreeSet<>();

Queue
Коллекция, предназначенная для хранения элементов в порядке, нужном для их обработки. Наследуется от Collection. Очереди обычно, но не обязательно, упорядочивают элементы в FIFO (first-in-first-out, «первым вошел — первым вышел») порядке. Deque - двунаправленные очереди

Queue<String> queue = new ArrayDeque<>();
ArrayDeque<String> array = new ArrayDeque<>();
PriorityQueue<String> queue = new PriorityQueue<>();
peekFirst() - извлекает первый элемент
pollFirst() - извлекает и удаляет первый элемент из очереди. Возвращает NULL, если очередь пуста
pop() - удаляет элемент из очереди
peek() - получает элемент из очереди

Map
Функциональность ассоциативных массивов. Предназначен для созданий структур данных в виде словаря, где каждый элемент имеет определенный ключ и значение. Для поиска объекта по ключу их необходимо сравнивать. • не наследуется от Collection - это отдельная структура данных
• каждому ключу сопоставлено значение, а, следовательно, ключи должны быть уникальны

HashMap
Реализован на хеш-таблицах, реализует интерфейс Map (хранение данных в виде пар ключ-значение)
• отличается от Hashtable тем, что не синхронизирован и допускает значения null
• ключи и значения могут быть любых типов, в том числе и null (уникальные), для хранения примитивных типов используются классы-обертки
• порядок элементов не гарантируется
• разрешение коллизий осуществляется с помощью метода цепочек
• начальный размер 16, при создании можно указать capacity в конструкторе (max capacity int/2), должно быть степенью двойки
• добавление, поиск (доступ к) и удаление элементов выполняются за константное время, время выборки (указанная сложность) не гарантируется, элемент добавляется в начало цепочки
• при добавлении элемента сначала ключ проверяется на null, далее генерируется хэш на основе ключа (используется метод hash(Int) в который передается key.hashcode()), далее определяется позиция в массиве (номер корзины), куда будет помещен элемент, если хэш или ключ совпадают - элемент перезаписывается, иначе корзина пустая и просто добавляется на позицию
• метод hashCode() используется для вычисления корзины (bucket) и следовательно вычисления индекса
• метод equals используется для проверки равенства ключей, если ключи равны true, иначе false
• если у объекта, который используется в качестве ключа в hashMap изменится поле, которое участвует в определении hashCode, то при обращении по ключу объект будет потерян
• если переопределить equals без hashCode или оба метода неправильно и эти объекты использовать в качестве ключей в HashMap (используя один объект как ключ, положить значение, используя другой – искать его), то значение найдено не будет
• увеличение количества корзин в hashmap происходи при достижении предельного значения (capacity * loadFactor), количество корзин становится x2, для всех хранимых элементов вычисляется новое «местоположение» с учетом нового числа корзин
• при удалении элементов размер table[] остается прежним, размер hashmap уменьшается, но не до предыдущего
• hashMap состоит из массива Node (хэш, ключ, значение, следующий node)
• если ключ null - хеш-код будет 0
• bucket - это единственный элемент массива HashMap. Он используется для хранения узлов (Nodes). Два или более узла могут иметь один и тот же bucket. В этом случае для связи узлов используется linked list. Один bucket может иметь более, чем один узел, это зависит от реализации метода hashCode(). Чем лучше реализованн ваш метод hashCode(), тем лучше будут использоваться ваши buckets
• вычисление индекса: хэш-код ключа может быть достаточно большим для создания массива. Сгенерированный хэш код может быть в диапазоне целочисленного типа и если мы создадим массив такого размера, то легко получим исключение outOfMemoryException. Потому мы генерируем индекс для минимизации размера массива.

HashMap<Integer, String> map = new HashMap<>();
LinkedHashMap<Integer, String> map = new LinkedHashMap<>();

TreeMap
Структура данных в виде дерева, где каждый элемент имеет уникальный ключ и некоторое значение. Полностью реализует SortedMap

TreeMap<Integer, String> map = new TreeMap<>();

Hashtable
Хеш-таблица пары ключ-значение
• в качестве ключа/значения объекты not null

Concurrent Collections

Как работает ConcurrentHashMap

Потокобезопасные коллекции, при итерировании не бросают ConcurrentModificationException

synchronized List
Потокобезопасный List

List<String> synchronizedList = Collections.synchronizedList(list);

synchronized Set
Потокобезопасный Set

Set<String> synchronizedSet = Collections.synchronizedSet(new HashSet<>());

synchronized HashMap
• синхронизация на уровне объекта (дает доступ только 1 потоку и блокирует для остальных)
• get set приводят к блокировке (блокировка всей коллекции = расходы на производительность)
• можем иметь 1 null в качестве ключа и сколько угодно null значений

Map<String, String> synchronizedMap = Collections.synchronizedMap(new HashMap<>());

CopyOnWriteArrayList
Потокобезопасный ArrayList
• операции add, set, remove созданиют новую копию внутреннего массива (копируются только ссылки на объекты)

List<String> list = new CopyOnWriteArrayList<>();

CopyOnWriteArraySet
Имплементация интерфейса Set, использующая за основу CopyOnWriteArrayList. В отличии от CopyOnWriteArrayList, дополнительных методов нет.

Set<String> set = new CopyOnWriteArraySet<>();

ConcurrentHashMap
В отличие от Hashtable и блоков synhronized на HashMap, данные представлены в виде сегментов, разбитых по hash'ам ключей. В результате, для доступ к данным лочится по сегментам, а не по одному объекту. В дополнение, итераторы представляют данные на определенный срез времени
• Entry в ConcurrentHashMap объявлены как volatile
• улучшенная функция хэширования (уменьшить вероятность коллизий)
• чтение происходит быстро, не требует блокировки и выполняются параллельно
• запись часто выполняется с блокировкой
• map делится на сегменты, каждый - потокобезопасная таблица
• если capacity - 16, значит 16 потоков могут одновременно работать с map
• не допускает null в ключах и значениях

Map<String, String> map = new ConcurrentHashMap<>();

Serialization

Serializable
Стандартный интерфейс Java. Объекты, реализующие этот интерфейс автоматически сериализуются. Под капотом рефлексия, поэтому медленный. Есть режим ручного управления через методы readObject, writeObject

transient
Игнорировать поле при сериализации/десериализации

class Book implements Serializable {
    private String name;
    private transient String description;
}

Modifiers

static
Модификатор, применяемый к полю, блоку, методу или внутреннему классу. Данный модификатор указывает на привязку субъекта к текущему классу

static class Cat {

    static String name = "Barsik";

    static {
        name = "Oscar";
    }

    public static void rename() {
        name = "Tom";
    }
}

final
Запретить наследование для классов. Запретить переопределять методы в подклассах. Сделать переменные неизменяемыми

final class Cat {

    final String name = "Barsik";

    final void main() {}
}

public
Поля и методы видны другим классам из текущего пакета и из внешних пакетов

private
Доступен на уровне класса

protected
Доступен из любого места в текущем классе или пакете или в производных классах, даже если они находятся в других пакетах

Concurrent

Собеседование по Java — многопоточность (вопросы и ответы)
Многопоточность в Java
Обзор java.util.concurrent.*

Heap
Куча. Используется для выделения памяти под объекты и классы. Создание нового объекта происходит в куче. Любой объект, созданный в куче, имеет глобальный доступ и на него могут ссылаться с любой части приложения. Если память кучи заполнена, вызывается OutOfMemoryError

Stack
Стековая память работает по схеме last in first out. Всегда, когда вызывается метод, в памяти стека создается новый блок, который содержит примитивы и ссылки на другие объекты в методе. Как только метод заканчивает работу, блок также перестает использоваться, тем самым предоставляя доступ для следующего метода. Размер стековой памяти намного меньше объема памяти в куче. Стек используется одним потоком выполнения программы. Память стека содержит только локальные переменные примитивных типов и ссылки на объекты, которые хранятся в куче. Из-за простоты распределения памяти, стековая память работает намного быстрее кучи. Если память стека полностью занята, то вызывается StackOverflowError

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

Runnable
Интерфейс, содержащий метод run, в котором выполняется код потока

Thread
API для потоков, которыми управляет JVM и ОС. Наследуется от Runnable. Как создать поток: создать объект класса Thread, передав ему в конструкторе нечто, реализующее интерфейс Runnable. Этот интерфейс содержит метод run(), выполняющийся в новом потоке. Поток закончит выполнение, когда завершится его метод run()

val thread: Thread = Thread(
    object: Runnable {
        override fun run() {
            // выполнение нового потока
        }
    }
)
thread.start()

Thread.sleep()
Статический метод, приостанавливает выполнение потока. Во время выполнения метода sleep() система перестает выделять потоку процессорное время, распределяя его между другими потоками

Thread.yield()
Статический метод, заставляет процессор переключиться на обработку других потоков системы. Метод может быть полезным, например, когда поток ожидает наступления какого-либо события и необходимо чтобы проверка его наступления происходила как можно чаще. В этом случае можно поместить проверку события и метод Thread.yield() в цикл

// ожидание поступления сообщения
while(!msgQueue.hasMessages()) { // пока в очереди нет сообщений
    Thread.yield(); // передать управление другим потокам
}

join()
Механизм, позволяющий одному потоку ждать завершения выполнения другого

ThreadPool
Пулы потоков представляют собой управляемую коллекцию потоков, которые доступны для выполнения различных задач. Пулы потоков обеспечивают:
• повышение производительности при выполнении большого количества задач в связи с сокращением накладных расходов на вызов каждой задачи • ограничивают расход ресурсов при выполнении набора задач • избавляют от необходимости управления жизненным циклом потоков

ThreadPoolExecutor
Реализация ExecutorService. Выполняет переданную задачу (Callable или Runnable), используя одну из внутренних доступных нитей из пула. Пул потоков содержит в себе ThreadPoolExecutor, который может содержать изменяющееся число потоков. Число нитей в пуле задается с помощью corePoolSize и maximumPoolSize

ScheduledThreadPoolExecutor
Является наследником ThreadPoolExecutor, определенного в пакете java.util.concurrent. Как видно из его названия, этот класс полезен, когда мы хотим запланировать многократное выполнение задач или запуск после заданной задержки на какое-то время в будущем. Он создает пул потоков фиксированного размера. Поэтому, когда он инициируется, ему необходимо указать corePoolSize

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

ExecutorService
ExecutorService исполняет асинхронный код в одном или нескольких потоках. Создание экземпляра ExecutorService делается либо вручную через конкретные реализации (ScheduledThreadPoolExecutor или ThreadPoolExecutor), но проще будет использовать фабрики класса Executors

ExecutorService service = Executors.newFixedThreadPool(2); // пул с 2 потоками
ExecutorService service = Executors.newCachedThreadPool(); // кэширующий пул потоков, который создает потоки по мере необходимости, но переиспользует неактивные потоки

ScheduledExecutorService
Иногда требуется выполнение кода асихронно и периодически или требуется выполнить код через некоторое время, тогда на помощь приходит ScheduledExecutorService. Он позволяет поставить код выполняться в одном или нескольких потоках и сконфигурировать интервал или время, на которое выполненение будет отложено. Интервалом может быть время между двумя последовательными запусками или время между окончанием одного выполнения и началом другого

ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.schedule(new Runnable() { ... }, 5, TimeUnit.SECONDS);

Future
Интерфейс описывает API для работы с задачами, результат которых мы планируем получить в будущем: методы получения результата, методы проверки статуса

Callable task = () -> {
    return "Hello, World!";
};
FutureTask<String> future = new FutureTask<>(task);
new Thread(future).start();

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

public synchronized void doSomething() {
    // целый синхронизированный метод
}
private Object obj = new Object();

public void doSomething() {
    //...логика, доступная для всех потоков
    synchronized (obj) {
        // логика, которая одновременно доступна только для одного потока
    }
}

volatile
Переменная volatile всегда будет атомарно читаться и записываться. Даже если это 64-битные double или long. JVM не будет помещать ее в кэш. Так что ситуация, когда 10 потоков работают со своими локальными копиями исключена. Не используется кэш (область памяти в которой JVM может сохранять локальную копию переменной, чтобы уменьшить время обращения к ней) при обращении к полю. Для volatile JVM гарантирует синхронизацию для операций чтения/записи, но не гарантирует для операций изменения значения переменной (инкремент/декремент) - использовать атомарный тип

atomic
В процессе работы многопоточного приложения разные потоки могут кэшировать значения переменных. Возможна ситуация, когда один поток изменил значение переменной, а второй не увидел этого изменения, потому что работал со своей, кэшированной копией переменной. В Java операции чтения и записи полей всех типов, кроме long и double, являются атомарными. Если в одном потоке изменить значение перменной, а в другом попытаться ее считать, то получим либо ее старое закэшированное значение, либо новое, без промежуточных вариантов. С long и double это не работает из-за кроссплатформенности, это тяжеловесные типы, весят по 64 бита. В 32-битных платформах не реализована атомарность чтения и записи 64-битных переменных, они читаются и записываются в две операции. Сначала в переменную записываются первые 32 бита, потом еще 32. В это время и может возникнуть ошибка, которая решается ключевым словом volatile
Операция называется атомарной, если её можно безопасно выполнять при параллельных вычислениях в нескольких потоках, не используя при этом ни блокировок, ни synchronized. Пример: инкремент, декримент

deadlock
Взаимная блокировка
• возникает в случае неверного порядка синхронизации, исправить порядок методов

BlockingQueue

Input - Output

Собеседование по Java — потоки ввода/вывода (вопросы и ответы)

InputStream/OutputStream
Bite-oriented streams. Байтовые потоки. В случае ошибки - IOException

Reader/Writer
Character-oriented streams. Символьные потоки. В случае ошибки - IOException

BufferedInputStream/BufferedOutputStream & BufferedReader/BufferedWriter
Классы-надстройки наделяют существующий поток дополнительными свойствами. Буферизируют поток и повышают производительность

Exceptions

Собеседование по Java — исключения (exceptions) (вопросы и ответы)

Checked Exceptions
Проверенные (логические) исключения - ошибки при вызове стороннего API, когда мы можем повторить вызов, отменить его или обработать через try-catch. Такие исключения могут быть проверены во время компиляции: ClassNotFoundException, IOException, SQLException

Unchecked Exceptions
Непроверенные исключения (runtime exceptions) - являются ошибками программирования и обычно не должны специально обрабатываться: NullPointerException, ArrayIndexOutOfBoundsException, ClassCastException

throw
Метод явного создания исключения

throw new IllegalArgumentException();

throws
Указать все исключения, которые может сгенерировать метод

public void someMethod() throws NullPointerException, IllegalArgumentException {}

Enum

enum
Перечесления. Набор логически связанных констант

enum Color {
    RED("#FF0000"),
    GREEN("#00FF00"),
    BLUE("#0000FF"), ;
    
    private String code;
    
    Color(String code) {
        this.code = code;
    }
    
    public String getCode() { 
        return code;
    }
}

Garbage Collector (GC)

Мягкие ссылки на страже доступной памяти или как экономить память правильно

Сборка мусора  —  процесс восстановления заполненной памяти среды выполнения путем уничтожения неиспользуемых объектов. Сборщик мусора запускается виртуальной машиной Java (JVM). В таких языках, как C и C++, программист отвечает как за создание, так и за уничтожение объектов, в Java сборщик мусора управляет памятью автоматически. Сборщик мусора — низкоприоритетный процесс, который запускается периодически и освобождает память, использованную объектами, которые больше не нужны. Если забыть уничтожить бесполезные объекты - выделенная им память не будет освобождаться, будет расходоваться все больше и больше системной памяти, и в конечном итоге перестанет выделяется. Такие приложения страдают от «утечек памяти». После определенного момента памяти уже не хватает для создания новых объектов, и программа нештатно завершается с OutOfMemoryError

GC roots
Все объекты на которые прямо или косвенно ссылаются из gc roots не будут удалены
• типы gs roots: классы, локальные переменные, активные потоки, ссылки JNI, некоторые другие объекты
• если два объекта ссылаются друг на друга и больше на них никто не ссылается, то они будут удалены сборщиком мусора

finalize()
Метод класса Object, который вызывается перед удалением объекта из памяти (перед сборкой мусора). Его можно переопределить и в теле метода указать те действия, которые должны быть выполнены перед уничтожением объекта

SoftReference
Уничтожится сборщиком мусора в ответ на потребности памяти (при нехватке). Используется для кэшей, чувствительных к памяти. Может пережить таким образом много сборок мусора

StringBuilder builder = new StringBuilder();
SoftReference<StringBuilder> softBuilder = new SoftReference(builder);

WeakReference
Уничтожится при ближайшей сборке мусора. Пока она не произошла можно получить объект методом get()

StringBuilder builder = new StringBuilder();
WeakReference<StringBuilder> weakBuilder = new WeakReference(builder);

PhantomReference
Этот тип ссылок в комбинации с ReferenceQueue позволяет нам узнать, когда объект более недоступен и на него нет других ссылок. Это позволяет нам сделать очистку ресурсов, используемых объектом, на уровне приложения. В отличии от finalize() мы сами контролируем процесс очистки ресурсов. Помимо этого, мы можем контролировать процесс создания новых объектов

WeakHashMap
HashMap, у которого ключи – это слабые ссылки – WeakReference. Пока на объекты, которые ты хранишь в WeakHashMap в качестве ключей есть обычные (сильные или мягкие) ссылки, эти объекты будут живы

WeakHashMap<Int, String> map = new WeakHashMap<Int, String>();
Clone this wiki locally