Java
18.01.2024 | https://habr.com/ru/articles/787166/ |
28.07.2020 | https://habr.com/ru/articles/512730/ |
11.12.2012 | https://habr.com/ru/post/162017 |
Dynamic Proxy
Прокси-класс — «надстройка» над оригинальным классом, которая позволяет при необходимости изменить его поведение. Чтобы добавить поведение нам нужен InvocationHandler
- перехватчик вызовов. Это специальный интерфейс который позволяет перехватить любые вызовы методов к нашему объекту и добавить нужное нам дополнительное поведение. Нам необходимо сделать собственный перехватчик — то есть, создать класс и реализовать этот интерфейс. Нам нужно реализовать всего один метод интерфейса — invoke
. Он делает то что нам нужно — перехватывает все вызовы методов к нашему объекту и добавляет необходимое поведение (здесь мы внутри метода invoke
выводим лог в консоль).
Project Valhalla
Нововведение в JVM улучшающее производительность. Каждый объект в Java помимо полезной информации содержит еще и т.н. метаданные (или заголовок/хедер). Даст возможность создавать кастомные примитивные или "value" объекты. Эти объекты должны представлять собой плоскую структуру, подобно массиву int[]
а не дерево "указателей".
native
Реализация данного метода выполнена на другом языке (C, C++, ассемблер). Пример - метод hashCode
у Object
.
Types
Basic types
Примитивные типы. Есть 8 типов деляется на 4 группы:
• целые числа - byte
short
int
long
• числа с плавающей точкой (иначе вещественные) - float
double
• логический тип данных - boolean
• символьный тип данных - char
Reference types
Ссылочные типы: Byte
Short
Integer
Long
Float
Double
Boolean
Character
. У классов-оберток есть ряд характеристик, не свойственных примитивам. У каждого примитивного типа данных есть свой класс-обертка. Ссылочный тип данных, который “оборачивает” своего примитивного младшего брата в Java-объект. Ниже приведены примитивные типы данных и соответствующие им классы обертки
• это классы: при работе с классам-обертками мы работаем с объектами
• данные объекты могут быть null
• предоставляют ряд констант и методов, упрощающих работу с определенным типом данных
Boxing
Процесс преобразования примитивного типа данных в объект его соответствующего типа-обертки.
int primitiveInt = 42; // Примитивный тип
Integer wrappedInt = primitiveInt; // Боксинг, превращает примитив в объект
Unboxing
Процесс преобразования объекта типа-обертки в соответствующий примитивный тип.
Integer wrappedInt = 42; // Объект типа Integer
int primitiveInt = wrappedInt; // Анбоксинг, превращает объект в примитив
Object
Object
Все классы являются наследниками суперкласса Object. Это не нужно указывать явно. Объект Object может ссылаться на объект любого другого класса.
Methods
• equals
сравнивает объекты
• 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
Эквивалентность. Сравнивает содержимое объектов. Стандартная реализация метода equals в Object вернет true лишь в одном случае — когда ссылки указывают на один и тот же объект (адрес объекта в памяти). Содержимое полей не учитывается. Если у двух объектов одного и того же класса содержимое одинаковое, то и хеш-коды должны быть одинаковые. Поэтому, при создании пользовательского класса, принято переопределять методы hashCode() и equals() таким образом, чтобы учитывались поля объекта
• одинаковые объекты — это объекты одного класса с одинаковым содержимым полей
• реализация этого метода для нового класса ложится на плечи разработчика
• при переопределении equals обязательно нужно переопределить hashCode (это даст равенство хеш-кодов для равных обектов, распределено полученное значение будет точно так же, как и исходные данные)
• равные объекты должны возвращать одинаковые хеш-коды
• если переопределить equals без hashCode - классы и методы будут некорректно работать. У объекта HashMap пара, помещенная в Map возможно не будет найдена в ней при обращении, если используется новый экземпляр ключа
• реализация equals должна подчиняться правилам рефлексивности, симметрии, транзитивности и непротиворечивости.
wait
Используется для приостановки выполнения текущего потока до тех пор, пока другой поток не вызовет метод notify
или notifyAll
на том же объекте монитора. Метод wait
вызывается на объекте синхронизации, который должен быть заблокирован (внутри блока synchronized
). Когда поток вызывает wait
, он освобождает монитор объекта и переходит в состояние ожидания.
// Поток блокируется, пока условие не станет истинным.
// После вызова wait поток освобождает монитор и переходит в состояние ожидания.
synchronized(object) {
while (conditionNotMet) {
object.wait();
}
// Продолжение работы после возобновления
}
notify
Используется для пробуждения одного из потоков, ожидающих на том же объекте синхронизации. Этот метод не освобождает монитор объекта, на котором он вызывается; поток, вызвавший notify
, остаётся владельцем монитора до тех пор, пока не завершит блок synchronized
.
// Метод notify пробуждает один из потоков, ожидающих на объекте object, но не освобождает монитор объекта сразу.
synchronized (object) {
// Изменение состояния
object.notify();
}
notifyAll
Пробуждает все потоки, ожидающие на том же объекте синхронизации. Все пробуждённые потоки будут конкурировать за монитор объекта.
// Все потоки, ожидающие на объекте object, будут пробуждены и конкурировать за получение монитора.
synchronized (object) {
// Изменение состояния
object.notifyAll();
}
Class
Статическая загрузка классов
После запуска java-приложения все классы которые приложение создает через new
(те классы которые импортируются директивой import
) будут загружены загрузчиком классов перед запуском приложения.
Динамическая загрузка классов
Возможность загружать классы после старта. После можно создавать экземпляры этого класса через Reflection Api. Вариант является ненадежным с точки зрения целостности программы, так как нужно руками обеспечить попадание этого класса в область в которой его увидит загрузчик классов тогда как при статической загрузке это проверяет компилятор. Однако это дает возможности создавать действительно модульные системы в которых интерфейс и реализация могут быть разделены физически по разным .jar. Классы могут быть загружены и выгружены динамически что позволяет строить гибкие приложения.
Class.forName("path.to.class.ClassName");
Strings
String
Последовательность символов. Все строковые классы — final (от них нельзя унаследоваться). String - immutable 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");
String Pool
Механизм, который позволяет оптимизировать работу с объектами строк, сохраняя их в специальной области памяти, называемой heap. Позволяет уменьшить потребление памяти и улучшить производительность, кэшируя строки и избегая дублирования. Строки в Java неизменяемы (immutable). Это означает, что как только строка создана, её содержимое не может быть изменено. Благодаря этому их можно безопасно кэшировать в пуле.
// Когда строка создается с использованием литералов, например, String s1 = "hello";, JVM проверяет пул на наличие строки "hello".
// Если такая строка уже существует в пуле, переменная s1 будет ссылаться на уже существующий объект. Если нет — строка добавляется в пул.
String s1 = "hello";
String s2 = "hello"; // Будет ссылаться на тот же объект в String Pool
new
Когда строка создается с использованием ключевого слова new, создается новый объект в куче (heap), даже если строка с таким же значением уже существует в пуле строк. Это означает, что объект в куче и объект в строковом пуле не будут ссылаться на один и тот же объект.
String s3 = new String("hello"); // Создаст новый объект в heap
intern
Используется чтобы поместить строку в пул строк или вернуть ссылку на уже существующую строку в пуле. Если строка не найдена в пуле, она добавляется туда, и возвращается ссылка на этот объект.
String s3 = new String("hello");
String s4 = s3.intern(); // Вернёт ссылку на строку из String Pool
Arrays
Используются для хранения нескольких значений в одной переменной вместо объявления отдельных переменных для каждого значения. Доступ по индексу. Имеют фиксированный размер, не изменить после объявления. Лучше по производительности.
String[] people = { "Bob", "Alice", "John" };
int[] numbers = new int[10];
Serialization
Serializable
Стандартный интерфейс Java. Объекты, реализующие этот интерфейс автоматически сериализуются. Под капотом рефлексия, поэтому медленный. Есть режим ручного управления через методы readObject
writeObject
.
transient
Игнорировать поле при сериализации/десериализации.
class Book implements Serializable {
private String name;
private transient String description;
}
Modifiers
static
Модификатор применяемый к полю блоку методу или внутреннему классу. Данный модификатор указывает на привязку субъекта к текущему классу. static
означает что переменная или функция является общей для всех экземпляров этого класса поскольку она принадлежит типу а не самим фактическим объектам. Итак если у вас есть переменная: private static int i = 0; и вы увеличиваете ее (i++) в одном экземпляре изменение будет отражено во всех экземплярах. Если переменная не статическая то у каждого нового объекта данного класса будет своё значение этой переменной меняя которое мы меняем его исключительно в одном объекте.
• статические поля и методы не потокобезопасны (Thread-safe). Это одна из наиболее частых причин возникновения проблем связанных с безопасностью мультипоточного программирования. Учитывая что каждый экземпляр класса имеет одну и ту же копию статической переменной, то такая переменная нуждается в защите — «залочивании» классом. Поэтому при использовании статических переменных, убедитесь, что они должным образом синхронизированы (synchronized), во избежание проблем, например таких как «состояние гонки» (race condition).
• статические методы не переопределяются. всегда вызывается метод родительского класса
В статическом блоке инициализируются статические переменные.
Статический метод может обратиться только к статическим переменным.
Статическим классом может быть только внутренный класс. Данный класс можно наследовать как и он может наследоваться от любого другого класса и имплементировать интерфейс. Статический вложенный класс ничем не отличается от любого другого внутреннего класса за исключением того что его объект не содержит ссылку на создавший его объект внешнего класса. У него есть доступ к приватным статическим переменным внешнего класса.
static class Cat {
static String name = "Barsik";
static {
name = "Oscar";
}
public static void rename() {
name = "Tom";
}
public static class Car {
public int km;
}
}
• Нельзя получить доступ к нестатическим членам класса внутри статического контекста (метода/блока).
public class Counter {
private int count;
public static void main(String args[]) {
System.out.println(count); // compile time error
}
}
final
Запретить наследование для классов. Запретить переопределять методы в подклассах. Сделать переменные неизменяемыми.
final class Cat {
final String name = "Barsik";
final void main() {}
}
public
Поля и методы видны другим классам из текущего пакета и из внешних пакетов.
private
Доступен на уровне класса.
protected
Доступен из любого места в текущем классе или пакете или в производных классах, даже если они находятся в других пакетах.
IO
InputStream
OutputStream
Bite-oriented streams. Байтовые потоки. В случае ошибки - IOException.
Reader
Writer
Character-oriented streams. Символьные потоки. В случае ошибки - IOException.
BufferedInputStream
BufferedOutputStream
& BufferedReader
BufferedWriter
Классы-надстройки наделяют существующий поток дополнительными свойствами. Буферизируют поток и повышают производительность.
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 {}
try
catch
finally
try {
s = reader.readLine();
} catch (IOException e) {
System.out.println(e.getMessage());
} finally {
reader.close();
}
Throwable
Подразделяется на Error
(OutOfMemoryError
StackOwerFlowError
) и Exception
(RuntimeException
IOException
).
Enum
enum
Перечесления. Набор логически связанных констант.
enum Color {
RED("#FF0000"),
GREEN("#00FF00"),
BLUE("#0000FF"), ;
private String code;
Color(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
Generics
Стирание типов
Внутри класса не хранится никакой информации о его типе-параметре. Эта информация доступна только на этапе компиляции и стирается (становится недоступной) в runtime. Если ты попытаешься положить объект не того типа в свой List<String>
, компилятор выдаст ошибку. Этого как раз и добивались создатели языка, создавая дженерики — проверки на этапе компиляции.
Lambda
Анонимный класс.
// Старый способ:
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Кнопка нажата. Старый способ!");
}
});
// Новый способ:
button.addActionListener( (e) -> {
System.out.println("Кнопка нажата. Lambda!");
});
// Старый способ:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
System.out.println(n);
}
// Новый способ:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// Новый способ с использованием оператора двойного двоеточия ::
list.forEach(System.out::println);
Java version match
Java Runtime | Java |
---|---|
49 | Java 5 |
50 | Java 6 |
51 | Java 7 |
52 | Java 8 |
53 | Java 9 |
54 | Java 10 |
55 | Java 11 |
56 | Java 12 |
57 | Java 13 |
58 | Java 14 |
59 | Java 15 |
60 | Java 16 |
61 | Java 17 |
62 | Java 18 |
63 | Java 19 |
64 | Java 20 |
65 | Java 21 |
Java. Вопросы на собесе
- Как в Java генерируется метод equals?
- Расскажи про примитивные и ссылочные типы?
- В чем разница между StringBuffer и StringBuilder?
- Расскажи про проверенные и непроверенные исключения?
- В чем отличие final finally finalize?
- Статическая и динамическая загрузка классов?
- Что такое стирание типов у дженериков?
- Как в Java хранятся строки с одинаковыми значениями?
Строки кэшируются в специальной области памяти, называемой “строковым пулом” (string pool), чтобы минимизировать создание дубликатов и экономить память.
- Основные методы класса Object?
equals()
hashCode()
toString()
notify()
notifyAll()
wait()
clone()
finalize()
- Для чего используются методы equals и hashcode у Object?
Методы
equals()
иhashCode()
используются для сравнения объектов и для оптимизации работы с хэш-структурами, такими какHashMap
иHashSet
.
- Какая реализация у метода hashCode?
Возвращает
Int
. Разная в зависимости от JVM. В общем случае, хэшкод основывается на внутреннем адресе объекта в памяти или на использовании генератора псевдослучайных чисел (алгоритм park miller).
- Какое максимальное значение у 32-битного Int?
2^{31} - 1