Skip to content

Latest commit

 

History

History
2110 lines (1616 loc) · 193 KB

File metadata and controls

2110 lines (1616 loc) · 193 KB

⚜️ Содержание


🌏 Типы данных

🔸 Примитивные и ссылочные типы

Определения

  • Примитивные типы данных - это базовые типы, которые хранят непосредственные значения. Они занимают фиксированный объем памяти и не имеют методов или свойств.

  • Ссылочные типы данных - это типы, которые хранят ссылку на объект в памяти (в куче). Они позволяют работать с более сложными структурами данных, такими как классы, массивы, строки и интерфейсы.

Таблица основных типов

Тип Категория Размер (бит) Диапазон/Описание
byte Примитивный 8 -128 до 127
short Примитивный 16 -32768 до 32767
int Примитивный 32 -2^31 до 2^31-1
long Примитивный 64 -2^63 до 2^63-1
float Примитивный 32 Число с плавающей точкой
double Примитивный 64 Число с плавающей точкой
char Примитивный 16 Символ Unicode
boolean Примитивный - true или false
String Ссылочный - Последовательность символов
Классы Ссылочный - Пользовательские типы
Массивы Ссылочный - Коллекции элементов одного типа
Интерфейсы Ссылочный - Абстрактные типы

Характеристики примитивных и ссылочных типов

Примитивные типы:

  • Занимают фиксированный объем памяти.
  • Не имеют методов и свойств.
  • Копируются по значению: при передаче переменной создается новая копия значения.
  • Пример использования:
int number = 10;
float pi = 3.14f;
char symbol = 'A';
boolean isTrue = true;

Ссылочные типы:

  • Хранят ссылку на объект в куче (heap).
  • Объекты могут быть изменены после создания.
  • Копируются по ссылке: две переменные могут ссылаться на один и тот же объект.
  • Пример использования:
String message = "Hello, World!";  
int[] numbers = {1, 2, 3, 4, 5};
List<String> list = new ArrayList<>();
list.add("Java");

Сравнение примитивных и ссылочных типов

Характеристика Примитивные типы Ссылочные типы
Хранение Значение хранится напрямую Хранится ссылка на объект
Размер Фиксированный Может варьироваться
Методы и свойства Отсутствуют Наличие методов и свойств
Копирование По значению По ссылке
Пример int, float, boolean String, массивы, классы

🔄 К содержанию - главы
🔼 К содержанию


Мутабельные и иммутабельные классы

1. Мутабельные классы

Описание

Мутабельные классы — это классы, объекты которых можно изменять после их создания. То есть состояние объекта может меняться через методы класса.

Преимущества:

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

Недостатки:

  • Может привести к ошибкам, если объект используется несколькими потоками одновременно.
  • Сложнее использовать в многопоточных приложениях.

Пример мутабельного класса:

// Класс StringBuilder является мутабельным  
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World!"); // Изменяет исходный объект
System.out.println(sb); // Вывод: Hello World!

Таблица мутабельных классов в Java

Класс Описание
StringBuilder Класс для создания и изменения строк. Позволяет добавлять, удалять и изменять символы без создания нового объекта.
ArrayList Динамический массив, который позволяет изменять размер коллекции и элементы внутри нее.
HashMap Коллекция, хранящая пары ключ-значение. Ключи и значения могут быть изменены после создания объекта.
HashSet Коллекция, хранящая уникальные элементы. Элементы можно добавлять, удалять или заменять.
LinkedList Двусвязный список, поддерживающий динамическое изменение элементов и их порядка.
Vector Устаревший аналог ArrayList, но с синхронизированными методами. Разрешает изменения содержимого.
Stack Реализация стека на основе Vector. Поддерживает операции добавления и удаления элементов.
Properties Подкласс Hashtable, используемый для работы с набором свойств. Может быть изменен во время выполнения.
GregorianCalendar Класс для представления календарной даты и времени. Позволяет изменять поля даты и времени.
DateFormat Абстрактный класс для форматирования и анализа дат. Некоторые реализации (например, SimpleDateFormat) мутабельны.
AtomicInteger Класс для работы с целыми числами с поддержкой атомарных операций. Его значение может быть изменено.
AtomicLong Аналогично AtomicInteger, но для длинных чисел. Значение объекта может меняться.
ThreadLocalRandom Генератор случайных чисел, который может изменять свое внутреннее состояние при каждом вызове методов.

2. Иммутабельные классы

Описание

Иммутабельные классы — это классы, объекты которых нельзя изменить после их создания. Любое изменение создает новый объект.

Преимущества:

  • Безопасность в многопоточных средах, так как объекты неизменяемы.
  • Упрощение логики программы, так как состояние объекта известно заранее.
  • Защита от несанкционированного изменения данных.

Недостатки:

  • Может потреблять больше памяти, так как при каждом изменении создается новый объект.
  • Меньшая производительность при частых изменениях.

Пример иммутабельного класса:

// Класс String является иммутабельным
String str = "Hello";
str += " World!"; // Создает новый объект
System.out.println(str); // Вывод: Hello World!

Таблица иммутабельных классов в Java

Класс Описание
String Класс для представления неизменяемых строк. Любая операция над строкой создает новый объект.
Integer Обертка над примитивным типом int. Значение объекта не может быть изменено после создания.
Long Обертка над примитивным типом long. Имеет фиксированное значение, которое нельзя изменить.
Double Обертка над примитивным типом double. После инициализации значение остается неизменным.
Float Обертка над примитивным типом float. Является иммутабельной оболочкой для чисел с плавающей точкой.
Character Обертка над примитивным типом char. Значение символа не может быть изменено.
Boolean Обертка над примитивным типом boolean. Состояние объекта (true/false) фиксировано.
BigDecimal Класс для работы с десятичными числами высокой точности. Все операции создают новые объекты.
BigInteger Класс для работы с целыми числами произвольной длины. Неизменяемость гарантирует безопасность данных.
UUID Класс для уникальных идентификаторов. После создания UUID его значение остается постоянным.
Enum Все перечисления (enum) в Java являются иммутабельными по умолчанию.
ImmutableList Коллекция из библиотеки Guava или других фреймворков, которая не позволяет модификацию содержимого.
ImmutableMap Иммутабельная реализация словаря, где пары ключ-значение остаются неизменными после создания.
ImmutableSet Иммутабельная коллекция множеств, которая запрещает добавление, удаление или изменение элементов.

Сравнение мутабельных и иммутабельных классов

Характеристика Мутабельные классы Иммутабельные классы
Изменяемость Объект можно изменять Объект нельзя изменять
Память Более эффективное использование Может потреблять больше памяти
Безопасность потоков Требует дополнительной синхронизации Безопасны для многопоточности
Примеры StringBuilder, ArrayList String, Integer

🔄 К содержанию - главы
🔼 К содержанию


🟡 Ключевые концепции ООП 6️⃣

ООП

Основные принципы ООП:

ООП (Объектно-Ориентированное Программирование) — это подход к программированию, в котором программа строится как набор объектов, каждый из которых имеет свои данные (свойства) и функции (методы). Эти объекты могут взаимодействовать друг с другом, обмениваться информацией и выполнять задачи.

Функциональное программирование
(Функциональное программирование — это стиль программирования, в котором основное внимание уделяется вычислениям через функции, а не изменению состояния или данных. В этом подходе функции являются «первоклассными объектами», то есть их можно передавать как аргументы, возвращать из других функций и использовать без изменения их состояния.
Основные принципы ФП:
Чистые функции — функции, которые всегда возвращают один и тот же результат для одинаковых входных данных и не изменяют состояние программы.
Отсутствие побочных эффектов — функции не изменяют внешние данные или состояние программы (например, глобальные переменные).
Функции высшего порядка — функции, которые могут принимать другие функции в качестве аргументов или возвращать их.

Инкапсуляция - сокрытие реализации.
Инкапсуляция подразумевает то, что мы скрываем какие-то свойства объекта и предоставляем к ним доступ в Java с помощью методов getter и setter, а также модификаторов доступа.
Вы нажимаете на кнопку зажигания, и автомобиль заводится. Вам не нужно знать, как именно работает двигатель внутри (процесс горения топлива, работа цилиндров и т.д.). Вся сложная реализация скрыта, а вы взаимодействуете только с простым интерфейсом — кнопкой.
Реализация: модификаторы доступа + геттеры/сеттеры.

Наследование - создание новой сущности на базе уже существующей.
Наследование. То есть то, что класс может наследовать поведение и поля другого класса родительского.
Все автомобили имеют общие характеристики, такие как колеса, двигатель и руль. На их основе можно создать более специализированные типы:
Спортивный автомобиль (SportsCar) наследует от обычного автомобиля (Car) и добавляет уникальные особенности, например, увеличенную мощность или спортивный режим.
Грузовик (Truck) также наследует от автомобиля, но добавляет возможность перевозить грузы.
Реализация: extends + переопределение.

Полиморфизм - возможность иметь разные формы для одной и той же сущности.
Полиморфизм. Один метод множества реализаций в Java с помощью переопределения и перегрузки.То есть один метод может быть реализован по-разному, в зависимости от того, от кого он вызывается, либо какие параметры в него передаются
У вас есть один пульт с кнопкой "Включить". В зависимости от того, на какое устройство вы его наведете, происходит разное действие:

  • Телевизор → включается экран, запускается программа
  • Кондиционер → запускается compressor, начинается охлаждение
  • Музыкальный центр → включается усилитель, начинается воспроизведение
    Одна команда "включить" - много разных реализаций.
    Реализация:
  • Перегрузка (статический полиморфизм): Несколько методов с одним именем, но разными параметрами в одном классе. Компилятор сам выбирает, какой вызвать.
  • Переопределение (динамический полиморфизм): Потомок изменяет логику метода, унаследованного от родителя. Какой метод будет вызван, решается во время выполнения программы.
  • Реализация: переопределение через @Override и вызов через ссылку на класс-родитель/интерфейс.

Абстракция - набор общих характеристик.
Абстракция. Мы абстрактно выделяем какие-то главные свойства, а неглавные не выделяем.
Автомобиль — это абстрактная концепция, которая объединяет общие свойства всех машин: они имеют колеса, двигатель, руль и могут ехать. Конкретные модели (например, Toyota Camry или Tesla Model 3) добавляют свои уникальные детали, но все они основаны на этой общей абстракции. Таким образом, абстракция позволяет нам думать об автомобиле как о единой сущности, не углубляясь в детали каждой конкретной модели. Реализация: interface/abstract + разная реализация.

Посылка сообщений - форма связи, взаимодействия между сущностями.
Переиспользование- все что перечислено выше работает на повторное использование кода.

1. Объекты и классы

  • Класс: Это шаблон или blueprint, который определяет структуру и поведение объектов. Он описывает, какие данные (свойства) и действия (методы) будут доступны для объектов этого типа.
  • Объект: Это экземпляр класса. Объект представляет собой конкретную сущность, созданную на основе класса. У каждого объекта есть свои уникальные значения свойств, но все они следуют общей структуре, заданной классом.

2. Инкапсуляция

  • Инкапсуляция — это механизм скрытия внутренней реализации объекта и предоставления доступа к его функционалу только через публичный интерфейс.
  • Основные методы реализации инкапсуляции:
    • Модификаторы доступа (public, private, protected, default).
    • Скрытие данных через ограничение прямого доступа к переменным.
    • Методы-геттеры и сеттеры для безопасного управления внутренними данными.

3. Наследование

  • Наследование позволяет одному классу (дочернему) получить все свойства и методы другого класса (родительского).
  • Основные аспекты реализации наследования.
    • Ключевое слово extends (для наследования класса).
    • Повторное использование кода, что упрощает разработку.
    • Расширение функциональности через добавление новых или изменение существующих методов.

4. Полиморфизм

  • Полиморфизм означает "многозначность" и позволяет использовать один интерфейс для различных типов данных.
  • Ключевые моменты
    • Перегрузка методов:
    • Позволяет иметь несколько методов с одним именем, но с разными параметрами.
  • Ключевые моменты:
    • Статический полиморфизм, который определяется на этапе компиляции.
    • Удобство для различных сценариев, например, сложение чисел разных типов.
  • Переопределение методов:
  • Позволяет дочерним классам изменить поведение метода родительского класса.
  • Ключевые моменты:
    • Динамический полиморфизм, который определяется на этапе выполнения.
    • Использование аннотации @Override, чтобы явно указать переопределение.объектов.

5. Абстракция

  • Абстракция — это процесс выделения главных характеристик объекта, игнорируя второстепенные детали. Основные методы реализации абстракции:
    • Абстрактные классы (с возможностью включения как абстрактных, так и конкретных методов).
    • Интерфейсы, которые определяют только сигнатуру методов без их реализации.

Зачем нужны эти концепции?

  • Модульность: Код становится более организованным и разделенным на логические блоки.
  • Расширяемость: Программа легко адаптируется к изменениям требований.
  • Безопасность: Защищает данные и предотвращает несанкционированное изменение внутренней реализации.
  • Читаемость: Упрощает понимание кода благодаря четкой структуре и разделению ответственности.

🔄 К содержанию - главы
🔼 К содержанию


🔹 Переопределение и перегрузка

Переопределение (Override):

  • Определение: Переопределение метода происходит, когда дочерний класс предоставляет свою реализацию метода, который уже определен в родительском классе.
  • Цель: Позволяет изменить поведение метода для конкретного типа данных или класса.
  • Правила:
    • Метод в дочернем классе должен иметь ту же сигнатуру (имя, типы параметров) и уровень доступа, что и в родительском классе.
    • Аннотация @Override используется для явного указания переопределения метода (рекомендуется для избежания ошибок).
  • Пример: Если в родительском классе есть метод display(), то дочерний класс может переопределить его для вывода другой информации.

Перегрузка (Overload):

  • Определение: Перегрузка метода означает создание нескольких методов с одним и тем же именем, но с разными параметрами (количеством, типами или порядком).
  • Цель: Позволяет использовать одинаковое имя метода для выполнения разных операций в зависимости от входных параметров.
  • Правила:
    • Сигнатуры методов должны различаться (разные типы или количество параметров).
    • Перегрузка может происходить как внутри одного класса, так и между родительским и дочерним классами.
  • Пример: Метод calculate(int a, int b) может быть перегружен как calculate(double a, double b).

Сравнительная таблица: Переопределение vs Перегрузка

Критерий Переопределение (Override) Перегрузка (Overload)
Определение Изменение реализации метода в дочернем классе, сохраняя ту же сигнатуру. Создание нескольких методов с одним и тем же именем, но разными параметрами.
Цель Изменить поведение метода для конкретного типа данных или класса. Предоставить разные варианты использования одного имени метода.
Сигнатура метода Должна быть одинаковой (имя и типы параметров). Должна различаться (количество, типы или порядок параметров).
Уровень доступа Уровень доступа в дочернем классе не может быть более строгим. Не влияет на перегрузку.
Аннотация Используется аннотация @Override для явного указания переопределения. Аннотация не требуется.
Место применения Только между родительским и дочерним классами. В пределах одного класса или между классами.
Возвращаемый тип Должен совпадать с типом возвращаемого значения родительского метода или быть его подтипом (covariant return type). Может быть любым, так как это не влияет на перегрузку.
Пример class Parent { void display() {} }
class Child extends Parent { @Override void display() {} }
void calculate(int a, int b) {}
void calculate(double a, double b) {}

🔄 К содержанию - главы
🔼 К содержанию


🔹 Абстрактный класс vs Интерфейс

Абстрактный класс:

  • Определение: Абстрактный класс — это класс, который не предназначен для создания экземпляров напрямую. Он содержит абстрактные методы (без реализации) и/или обычные методы (с реализацией).
  • Использование: Используется, когда нужно предоставить общую базовую функциональность для подклассов, а также задать контракт для обязательной реализации некоторых методов.
  • Особенности:
    • Может содержать как абстрактные, так и конкретные методы.
    • Поддерживает наследование (одиночное наследование).
    • Может иметь состояния (поля), которые могут быть использованы подклассами.
  • Пример: Базовый класс Vehicle может быть абстрактным, а подклассы Car и Truck будут реализовывать специфические методы.

Мы можем создать объект абстрактного класса?
Нет

Интерфейс:

  • Определение: Интерфейс представляет собой полностью абстрактный тип, содержащий только объявления методов (до Java 8) или методы по умолчанию и статические методы (начиная с Java 8).
    Методы по умолчанию:
    Методы по умолчанию (default methods) появились в Java 8. Они позволяют добавлять реализацию методов в интерфейс без необходимости изменять классы, которые уже этот интерфейс реализуют. Такие методы определяются с помощью ключевого слова "default". Это позволяет добавлять новые методы в интерфейсы, сохраняя обратную совместимость.
    Статические методы:
    Статические методы в интерфейсах также появились в Java 8. Эти методы принадлежат самому интерфейсу, а не его экземплярам. Они определяются с использованием ключевого слова "static". Такие методы можно вызывать напрямую через имя интерфейса, и они используются для предоставления утилитарной функциональности, связанной с интерфейсом.

  • Использование: Используется для определения контракта, который должны реализовать классы. Это позволяет обеспечить полиморфизм без наследования.

  • Особенности:

    • Начиная с Java 8, интерфейсы могут содержать методы по умолчанию (default) и статические методы.
    • Класс может реализовывать несколько интерфейсов (множественная "реализация").
    • Не может содержать состояния (поля), за исключением final static переменных.
  • Пример: Интерфейс Runnable определяет метод run(), который должен быть реализован классами для выполнения потока.

Характеристика Абстрактный класс Интерфейс
Наследование Одиночное наследование (extends) Множественная реализация (implements)
Поля/Состояние Может иметь обычные и статические поля Все поля автоматически public static final (константы), даже если модификаторы не указаны явно
Методы Может иметь поля любого типа: обычные, статические, final, с любыми модификаторами доступа abstract, default, static, private
Конструкторы Может иметь конструкторы Не может иметь конструкторов
Когда использовать Когда требуется частичная реализация с возможностью переопределения в наследниках Когда нужен только контракт (спецификация поведения)
Применение Основные классы и структуры, требующие общего функционала API, плагины, колбэки, стратегии (где важна гибкость и совместимость)
Связанность Сильная связанность (наследование) Слабая связанность (разные реализации).

🔄 К содержанию - главы
🔼 К содержанию


🔵 Модификаторы и управление доступом

🔸 Модификаторы доступа

Модификаторы доступа в Java определяют, какой код может получить доступ к классам, методам, полям или конструкторам.

Модификатор доступа Уровень доступа
private Видно только внутри класса.
default Видно только внутри пакета.
protected Видно внутри класса + у наследников (extends).
public Видно всем, даже тебе🤭
  1. private:

    • Доступен только внутри того же класса.
    • Используется для скрытия данных (инкапсуляция).
  2. default (без модификатора):

    • Доступен только внутри пакета.
    • Не требует явного указания.
  3. protected:

    • Доступен внутри пакета и во всех подклассах вне пакета.
    • Часто используется для методов, которые должны быть доступны дочерним классам.
  4. public:

    • Доступен из любого места в программе.
    • Используется для открытых API и основных точек входа.

🔄 К содержанию - главы
🔼 К содержанию


🔸 Ключевые слова

final

  • На уровне класса
    Класс не может быть унаследован (запрещает наследование).

  • На уровне метода
    Метод нельзя переопределить в подклассах.

  • На уровне переменной
    Значение переменной нельзя изменить после инициализации (константа или неизменяемая ссылка).

  • На уровне параметра метода
    Значение параметра нельзя изменить внутри метода.

static

  • На уровне класса
    Поля и методы принадлежат самому классу, а не объектам.
    Можно обращаться без создания экземпляра.

  • На уровне метода
    Метод может вызываться без объекта.
    Внутри метода нельзя использовать this и super.

  • На уровне переменной
    Переменная общая для всех объектов класса (одно значение на весь класс).

  • На уровне вложенного класса
    Статический вложенный класс не связан с экземпляром внешнего класса.

super

  • На уровне конструктора
    Вызывает конструктор родительского класса.

  • На уровне метода
    Обращение к методу родительского класса (даже если он переопределён).

  • На уровне поля
    Доступ к полю родительского класса, если оно перекрыто в наследнике.

🔄 К содержанию - главы
🔼 К содержанию


🔸 Final/finally/finalize

Ключевое слово Описание
final - Класс: Запрещает наследование от этого класса.
- Метод: Запрещает переопределение метода в подклассах.
- Переменная: Значение переменной становится неизменяемым после инициализации.
finally Блок finally используется вместе с блоком try-catch для выполнения важного кода, который должен выполняться независимо от того, произошло исключение или нет. Пример: закрытие файлов, соединений с базами данных.
finalize Метод finalize() вызывается перед сборкой мусора для выполнения очистки ресурсов. Не рекомендуется использовать, так как его поведение не гарантировано, и он был объявлен устаревшим начиная с Java 9.

final:

  • Класс: Запрещает наследование от этого класса.
  • Метод: Запрещает переопределение метода в подклассах.
  • Переменная: Значение переменной становится неизменяемым после инициализации.

finally:

  • Блок finally используется вместе с блоком try-catch для выполнения важного кода, который должен выполняться независимо от того, произошло исключение или нет.
  • Пример: закрытие файлов, соединений с базами данных.

finalize (устаревший):

  • Метод finalize() вызывается перед сборкой мусора для выполнения очистки ресурсов.
  • Не рекомендуется использовать, так как его поведение не гарантировано, и он был объявлен устаревшим начиная с Java 9.

🔸 Final: ограничения

Использование ключевого слова final накладывает следующие ограничения:

  1. Final классы:

    • Нельзя создать подклассы от final класса.
    • Полезно для классов, которые предоставляют финальную реализацию и не должны изменяться.
  2. Final методы:

    • Нельзя переопределить final метод в подклассах.
    • Полезно для методов, которые обеспечивают критически важную функциональность и не должны быть изменены.
  3. Final переменные:

    • Значение final переменной должно быть присвоено один раз и не может быть изменено.
    • Для примитивных типов это означает фиксированное значение.
    • Для ссылочных типов это означает, что ссылка не может быть изменена, но содержимое объекта может меняться, если сам объект не является неизменяемым.

🔄 К содержанию - главы
🔼 К содержанию


Ключевое слово Описание
static Обозначает, что переменная, метод или блок принадлежит классу, а не экземпляру. Это позволяет обращаться к ним без создания объекта.
final Используется для обозначения констант (неизменяемых значений), окончательных классов (которые нельзя наследовать) или методов (которые нельзя переопределить).
abstract Помечает класс или метод как абстрактный. Абстрактный класс не может быть создан напрямую, а абстрактный метод должен быть реализован в подклассах.
private Указывает, что доступ к методу или переменной возможен только внутри того же класса. Это ограничивает видимость элементов класса.
protected Разрешает доступ к методу или переменной внутри того же пакета и всем подклассам вне пакета.
public Делает метод или переменную доступными из любого места программы. Это наиболее открытый уровень доступа.
this Ссылается на текущий экземпляр класса. Используется для доступа к переменным или методам того же объекта.
super Ссылается на родительский класс. Используется для вызова методов или доступа к переменным родителя.
synchronized Используется для обеспечения потокобезопасности. Метод или блок кода помеченный этим словом может выполняться только одним потоком одновременно.
transient Указывает, что поле не должно сериализоваться при сохранении состояния объекта.
volatile Обозначает, что значение переменной может изменяться разными потоками, и все изменения должны быть видны другим потокам без дополнительной синхронизации.
native Помечает метод, который реализован на другом языке программирования (например, C или C++).
strictfp Гарантирует точность вычислений с плавающей запятой, заставляя JVM использовать строгие правила IEEE 754.
interface Обозначает спецификацию поведения, которую должны реализовать классы. Интерфейс определяет контракт без конкретной реализации.
enum Используется для создания ограниченного набора констант. Например, дни недели или цвета.
package Группирует связанные классы и интерфейсы в одну единицу. Это помогает организовать код и избежать конфликтов имен.
import Позволяет использовать классы из других пакетов без необходимости указывать полное имя класса.
throws Указывает, что метод может выбросить исключение. Это предупреждает вызывающий код о возможных проблемах.
try Используется для блока кода, где может возникнуть исключение.
catch Обрабатывает исключения, возникающие в блоке try.
finally Выполняется всегда после блока try-catch, независимо от того, было ли выброшено исключение.
throw Прерывает выполнение метода и передает управление обработчику исключений.
assert Проверяет условие во время выполнения программы. Если условие ложно, выбрасывается ошибка AssertionError.

🔸 Static: применение

Ключевое слово static используется для создания членов класса, которые принадлежат самому классу, а не его экземплярам.

  1. Static переменные:

    • Общие для всех экземпляров класса.
    • Хранят одно значение для всего класса.
    • Пример: счетчик количества созданных объектов.
  2. Static методы:

    • Может быть вызван без создания экземпляра класса.
    • Не имеет доступа к нестатическим полям и методам, так как они принадлежат конкретным экземплярам.
    • Пример: методы класса Math, такие как Math.sqrt().
  3. Static блоки:

    • Выполняются один раз при загрузке класса.
    • Используются для инициализации статических переменных.
  4. Static вложенные классы:

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

🔄 К содержанию - главы
🔼 К содержанию


🟠 Операторы и циклы

🔹 Условные операторы

Условные операторы используются для выполнения различных блоков кода в зависимости от условия.

  1. if:

    • Выполняет блок кода, если условие истинно.
    • Синтаксис:
      if (условие) {
          // Код, который выполняется, если условие истинно    
      }
  2. if-else:

    • Выполняет один блок кода, если условие истинно, и другой блок, если ложно.
    • Синтаксис:
      if (условие) {
          // Код, который выполняется, если условие истинно
      } else {
          // Код, который выполняется, если условие ложно
      }
  3. if-else if-else:

    • Позволяет проверить несколько условий последовательно.
    • Синтаксис:
      if (условие1) {
          // Код для условия1
      } else if (условие2) {
          // Код для условия2
      } else {
          // Код для остальных случаев
      }
  4. switch:

    • Используется для выбора одного из нескольких вариантов на основе значения выражения.
    • Синтаксис:
      switch (выражение) {
          case значение1:
              // Код для значения1  
              break;
          case значение2:
              // Код для значения2
              break;
          default: // Блок default выполняется, если значение выражения не совпало ни с одним из case.
              // Код для остальных случаев
      }

🔄 К содержанию - главы
🔼 К содержанию


🔹 Автоупаковка и распаковка

  1. Автоупаковка (Autoboxing):

    • Процесс автоматического преобразования примитивных типов данных (например, int, double) в их обертки (например, Integer, Double).
    • Пример:
      int primitive = 10;
      Integer wrapper = primitive; // Автоупаковка
  2. Распаковка (Unboxing):

    • Процесс автоматического преобразования оберток обратно в примитивные типы.
    • Пример:
      Integer wrapper = 10;
      int primitive = wrapper; // Распаковка
  3. Преимущества:

    • Упрощает работу с коллекциями, так как они принимают только объекты, а не примитивные типы.
    • Сокращает необходимость ручного преобразования между примитивами и их обертками.
  4. Ограничения:

    • Может привести к непредвиденным ошибкам при работе с null значениями, например:
      Integer value = null;
      int result = value; // NullPointerException

🔄 К содержанию - главы
🔼 К содержанию


🔹 Циклы

Циклы используются для повторного выполнения блока кода несколько раз.

  1. for:

    • Используется, когда известно количество итераций.
    • Синтаксис:
      for (инициализация; условие; обновление) {
          // Код для выполнения
      }
    • Пример:
      for (int i = 0; i < 5; i++) {
          System.out.println(i);
      }
  2. while:

    • Выполняет блок кода, пока условие истинно.
    • Синтаксис:
      while (условие) {
          // Код для выполнения
      }
    • Пример:
      int i = 0;
      while (i < 5) {
          System.out.println(i);  
          i++;
      }
  3. do-while:

    • Похож на while, но гарантирует выполнение блока кода хотя бы один раз.
    • Синтаксис:
      do {
          // Код для выполнения
      } while (условие);
    • Пример:
      int i = 0;
      do {
          System.out.println(i);
          i++;
      } while (i < 5);
  4. for-each:

    • Упрощенный вариант цикла for для перебора элементов коллекций или массивов.
    • Синтаксис:
      for (тип элемента : коллекция) {
          // Код для выполнения
      }
    • Пример:
      int[] numbers = {1, 2, 3, 4, 5};
      for (int number : numbers) {
          System.out.println(number);
      }

🔄 К содержанию - главы
🔼 К содержанию


❌ Работа с исключениями

🔸 Что такое исключения

Исключение (Exception) — это как светофор, который резко переключился на красный, когда ты едешь по программе.

  • Это непредвиденная проблема или ошибка, которая случается, пока программа работает.
  • Например:
    • Ты просишь программу открыть файл, а его нет.
    • Ты пытаешься поделить число на ноль (математика этого не позволяет).
    • Программа пытается подключиться к Интернету, а связь оборвалась.

Когда такое происходит, программа прерывает свою обычную работу и кричит (выбрасывает исключение): "Эй! У меня тут проблема!"

Зачем мы используем исключения?

Используем исключения, чтобы программа была непотопляемой и вежливой.

  1. Программа не "умирает" (Непрерывность):

    • Если ошибка происходит, и ты её не обрабатываешь, программа просто вылетает (аварийно завершается).
    • Исключения позволяют программе поймать проблему, как ловит сеть, и не упасть.
  2. Вежливое сообщение (Обратная связь):

    • Вместо того чтобы просто погаснуть, программа может вежливо сообщить пользователю: "Извините, вы ввели не число" или "Не удалось сохранить данные, попробуйте позже".
  3. Разделение ответственности (Чистота кода):

    • Ты пишешь "нормальный" код, который делает свою работу (например, загружает данные).
    • Отдельно ты пишешь код, который включается только если что-то пошло не так (например, если загрузить данные не удалось). Это делает основной план действий очень чистым и понятным.

Итог: Исключения — это план "Б" для твоей программы. Они позволяют предвидеть неприятности и подготовить для них спасательные круги, чтобы программа могла продолжить работу или корректно завершиться.

🔄 К содержанию - главы
🔼 К содержанию


🔸 Иерархия исключений

В Java все исключения организованы в иерархическую структуру, которая начинается с класса Throwable. От него наследуются два основных подкласса: Error и Exception.

Описание изображения

  1. Throwable:

    • Корневой класс для всех ошибок и исключений.
    • Позволяет обрабатывать как программные ошибки (Exception), так и системные (Error).
  2. Error:

    • Представляет серьезные проблемы, которые обычно невозможно обработать программой.
    • Примеры:
      • OutOfMemoryError: Возникает, когда JVM не может выделить больше памяти.
      • StackOverflowError: Происходит при переполнении стека вызовов, обычно из-за бесконечной рекурсии.
      • VirtualMachineError: Указывает на серьезные сбои в виртуальной машине Java.
      • NoClassDefFoundError: JVM не может найти определенный класс.
      • UnsatisfiedLinkError: Ошибка связывания нативных библиотек.
  3. Exception:

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

    Checked Exceptions:

    • Исключения, которые обязательно должны быть обработаны или объявлены в сигнатуре метода.
    • Примеры:
      • IOException: Ошибка ввода-вывода, например, при чтении файла.
      • SQLException: Ошибка при работе с базами данных.
      • FileNotFoundException: Файл не найден.
      • ClassNotFoundException: Класс не найден.
      • ParseException: Ошибка анализа данных, например, при разборе даты.
      • InterruptedException: Поток был прерван.

    Unchecked Exceptions (Runtime Exceptions):

    • Исключения, которые не требуют обязательной обработки.
    • Примеры:
      • NullPointerException: Попытка обращения к объекту через null.
      • ArrayIndexOutOfBoundsException: Доступ к несуществующему индексу массива.
      • ArithmeticException: Ошибка математической операции, например, деление на ноль.
      • ClassCastException: Неправильное приведение типов.
      • IllegalArgumentException: Некорректный аргумент метода.
      • NumberFormatException: Ошибка преобразования строки в число.
      • IllegalStateException: Неверное состояние объекта для вызова метода.

Ключевые различия между Error и Exception:

  • Error: Серьезные системные сбои, обработка обычно невозможна. Например, нехватка памяти.
  • Exception: Ошибки программы, которые можно и нужно обрабатывать. Например, неправильный ввод пользователя.

🔄 К содержанию - главы
🔼 К содержанию


🔸 Обработка исключений

Обработка исключений в Java осуществляется с помощью блоков try-catch-finally и оператора throw/throws.

  1. try:

    • Блок кода, который может выбросить исключение.
    • Синтаксис:
      try {
          // Код, который может выбросить исключение
      }
  2. catch:

    • Блок для обработки конкретного типа исключения.
    • Можно использовать несколько блоков catch для разных типов исключений.
    • Синтаксис:
      catch (ExceptionType e) {
          // Обработка исключения
      }
  3. finally:

    • Блок, который выполняется всегда, независимо от того, было ли выброшено исключение.
    • Используется для освобождения ресурсов (например, закрытие файлов, соединений с базами данных).
    • Синтаксис:
      finally {
          // Код, который выполняется всегда
      }
  4. throw:

    • Оператор для явного выброса исключения.
    • Синтаксис:
      throw new ExceptionType("Сообщение об ошибке");
  5. throws:

    • Ключевое слово для указания, что метод может выбросить определенное исключение.
    • Синтаксис:
      public void someMethod() throws ExceptionType {
          // Код метода
      }
  6. Пример полной конструкции:

    try {
        // Код, который может выбросить исключение
    } catch (SpecificException e) {
        // Обработка конкретного исключения
    } catch (AnotherException e) {
        // Обработка другого исключения
    } finally {
        // Код, который выполняется всегда
    }
  7. Автоматическое управление ресурсами (try-with-resources):
    Для использования механизма try-with-resources в Java необходимо, чтобы объект, который вы хотите использовать в блоке try, реализовывал интерфейс AutoCloseable
    Введено в Java 7 для автоматического закрытия ресурсов, таких как файлы или соединения с базами данных. Синтаксис:

    try (Resource resource = new Resource()) {
     // Код, который использует ресурс
    } catch (Exception e) {
     // Обработка исключения
    }
Элемент / Ключевое слово Назначение (Что делает?) Место использования (Где используется?) Главная задача
try Определяет опасную зону — блок кода, который может вызвать ошибку. Вокруг потенциально проблемного кода. Изолировать код, где может произойти исключение, для последующей его обработки.
catch Перехватывает и обрабатывает конкретный тип исключения, если оно было выброшено в блоке try. Сразу после блока try (может быть несколько). Реализовать логику восстановления, оповещения или записи ошибки.
finally Определяет код, который обязательно выполнится в конце, независимо от того, было исключение или нет. После try и всех catch блоков. Освобождение/закрытие ресурсов (файлов, соединений) для предотвращения утечек.
throw Явно (принудительно) выбрасывает новое или существующее исключение из текущего места выполнения. Внутри тела метода (как обычная инструкция). Создать и отправить исключение, когда обнаружено недопустимое состояние.
throws Декларирует (предупреждает) в сигнатуре метода, что этот метод может выбросить одно или несколько указанных проверяемых исключений. В заголовке метода. Обязать вызывающий код либо обработать это исключение, либо пробросить его дальше.
try-with-resources Автоматически и безопасно закрывает ресурсы, объявленные в скобках try, после завершения работы. Вместо обычного try при работе с ресурсами, реализующими AutoCloseable. Обеспечить гарантированное освобождение ресурсов, делая ненужным явный блок finally для закрытия.

🔄 К содержанию - главы
🔼 К содержанию


🟤 Современные API

🔹 Stream API 1️⃣2️⃣

Теоретическое описание

Stream API — это инструмент, введенный в Java 8, который позволяет обрабатывать коллекции данных в функциональном стиле. Он представляет собой последовательность элементов, над которыми можно выполнять различные операции, такие как фильтрация, преобразование и агрегирование. Stream API делает работу с данными более эффективной и компактной, позволяя писать код в декларативном стиле.

Основная идея Stream API заключается в том, чтобы отделить логику обработки данных от их хранения. Потоки не хранят данные, они лишь предоставляют возможность их обработки.


Основные концепции

  1. Поток (Stream)
    Поток — это последовательность элементов, которая поддерживает операции обработки. Потоки создаются на основе существующих коллекций или массивов и не изменяют исходные данные.

  2. Операции над потоками

    • Промежуточные операции: Возвращают новый поток и могут быть вызваны друг с другом. Примеры: filter, map, sorted.
    • Терминальные операции: Завершают цепочку операций и производят результат или побочный эффект. Примеры: collect, forEach, reduce.
  3. Ленивое выполнение
    Промежуточные операции выполняются лениво, то есть фактическая обработка начинается только при вызове терминальной операции.

  4. Параллельная обработка
    Stream API поддерживает параллельную обработку данных через метод parallelStream(), что может значительно ускорить вычисления для больших наборов данных.


Преимущества Stream API

  • Функциональный стиль программирования: Упрощает сложные операции над данными, делая код более читаемым и понятным.
  • Ленивое выполнение: Оптимизирует производительность за счет отложенного выполнения промежуточных операций.
  • Параллельная обработка: Обеспечивает возможность использования нескольких потоков для обработки данных.
  • Компактность: Сокращает объем кода по сравнению с традиционными циклами и условными конструкциями.

Таблица основных операций Stream API

Тип операции Методы Описание
Промежуточные filter Отфильтровывает элементы, соответствующие заданному условию.
map Преобразует каждый элемент потока с помощью заданной функции.
flatMap Преобразует каждый элемент потока в поток и объединяет все потоки.
peek Отладочная операция — позволяет "заглянуть" в элементы, не модифицируя их.
sorted Сортирует элементы потока.
distinct Удаляет дубликаты из потока.
limit Ограничивает количество элементов в потоке.
skip Пропускает указанное количество элементов в начале потока.
takeWhile Берёт элементы, пока условие истинно.
dropWhile Пропускает элементы, пока условие истинно, затем берёт оставшиеся.
Терминальные forEach Выполняет действие для каждого элемента потока.
forEachOrdered Выполняет действие с сохранением порядка (для параллельных потоков).
collect Собирает результаты в коллекцию или другую структуру данных.
reduce Агрегирует элементы потока в одно значение (например, сумма или максимум).
count Возвращает количество элементов в потоке.
anyMatch, allMatch, noneMatch Проверяет, удовлетворяют ли элементы заданному условию.
findFirst, findAny Находит первый или любой элемент, удовлетворяющий условию.
toArray Преобразует поток в массив.
min, max Находит минимальный или максимальный элемент по заданному компаратору.

  1. Пример использования:

    List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); 
    names.stream()
         .filter(name -> name.startsWith("A"))
         .map(String::toUpperCase)
         .forEach(System.out::println);
  2. Преимущества:

    • Упрощает работу с коллекциями.
    • Поддерживает параллельную обработку данных (parallelStream).
    • Позволяет писать более читаемый и компактный код.

🔄 К содержанию - главы
🔼 К содержанию


🔹 Лямбда-выражения9️⃣

Теоретическое описание

Лямбда-выражения — это механизм, позволяющий представлять анонимные функции в Java. Они были введены в язык программирования начиная с версии 8 и служат для передачи логики как параметра без необходимости создавать полноценные классы или анонимные внутренние классы.

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

Основная цель лямбда-выражений — упростить код, сделать его более компактным и читаемым, особенно при работе с функциональными интерфейсами и API потоков (Stream API).

Ключевые характеристики

  1. Функциональные интерфейсы
    Лямбда-выражения могут использоваться только с функциональными интерфейсами — интерфейсами, которые содержат ровно один абстрактный метод. Например, Runnable, Callable, Predicate, Consumer и другие.

  2. Типизация
    Лямбда-выражения являются строго типизированными. Тип лямбды определяется контекстом, в котором она используется, обычно это функциональный интерфейс.

  3. Синтаксис
    Структура лямбда-выражения состоит из списка параметров, разделенных запятой, стрелки -> и тела выражения. Если тело выражения состоит из одного оператора, фигурные скобки можно опустить.

  4. Захват переменных
    Лямбда-выражения могут использовать переменные из окружающего контекста (локальные переменные или поля класса). Однако локальные переменные должны быть effectively final (не изменяться после инициализации).

  5. Применение

    • Лямбда-выражения широко используются для реализации функциональных интерфейсов.
    • Они значительно упрощают работу с коллекциями через Stream API, позволяя выполнять операции фильтрации, преобразования и агрегирования данных.
    • Сокращают необходимость создания анонимных классов, что делает код более лаконичным.

Преимущества лямбда-выражений

  • Компактность: Лямбда-выражения позволяют записывать код короче, чем использование анонимных классов.
  • Читаемость: Благодаря простому синтаксису, код становится легче восприниматься.
  • Функциональный стиль программирования: Поддержка императивного подхода к решению задач через функциональные интерфейсы и Stream API.

Таблица основных понятий

Термин Описание
Функциональный интерфейс Интерфейс с одним абстрактным методом, который может быть представлен лямбдой.
Effectively final Переменная, которая не явно объявлена как final, но фактически не изменяется.
Stream API API для обработки коллекций данных в функциональном стиле.
Захват переменных Возможность использования внешних переменных внутри лямбда-выражения.
Анонимные классы Предшественники лямбд для реализации одноразовых объектов.

Пример с коллекциями:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5)
numbersforEach(n -> Systemoutprintln(n * 2))

🔄 К содержанию - главы
🔼 К содержанию


🔹 Функциональные интерфейсы

Что такое функциональный интерфейс?

Функциональный интерфейс — это специальный тип интерфейса в Java, который содержит ровно один абстрактный метод. Он служит основой для работы с лямбда-выражениями и методами ссылок, которые были добавлены в Java 8. Благодаря функциональным интерфейсам можно передавать логику (кусок кода) как параметр, что делает код более гибким и компактным.


Зачем нужны функциональные интерфейсы?

Функциональные интерфейсы позволяют:

  1. Упростить код: Вместо создания целых классов или анонимных классов для реализации интерфейсов, можно использовать короткие лямбда-выражения.
  2. Стандартизировать операции: Они предоставляют готовые шаблоны для распространенных типов операций, таких как проверка условий, преобразование данных или выполнение действий.

Основные стандартные функциональные интерфейсы

В Java есть множество встроенных функциональных интерфейсов, каждый из которых решает определенную задачу:

Интерфейс Назначение Пример использования
Predicate<T> Проверяет условие и возвращает true или false. Проверка, является ли число четным: n -> n % 2 == 0
Function<T, R> Преобразует значение одного типа в другой. Преобразование строки в число: str -> Integer.parseInt(str)
Consumer<T> Выполняет действие с переданным параметром и ничего не возвращает. Вывод текста в консоль: text -> System.out.println(text)
Supplier<T> Возвращает значение без параметров. Получение текущей даты: () -> new Date()
BiFunction<T, U, R> Принимает два параметра разных типов и возвращает результат. Сложение двух чисел: (a, b) -> a + b
UnaryOperator<T> Принимает и возвращает значение одного типа (специализированный Function). Увеличение числа на 1: n -> n + 1

Как работают функциональные интерфейсы?

Когда вы используете лямбда-выражение, Java автоматически связывает его с соответствующим функциональным интерфейсом. Например:

  • Если ваше лямбда-выражение представляет собой проверку условия (n -> n > 0), оно будет связано с интерфейсом Predicate.
  • Если лямбда выполняет преобразование (str -> str.toUpperCase()), она будет связана с интерфейсом Function.

Преимущества функциональных интерфейсов

  1. Упрощение кода: Лямбда-выражения делают код короче и понятнее.
  2. Гибкость: Можно легко передавать логику между методами и классами.
  3. Стандартные контракты: Использование стандартных интерфейсов уменьшает необходимость создавать свои собственные.

🔄 К содержанию - главы 🔼 К содержанию


🗂️Коллекции

🔸 Java Collections и Map 7️⃣

Описание изображения

Таблица иерархии коллекций в Java

Интерфейс/Класс Описание Реализации / Примечания
Collection Корневой интерфейс для всех коллекций (кроме Map). Включает методы общего назначения: add(), remove(), size().
List Упорядоченная коллекция с возможностью дублирования элементов.
- ArrayList Базируется на массиве, быстрый доступ по индексу. Не потокобезопасен.
- LinkedList Базируется на двусвязном списке, эффективен для вставки/удаления. Поддерживает работу как Deque.
- Vector Синхронизированная версия ArrayList. Устаревшая, менее эффективна.
- Stack Наследник Vector, реализует стек (LIFO). Устаревшая, рекомендуется использовать Deque.
Set Коллекция без дубликатов, не гарантирует порядок хранения.
- HashSet Базируется на хеш-таблице, не сохраняет порядок. Потоки могут вызывать неопределённое поведение.
- LinkedHashSet Сохраняет порядок вставки элементов. Использует связанный список внутри HashSet.
- TreeSet Автоматически сортирует элементы по их натуральному порядку или компаратору. Реализовано на основе красно-черного дерева.
Queue Коллекция для очередей, обычно работает по принципу FIFO (First In, First Out).
- PriorityQueue Хранит элементы в порядке их приоритета. Не поддерживает null и не гарантирует потокобезопасность.
- LinkedList Может использоваться как очередь. Поддерживает работу как Deque.
Deque Двусторонняя очередь, позволяет добавление/удаление с обоих концов.
- ArrayDeque Более эффективная реализация, основанная на массиве. Рекомендуется вместо LinkedList.
- LinkedList Также может использоваться как Deque. Менее эффективен, чем ArrayDeque.
Map Отображение ключ-значение, где ключи уникальны.
- HashMap Базируется на хеш-таблице, не гарантирует порядок. Не потокобезопасен.
- LinkedHashMap Сохраняет порядок вставки пар ключ-значение. Использует связанный список внутри HashMap.
- TreeMap Автоматически сортирует ключи по их натуральному порядку или компаратору. Реализовано на основе красно-чёрного дерева.
- EnumMap Специализированный Map, где ключи ограничены набором значений enum. Очень эффективен для работы с перечислениями.
Сортированные коллекции Расширения для автоматической сортировки.
- SortedSet Расширение Set, автоматически сортирует элементы. Реализация: TreeSet.
- NavigableSet Расширение SortedSet, предоставляет дополнительные методы навигации. Реализация: TreeSet.
- SortedMap Расширение Map, автоматически сортирует ключи. Реализация: TreeMap.
- NavigableMap Расширение SortedMap, предоставляет дополнительные методы навигации. Реализация: TreeMap.
Потокобезопасные коллекции Коллекции, безопасные для работы в многопоточной среде.
- Vector Синхронизированная версия ArrayList. Устаревшая, менее эффективна.
- Stack Синхронизированная версия стека. Устаревшая, рекомендуется использовать Deque.
- ConcurrentHashMap Потокобезопасная реализация Map, более эффективная, чем синхронизация. Используется в многопоточных приложениях.
Критерий ArrayList LinkedList
Структура данных Динамический массив Двусвязный список
Доступ по индексу (get) ✅ O(1) - очень быстрый ❌ O(n) - медленный
Добавление в начало (addFirst) ❌ O(n) - медленно ✅ O(1) - очень быстро
Добавление в конец (addLast) ✅ O(1) - быстро ✅ O(1) - быстро
Удаление из начала (removeFirst) ❌ O(n) - медленно ✅ O(1) - очень быстро
Удаление из конца (removeLast) ✅ O(1) - быстро ✅ O(1) - быстро
Использование памяти ✅ Экономичнее ❌ Занимает больше
Производительность ✅ Лучше в 95% случаев ❌ Хуже в большинстве сценариев
Когда использовать Для чтения и работы с данными Для частых изменений в начале списка

Java Collections Framework предоставляет набор интерфейсов и классов для работы с коллекциями данных

Основные интерфейсы:

  • Collection: Базовый интерфейс для всех коллекций

  • List: Упорядоченная коллекция с возможностью дублирования элементов

    List<String> list = new ArrayList<>
  • Set: Коллекция без дубликатов

    Set<String> set = new HashSet<>
  • Queue: Коллекция для очередей (FIFO)

    Queue<String> queue = new LinkedList<>

Map:

Представляет набор ключ-значение

Основные реализации:

  • HashMap: Хэш-таблица без гарантии порядка

    Map<String, Integer> map = new HashMap<>
  • LinkedHashMap: Сохраняет порядок вставки

    Map<String, Integer> linkedMap = new LinkedHashMap<>
  • TreeMap: Отсортированная по ключам коллекция

    Map<String, Integer> treeMap = new TreeMap<>

Преимущества:

  • Единый интерфейс для различных типов коллекций
  • Гибкость выбора реализации в зависимости от задачи
  • Поддержка потокобезопасных коллекций через Collections.synchronizedXXX()

🔄 К содержанию - главы
🔼 К содержанию


🔸 Методы для работы с коллекциями

Коллекция Метод Описание
List get(int index) Получает элемент по индексу
List set(int index, E element) Заменяет элемент по индексу
List indexOf(Object o) Возвращает индекс первого вхождения
List lastIndexOf(Object o) Возвращает индекс последнего вхождения
List subList(int from, int to) Возвращает подсписок
ArrayList ensureCapacity(int minCap) Увеличивает ёмкость списка
LinkedList addFirst(E e) Добавляет элемент в начало
LinkedList addLast(E e) Добавляет элемент в конец
LinkedList getFirst() Получает первый элемент
LinkedList getLast() Получает последний элемент
Set add(E e) Добавляет элемент (если его нет)
Set remove(Object o) Удаляет элемент
Set contains(Object o) Проверяет наличие элемента
HashSet clone() Создаёт копию множества
TreeSet first() Первый (наименьший) элемент
TreeSet last() Последний (наибольший) элемент
TreeSet higher(E e) Элемент строго больше заданного
TreeSet lower(E e) Элемент строго меньше заданного
Map put(K key, V value) Добавляет пару ключ-значение
Map get(Object key) Получает значение по ключу
Map remove(Object key) Удаляет пару по ключу
Map containsKey(Object key) Проверяет наличие ключа
Map containsValue(Object value) Проверяет наличие значения
Map keySet() Возвращает множество ключей
Map values() Возвращает коллекцию значений
Map entrySet() Возвращает пары ключ-значение
HashMap compute(K key, BiFunction) Обновляет значение по ключу
TreeMap firstEntry() Первая пара ключ-значение
TreeMap lastEntry() Последняя пара ключ-значение
Queue offer(E e) Добавляет элемент (не выбрасывает исключение)
Queue poll() Удаляет и возвращает голову очереди
Queue peek() Возвращает голову очереди без удаления
Deque push(E e) Добавляет элемент в начало
Deque pop() Удаляет и возвращает элемент из начала
Deque descendingIterator() Итератор в обратном порядке

🔄 К содержанию - главы
🔼 К содержанию


⭕ Глубокие концепции Java

🔹 Коллизия

Коллизия в программировании — это ситуация, когда два или более элемента данных сталкиваются, нарушая ожидаемое поведение системы. Чаще всего коллизии рассматриваются в контексте хэш-функций.

Пример: В хэш-таблицах коллизия возникает, если два ключа имеют одинаковый хэш-значение и попадают в одно и то же "ведро" (bucket).

Методы разрешения коллизий:

  • Цепочки (Chaining): Каждый bucket содержит список элементов, и при коллизии новый элемент добавляется в этот список.
  • Открытая адресация (Open Addressing): Найдется другое свободное место для хранения элемента.
  • Сбалансированные хэш-функции: Минимизация вероятности коллизий через улучшенные алгоритмы хэширования.

🔄 К содержанию - главы
🔼 К содержанию


🔹 Рефлексия

Рефлексия — это механизм, с помощью которого программа может анализировать и изменять свое поведение во время выполнения.

Возможности рефлексии:

  • Извлечение информации о классах, методах, конструкторах, полях и аннотациях.
  • Динамическое создание объектов и вызов методов.
  • Работа с приватными полями и методами (с нарушением инкапсуляции).

Недостатки рефлексии:

  • Снижение производительности.
  • Нарушение принципов инкапсуляции.
  • Усложнение отладки.

Пример использования: Рефлексия активно применяется в фреймворках (например, Spring) для создания динамического поведения.

Рефлексия в Java реализуется через пакет java.lang.reflect и класс Class, который является центральным элементом механизма рефлексии.

🔧 Основные компоненты рефлексии

Компонент Назначение
Class Представляет метаинформацию о классе. Позволяет получить доступ к методам, полям, конструкторам и аннотациям.
Method Представляет метод класса. Позволяет вызывать его динамически.
Field Представляет поле класса. Позволяет читать и изменять его значение.
Constructor Представляет конструктор класса. Позволяет создавать экземпляры.
Modifier Предоставляет информацию о модификаторах доступа (public, private и т.д.).
Annotation Позволяет работать с аннотациями в рантайме.
Array, Proxy и др. Дополнительные классы для работы с массивами и динамическими прокси.

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

  • При загрузке класса JVM создает объект Class, который содержит всю метаинформацию.
  • Через Class.forName("имя_класса") можно получить доступ к этому объекту.
  • Далее можно извлекать методы, поля, конструкторы и вызывать их с помощью соответствующих API.

🔄 К содержанию - главы
🔼 К содержанию


🔹 Хэш-таблица

Хэш-таблица — это структура данных, которая обеспечивает быстрый доступ к элементам на основе ключей. Элементы хранятся в массиве, доступ к которым осуществляется с использованием хэш-функции.

🔸 Ключевые элементы хэш-таблицы:

  1. Ключ (Key): Уникальная идентифицирующая информация, которая используется для нахождения значения. Например, ключом может быть строка или число.

  2. Хэш-функция: Алгоритм, который преобразует ключ в целочисленное значение (индекс). Это значение указывает, где хранить данные в массиве.

  3. Бакет (Bucket): Место в массиве, куда помещаются значения. Если несколько ключей имеют одинаковый хэш-код, то они хранятся в одном бакете, используя метод разрешения коллизий.

  4. Коллизия: Ситуация, когда два ключа генерируют одинаковый хэш-код. Это приводит к размещению нескольких элементов в одном бакете.

🔸 Методы разрешения коллизий:

  1. Метод цепочек (Chaining): Каждый бакет содержит список (или другую структуру данных), чтобы хранить элементы при коллизии.

  2. Открытая адресация (Open Addressing):
    Новый элемент помещается в другую свободную ячейку массива, если произошла коллизия.

  3. Перехэширование (Rehashing): Использование альтернативной хэш-функции для повторного распределения элементов.

🔸 Преимущества хэш-таблиц:

  • Быстрый доступ к данным по ключу: поиск, добавление и удаление происходят за O(1) в среднем случае.
  • Эффективность работы при большом количестве элементов.

🔸 Недостатки:

  • Хэш-таблицы требуют хорошей хэш-функции для минимизации коллизий.
  • Потребление памяти из-за необходимости хранения массива, включая пустые бакеты.

🔄 К содержанию - главы
🔼 К содержанию


🔹 Очереди и стеки

Очередь — структура данных, работающая по принципу FIFO (First-In-First-Out).
Стек — структура данных, работающая по принципу LIFO (Last-In-First-Out).

Различия:

  • Очередь позволяет добавлять элементы в конец и удалять с начала.
  • Стек позволяет добавлять и удалять элементы только с одного конца.

Примеры использования:

  • Очередь: управление задачами в многопоточных приложениях.
  • Стек: выполнение операций обратной польской записи.

🔄 К содержанию - главы
🔼 К содержанию


🏆Структуры данных и обобщения (Generics)

🔹 hashCode и equals + Хеш-таблицы, buckets

  1. Что такое hashCode?
    Метод hashCode — это метод, который возвращает целочисленное значение (хеш-код), представляющее объект. Это значение используется для определения положения объекта в структурах данных, основанных на хеш-таблицах, таких как HashMap, HashSet или Hashtable.

Зачем нужен hashCode?
Хеш-код помогает ускорить операции поиска, добавления и удаления элементов в коллекциях. Например, в HashMap объекты размещаются в "бакеты" (ячейки) на основе их хеш-кода. Таким образом, чтобы найти объект, программа сначала вычисляет его хеш-код и сразу переходит к соответствующему бакету, а не проверяет каждый элемент по очереди.

Основные правила работы с hashCode: Связь с методом equals:
Если два объекта считаются равными согласно методу equals, их хеш-коды должны быть одинаковыми.
Однако если хеш-коды одинаковые, это не означает, что объекты равны (возможны коллизии). Коллизия в контексте Java — это ситуация, когда два или более различных объекта имеют одинаковый хэш-код и попадают в одно и то же "ведро" (bucket) в хэш-таблице. Это происходит при использовании таких структур данных, как HashMap или HashSet.
Решение коллизий (теория):

  • Метод цепочек (Chaining): Каждый bucket хранит список элементов. Если происходит коллизия, новый элемент добавляется в список.
  • Открытая адресация (Open Addressing): При коллизии ищется другое свободное место в массиве для хранения элемента.
  • Реализация собственных хэш-функций: Хэш-функции должны быть хорошо сбалансированными, чтобы минимизировать вероятность коллизий.
    Переопределение:
    По умолчанию метод hashCode наследуется от класса Object и вычисляет хеш-код на основе адреса объекта в памяти.
    Если вы переопределяете метод equals (например, для сравнения объектов по содержимому полей), вам также нужно переопределить hashCode, чтобы обеспечить корректную работу коллекций.
    Консистентность:
    Хеш-код должен оставаться постоянным для одного и того же объекта, пока его состояние (поля), влияющие на вычисление хеш-кода, не меняется.
  1. Что такое equals? Метод equals используется для проверки равенства двух объектов на основе их содержимого, а не их ссылок в памяти. По умолчанию метод equals, унаследованный от класса Object, сравнивает объекты по их ссылкам (то есть проверяет, указывают ли две переменные на один и тот же объект в памяти). Однако часто требуется сравнивать объекты по их состоянию (например, значениям полей).

Зачем нужен equals? Когда вы создаете собственный класс, вам может потребоваться определить, какие объекты считаются равными с точки зрения логики программы. Например:

Два объекта Person могут считаться равными, если у них одинаковые имя и возраст.
Два объекта Book могут быть равны, если у них одинаковый ISBN.
Переопределение метода equals позволяет реализовать такую логику сравнения.

Основные правила работы с equals:
Связь с hashCode:
Если два объекта равны согласно методу equals, их хеш-коды должны быть одинаковыми.
Это правило необходимо для корректной работы коллекций, таких как HashMap или HashSet.
Симметричность:
Если a.equals(b) возвращает true, то b.equals(a) также должен возвращать true.
Транзитивность:
Если a.equals(b) и b.equals(c) возвращают true, то a.equals(c) тоже должен возвращать true.
Последовательность:
Результат equals должен оставаться постоянным, пока состояние объектов не меняется.
Сравнение с null:
Метод equals никогда не должен выбрасывать исключение при сравнении с null. Вместо этого он должен возвращать false.

3.Что такое хеш-таблицы и бакеты? Хеш-таблица — это эффективная структура данных, которая позволяет быстро выполнять операции добавления, удаления и поиска элементов. Она основана на использовании хеш-функции , которая преобразует ключ объекта в целочисленное значение (хеш-код), которое определяет, в какую ячейку (бакет) таблицы будет помещен этот объект.

Как работает хеш-таблица?
Хеш-функция:
Когда вы добавляете элемент в хеш-таблицу, его ключ обрабатывается хеш-функцией, которая вычисляет целочисленное значение (хеш-код). Это значение используется для определения конкретного бакета, куда будет помещен элемент.
Бакеты:
Бакеты — это внутренние ячейки хеш-таблицы. Каждый бакет может содержать один или несколько элементов. Хеш-код указывает, в какой бакет поместить элемент.
Поиск элемента:
При поиске элемента по ключу хеш-функция снова вычисляет хеш-код, чтобы найти нужный бакет. После этого выполняется сравнение с помощью метода equals, чтобы убедиться, что найденный элемент действительно соответствует ключу.

🔄 К содержанию - главы
🔼 К содержанию


🔹 Generics: общее понимание

Generics (обобщения) позволяют создавать классы, интерфейсы и методы, работающие с различными типами данных, при этом сохраняя типобезопасность.

Дженерики: Дженерики позволяют параметризировать классы, интерфейсы и методы типами. Это улучшает безопасность типов на этапе компиляции и делает код более устойчивым к ошибкам.

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

Контрвариантность: Контрвариантность допускает использование более общих типов. Она часто применяется там, где важна запись данных в коллекцию, но чтение ограничено объектами базового типа.

Инвариантность: Инвариантность подразумевает, что обобщённый тип нельзя заменить другим, даже если они связаны отношениями наследования. Для работы с наследованием используются ковариантные или контрвариантные wildcards.

Таблица: Что такое Generics в Java

Понятие Определение
Generics Механизм, позволяющий создавать классы, интерфейсы и методы, которые работают с параметризованными типами. Это позволяет избежать явного приведения типов и обеспечивает более безопасную работу с данными во время компиляции.
Типовые параметры Параметры типа (например, <T>, <E>, <K, V>), используемые для указания типа данных, который будет использоваться в классе, методе или интерфейсе. Они могут представлять любой тип, например, String, Integer или пользовательский класс.
Преимущества - Безопасность типов: Компилятор проверяет соответствие типов на этапе компиляции, предотвращая ошибки времени выполнения.
- Устранение приведения типов: Уменьшается необходимость использования оператора (Type) для преобразования объектов.
- Код переиспользование: Одна реализация может работать с разными типами данных.
Ограничения типов Возможность ограничить типы, которые могут быть использованы с generics, с помощью ключевого слова extends. Например, <T extends Number> означает, что параметр типа должен быть подклассом Number.
Wildcards Используются для работы с неизвестными типами данных. Существует три основных вида:
- ? — любой тип.
- <? extends T> — любой тип, являющийся подтипом T.
- <? super T> — любой тип, являющийся надтипом T.
Необходимость Generics особенно полезны при работе с коллекциями, так как они позволяют хранить элементы одного типа, исключая возможность добавления неправильных типов данных. Например, List<String> может содержать только строки.
Стерилизация Когда код, использующий generics, компилируется, компилятор выполняет процесс стирания типов (type erasure). Это означает, что в байт-коде все типовые параметры заменяются их ограничениями (или Object, если ограничений нет).
Недостатки - Невозможность создания экземпляров параметризованного типа (new T() недопустимо).
- Нельзя использовать примитивные типы (например, int, char) в качестве параметров типа; вместо них нужно использовать обертки (Integer, Character).

Таблица: Виды Generics в Java

Вид Generics Описание Пример использования
Простые Generics Базовый тип параметризации, где используется один параметр типа для обобщения. List<T> — коллекция объектов типа T.
Множественные Generics Позволяет использовать несколько параметров типов одновременно. Map<K, V> — словарь с ключами типа K и значениями типа V.
Ограниченные Generics Указывает ограничения на типы, которые могут быть использованы с generics. <T extends Number>T должен быть подклассом Number.
Нижние границы Generics Указывает, что тип должен быть надтипом указанного класса. <T super Integer>T должен быть надтипом Integer.
Wildcards (Звездочки) Используются для работы с неизвестными или универсальными типами данных.
- Unbounded Wildcard Обозначает любой тип (?). List<?> — список любого типа.
- Upper Bounded Wildcard Обозначает тип, который является подклассом указанного класса (<? extends T>). List<? extends Number> — список подклассов Number.
- Lower Bounded Wildcard Обозначает тип, который является надклассом указанного класса (<? super T>). List<? super Integer> — список надклассов Integer.
Статические Generics Позволяет создавать статические методы, поддерживающие параметры типов. public static <T> void printArray(T[] array)
Generic Methods Методы, которые принимают или возвращают параметризованные типы, независимо от класса. public <T> T getFirstElement(List<T> list)
Generic Classes Классы, которые определены с параметрами типов, позволяя работать с различными типами данных. public class Box<T> { private T content; }
Generic Interfaces Интерфейсы, которые используют параметры типов для абстракции различных типов данных. public interface Comparator<T> { int compare(T o1, T o2); }
Type Erasure Процесс, при котором компилятор заменяет все параметры типов на их ограничения или Object. Компилятор преобразует List<String> в List<Object>.
  1. Основная идея:

    • Generics обеспечивают возможность использовать параметризованные типы, что позволяет избежать приведения типов и уменьшить количество ошибок во время выполнения.
    • Пример:
      List<String> list = new ArrayList<>();
      listadd("Hello");
      String str = listget(0); // Без явного приведения типа
  2. Ограничения типов:

    • Можно указывать ограничения для параметров типа с помощью ключевого слова extends.
    • Пример:
      class Box<T extends Number> {
          private T value;
          public void set(T value) { thisvalue = value; }
          public T get() { return value; }
      }
  3. Wildcards:

    • Используются для работы с неизвестными типами.
    • Пример:
      List<?> list = new ArrayList<>(); // Любой тип
      List<? extends Number> numbers = new ArrayList<>(); // Подтип Number
      List<? super Integer> integers = new ArrayList<>(); // Супертип Integer
  4. Type Erasure:

    • Во время компиляции информация о типах generics удаляется (type erasure), поэтому generics работают только на уровне компиляции.
    • Это может привести к ограничениям, например, нельзя создать новый экземпляр параметризованного типа.

🔄 К содержанию - главы
🔼 К содержанию


👑 Управление памятью и работа со ссылками

🔸 Управление памятью, сборщик мусора - Memory Management, Garbage Collection

В Java управление памятью автоматизировано, что делает язык более безопасным и удобным для использования. Память делится на несколько областей:

Stack (Стек) : Хранит локальные переменные и информацию о вызовах методов. Каждый поток имеет свой собственный стек.
Heap (Куча) : Общая область памяти, где создаются все объекты. Управление памятью здесь осуществляется через механизм сборки мусора.
Method Area (Область методов) : Хранит метаданные классов, такие как имена, типы и код методов.
PC Register (Регистр команд) : Отслеживает текущую выполняемую инструкцию для каждого потока.
Native Method Stack (Стек нативных методов) : Используется для выполнения методов, написанных на других языках программирования.

Сборщик мусора (Garbage Collector) автоматически удаляет объекты, которые больше недоступны для программы. Это позволяет разработчику не беспокоиться о ручном освобождении памяти, снижая риск утечек.

finalize() метод

Метод finalize() — это специальный метод в Java, который был предназначен для выполнения очистки ресурсов перед тем, как объект будет удален сборщиком мусора. Однако его использование считается устаревшим и не рекомендуется в современных приложениях.

Что такое finalize()?
Метод finalize() определен в классе Object и может быть переопределен в подклассах.
Он вызывается JIT-компилятором (Just-In-Time Compiler) перед удалением объекта сборщиком мусора.
Его основное назначение — выполнить действия по очистке ресурсов (например, закрыть файлы, сокеты или другие внешние ресурсы).

Альтернативы finalize() Современные подходы к управлению ресурсами в Java:

Интерфейс AutoCloseable и конструкция try-with-resources:
Используется для автоматического закрытия ресурсов (например, файлов, соединений с базой данных).
После выхода из блока try, ресурс автоматически закрывается.
Ручное управление ресурсами:
Явно вызывать методы для освобождения ресурсов (например, close() для файлов или disconnect() для сетевых соединений).
Финализаторы заменены на Cleaner и PhantomReference:
В Java 9 появился класс java.lang.ref.Cleaner, который является более эффективной альтернативой finalize().
PhantomReference позволяет отслеживать объекты, готовые к сборке мусора, без использования finalize().

1. Stack (Стек)

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

2. Heap (Куча)

  • Общая область памяти для всех потоков, где создаются объекты.
  • Управление памятью в куче осуществляется автоматически через механизм сборки мусора (Garbage Collector).
  • Все объекты, созданные с помощью оператора new, выделяются в куче.
  • Размер кучи можно настраивать через параметры JVM.

3. Method Area (Область методов)

  • Часть памяти, используемая для хранения структуры классов, таких как метаданные классов, код методов и константы.
  • Включает в себя String Pool — пул строк, где хранятся литеральные строки.
  • Является общей для всех потоков.

4. PC Register (Регистр команд)

  • Содержит адрес текущей выполняемой инструкции для каждого потока.
  • Отслеживает порядок выполнения команд.

5. Native Method Stack (Стек нативных методов)

  • Используется для выполнения нативных методов (методов, написанных на языках, отличных от Java, таких как C или C++).
  • Аналогичен обычному стеку, но нативный код.

6. Garbage Collector (Сборщик мусора)

  • Автоматический механизм очистки памяти в куче.
  • Освобождает память, занимаемую объектами, которые больше не доступны для программы.
  • Работает асинхронно и может временно приостанавливать выполнение программы (pause).
Раздел памяти Входит в... Общий или Потокозависимый? Что хранит? Главное отличие / Особенность
Heap (Куча) JVM Общий (для всех потоков) Все объекты (экземпляры классов), созданные через new. Управляется Сборщиком Мусора (GC). Основная область для данных приложения.
Method Area (Область Методов) JVM Общий (для всех потоков) Метаданные классов, код методов, статические переменные, String Pool. Хранит "чертежи" и постоянную информацию о структуре программы.
Stack (Стек) JVM Потокозависимый (у каждого потока свой) Локальные переменные, примитивные типы, ссылки на объекты в Куче, информация о вызовах методов (фреймы). Работает по принципу LIFO (последний пришёл — первый ушёл). Вызывает StackOverflowError.
PC Register (Регистр Команд) JVM Потокозависимый (у каждого потока свой) Адрес текущей выполняемой инструкции JVM для данного потока. Помогает потоку "помнить", где он остановился, чтобы продолжить работу после переключения.
Native Method Stack (Стек Нативных Методов) JVM Потокозависимый (у каждого потока свой) Используется для нативных методов (код на C/C++), вызванных из Java-кода. Аналогичен обычному Стеку, но для кода, не написанного на Java.

Дополнительное пояснение о Сборщике Мусора (GC)

Сборщик мусора (Garbage Collector) не является областью памяти, но это ключевой механизм, работающий с Кучей (Heap).

  • Задача: Освобождать место в Куче, удаляя те объекты, на которые больше нет ни одной ссылки из работающей программы (они недостижимы).
  • Особенность: Это автоматическое управление памятью. Программисту не нужно вручную удалять объекты, как в C++.
  • Минус: Иногда GC должен остановить все потоки приложения (так называемая "пауза" или Stop-The-World), чтобы выполнить очистку, что может вызвать небольшие задержки.

🔄 К содержанию - главы
🔼 К содержанию


🔸 Строки и пул строк(Pool string)

Строки в Java являются неизменяемыми (immutable), что означает, что их содержимое нельзя изменить после создания.

Пул строк — это специальная область памяти в Java Heap(Куча), где хранятся литеральные строки (строки, объявленные как "текст"). JVM автоматически управляет этим пулом для оптимизации использования памяти. Если строка уже существует в пуле, вместо создания новой копии ссылается на существующий объект.

Строка (String) в Java представляет собой неизменяемую (иммутабельную) последовательность символов. Класс String является частью пакета java.lang и используется для работы с текстовыми данными. Поскольку строки являются иммутабельными, любая операция, изменяющая строку (например, конкатенация или замена символов), на самом деле создает новую строку.

  1. Пул строк:

    • Java автоматически сохраняет строки в пуле для оптимизации использования памяти.
    • Если строка уже существует в пуле, создается ссылка на существующую строку, а не новая копия.
    • Пример:
      String str1 = "Hello";
      String str2 = "Hello";
      Systemoutprintln(str1 == str2); // true, так как строки берутся из пула  
  2. new String:

    • Создание строки с использованием оператора new всегда приводит к созданию нового объекта в памяти, даже если такая строка уже существует в пуле.
    • Пример:
      String str3 = new String("Hello");
      Systemoutprintln(str1 == str3); // false, так как str3 создан вне пула
  3. intern:

    • Метод intern() добавляет строку в пул, если её там нет.
    • Пример:
      String str4 = new String("Hello").intern();
      Systemoutprintln(str1 == str4); // true, так как str4 теперь ссылается на строку из пула

🔄 К содержанию - главы
🔼 К содержанию


🔸 Каллизия (Call-by-value и Call-by-reference)

Каллизия (Call-by-value и Call-by-reference)

1. Call-by-value

  • При передаче параметров по значению (call-by-value) в метод копируется значение аргумента.
  • Любые изменения, произведенные внутри метода с этими параметрами, не влияют на оригинальные данные за пределами метода.

2. Особенность Java: Объекты передаются через ссылки, но все еще call-by-value

  • В Java используется только call-by-value, даже при работе с объектами.
  • Когда передается объект, kopия ссылки на этот объект передается в метод.
  • Это позволяет изменять состояние объекта (например, его поля) внутри метода, но не позволяет заменить сам объект на другой.

3. Сравнение Call-by-value и Call-by-reference

Особенность Call-by-value Call-by-reference
Передача данных Передается копия значения или ссылки. Передается сама ссылка на данные.
Изменение параметров Изменения внутри метода не влияют на оригинальные данные. Изменения внутри метода влияют на оригинальные данные.
Java Используется только call-by-value, даже для объектов (передается копия ссылки). Языки, такие как C++ или Python, поддерживают call-by-reference для некоторых типов.

4. Важно помнить

  • В Java всегда используется call-by-value.
  • Для примитивных типов передается копия значения.
  • Для объектов передается копия ссылки, что позволяет изменять состояние объекта, но не заменять сам объект.

🔄 К содержанию - главы
🔼 К содержанию


💡 Фундаментальные концепции

🔹 Методы класса Object

Таблица: Методы класса Object

Метод Возвращаемый тип Описание
toString() String Возвращает строковое представление объекта. Может быть переопределен для более осмысленного вывода.
equals(Object obj) boolean Сравнивает этот объект с указанным объектом (obj). По умолчанию сравнивает ссылки.
hashCode() int Возвращает хэш-код объекта. Должен быть согласован с методом equals().
clone() Object Создает и возвращает копию объекта. Класс должен реализовать интерфейс Cloneable.
finalize() void Вызывается перед сборкой мусора. Устаревший с Java 9, не рекомендуется использовать.
getClass() Class<?> Возвращает объект типа Class, представляющий класс этого объекта.
notify() void Будит один поток, который ждет на этом объекте. Используется в многопоточности.
notifyAll() void Будит все потоки, которые ждут на этом объекте.
wait() void Приостанавливает выполнение текущего потока, пока другой поток не вызовет notify() или notifyAll().
wait(long timeout) void Аналогично wait(), но с таймаутом в миллисекундах.
wait(long timeout, int nanos) void Аналогично wait(long timeout), но с точностью до наносекунд.

Класс Object является суперклассом всех классов в Java. Он предоставляет базовые методы, которые могут быть переопределены в подклассах.

  1. toString:

    • Возвращает строковое представление объекта.
    • Переопределяется для удобного вывода информации о состоянии объекта.
    • Пример:
      @Override
      public String toString() {
          return "MyClass{" + "field1=" + field1 + ", field2=" + field2 + '}';
      }
  2. equals:

    • Описан выше. Используется для сравнения объектов.
  3. hashCode:

    • Описан выше. Используется для работы с хеш-таблицами.
  4. clone:

    • Создает копию объекта. Требует реализации интерфейса Cloneable.
    • Пример:
      @Override
      protected MyClass clone() throws CloneNotSupportedException {
          return (MyClass) super.clone();
      }
  5. finalize (устаревший):

    • Вызывается перед сборкой мусора для выполнения очистки ресурсов.
    • Не рекомендуется использовать из-за неопределённости времени вызова.
  6. getClass:

    • Возвращает объект Class, представляющий тип объекта.
  7. wait/notify/notifyAll:

    • Используются для синхронизации потоков.
    • Пример:
      synchronized (this) {
          wait(); // Приостанавливает текущий поток
          notify(); // Пробуждает один поток
      }

🔄 К содержанию - главы
🔼 К содержанию


🔹 Базовый синтаксис - Basic Syntax3️⃣

JVM, Java Virtual Machine (Виртуальная машина Java) — основная часть среды времени исполнения Java (JRE). Виртуальная машина Java исполняет байт-код Java, предварительно созданный из исходного текста Java-программы компилятором Java.

JRE, Java Runtime Environment (Среда времени выполнения Java) - минимально-необходимая реализация виртуальной машины для исполнения Java-приложений.

JDK, Java Development Kit (Комплект разработки на Java) - среда для разработки программ на Java, включающая в себя JRE - среду для обеспечения запуска Java программ, которая в свою очередь содержит JVM - интерпретатор кода Java программ.

Java — строго типизированный язык программирования, требующий явного объявления типов переменных. Программа всегда начинается с класса, который содержит метод main. Этот метод является точкой входа программы.

Основные правила синтаксиса включают:

Все операции выполняются внутри блоков, ограниченных фигурными скобками {}. Инструкции завершаются точкой с запятой (;). Комментарии пишутся с помощью // (однострочный) или /* ... */ (многострочный).

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

Метод main является основной точкой входа в программу на Java. Без этого метода программа не сможет быть выполнена, так как JVM (Java Virtual Machine) ищет именно его для запуска.

🔄 К содержанию - главы
🔼 К содержанию


🔹 Интерфейсы - Interface5️⃣

Интерфейсы в Java — это как "правила игры", которые класс должен соблюдать. Они помогают определить, какие действия объект должен уметь выполнять, но не указывают, как именно их выполнять.

Что такое интерфейс?

Интерфейс — это набор методов без реализации (то есть без тела), который классы обязаны реализовать.
Когда класс реализует интерфейс, он гарантирует выполнение всех методов, описанных в интерфейсе.

Особенности интерфейсов 📝

Абстрактные методы

  • Методы без тела (реализации).
  • Классы, реализующие интерфейс, обязаны предоставить реализацию всех абстрактных методов.

Константы

  • Переменные с модификаторами public static final.
  • Доступны всем классам, реализующим интерфейс.
  • Неизменяемы (константы).

Методы по умолчанию (default)

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

Статические методы

  • Также появились с Java 8.
  • Вызываются напрямую через имя интерфейса.
  • Не могут быть переопределены в классах, реализующих интерфейс.

Зачем нужны интерфейсы? 🤔

Множественная реализация

  • В Java класс может наследоваться только от одного класса, но реализовывать несколько интерфейсов.
  • Обеспечивает гибкость при проектировании.

Общие контракты

  • Определяют единые правила для множества классов.
  • Позволяют работать с объектами различных классов одинаково через ссылки на интерфейс.

Типы интерфейсов

Тип интерфейса Описание Примеры интерфейсов
Маркерный Не содержит методов. Используется для обозначения свойств объектов. Serializable, Cloneable
Функциональный Содержит один абстрактный метод. Часто применяется в лямбда-выражениях. Runnable, Callable, Function<T, R>
Нормальный Содержит один или более абстрактных методов и может включать default, static, private методы. List, Map, Set
Интерфейс с вложенными интерфейсами Может содержать вложенные интерфейсы для логической группировки или структурирования API. Map.Entry<K, V>
Потребительский (Consumer) Интерфейсы, которые потребляют данные, не возвращая результата. Используются в обработке данных. Consumer<T>, BiConsumer<T, U>

🔄 К содержанию - главы
🔼 К содержанию


🔹 Методы

Методы в Java — это блоки кода, выполняющие определенные действия. Они обеспечивают переиспользование кода, инкапсуляцию и структурирование программы.

Что такое метод? 📝

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

Основные особенности методов 🚩

Объявление метода

  • Состоит из модификаторов доступа, типа возвращаемого значения, имени метода и параметров (если есть).
  • Пример объявления (без реализации): public int add(int a, int b)

Параметры метода

  • Передаваемые значения, необходимые для выполнения метода.
  • Могут быть примитивными типами или объектами.

Возвращаемое значение

  • Тип, который метод возвращает (int, String, void и т. д.).
  • void означает, что метод ничего не возвращает.

Типы методов в Java 📚

Тип метода Описание
Статический метод Объявляется с ключевым словом static. Вызывается через имя класса.
Экземплярный метод Принадлежит объекту класса. Требует создания экземпляра для вызова.
Абстрактный метод Объявляется с ключевым словом abstract. Не имеет реализации. Реализуется в подклассах.
Метод по умолчанию Объявляется в интерфейсах с ключевым словом default. Имеет реализацию.
Финализированный метод Объявляется с ключевым словом final. Не может быть переопределен в подклассах.
Синхронизированный метод Объявляется с ключевым словом synchronized. Обеспечивает потокобезопасность.
Конструктор Специальный метод, вызываемый при создании объекта. Не имеет типа возвращаемого значения.
Перегруженный метод Метод с тем же именем, но с разными параметрами.
Нативный метод Объявляется с ключевым словом native. Реализуется вне Java (например, на C/C++).
Private метод Объявляется с ключевым словом private. Доступен только внутри класса.
Protected метод Объявляется с ключевым словом protected. Доступен внутри пакета и в подклассах.
Public метод Объявляется с ключевым словом public. Доступен из любого места программы.
Пакетно-приватный метод Метод без модификатора доступа. Доступен только внутри пакета.
Метод с переменным числом аргументов Объявляется с использованием ... после типа параметра. Позволяет передавать произвольное количество аргументов.
Переопределенный метод Метод, который переопределяет метод родительского класса с помощью аннотации @Override.
Лямбда-выражение Не является методом в классическом смысле, но используется как анонимная реализация функционального интерфейса.
Метод, объявленный в enum Метод, определенный внутри перечисления (enum). Может быть статическим или экземплярным.
Метод, объявленный в record Метод, определенный внутри записи (record). Может быть как автоматически сгенерированным, так и пользовательским.
Метод, помеченный как strictfp Объявляется с ключевым словом strictfp. Гарантирует одинаковое поведение операций с плавающей точкой на всех платформах.
Метод с возвращаемым значением Метод, который возвращает значение определенного типа (например, int, String).
Метод без возвращаемого значения Метод, объявленный с типом возвращаемого значения void. Не возвращает никаких данных.

Модификаторы доступа к методам 🔐

Модификатор Описание
public Доступен из любого класса.
protected Доступен из классов того же пакета и подклассов.
private Доступен только внутри объявляющего класса.
(Без модификатора) Доступен только внутри пакета (package-private).

Специальные типы методов 🎯

Перегрузка (Overloading)

  • Создание нескольких методов с одним именем, но разными параметрами.
  • Позволяет разные способы вызова метода в зависимости от параметров.

Переопределение (Overriding)

  • Позволяет подклассу предоставить свою реализацию метода, объявленного в суперклассе.
  • Требуется одинаковая сигнатура метода (имя, параметры и тип возвращаемого значения).

Методы в интерфейсах

Тип метода На английском Модификатор доступа Можно переопределять? Описание
Абстрактный Abstract public Да (обязательно) Метод без реализации. Классы, реализующие интерфейс, должны его реализовать.
По умолчанию Default public Да (опционально) Метод с реализацией, добавленный с Java 8. Можно переопределить в классе.
Статический Static public Нет Метод, который можно вызывать через имя интерфейса. Не наследуется.
Приватный Private private Нет Метод, используемый внутри интерфейса для разбиения логики.

🔄 К содержанию - главы
🔼 К содержанию


🔹 Классы

Класс — это шаблон или структура в объектно-ориентированном программировании (ООП), которая описывает свойства (поля) и поведение (методы) объектов. Он используется для создания экземпляров (объектов), которые имеют одинаковые характеристики и поведение.

Основные элементы класса:

  • Поля: Переменные, которые хранят данные или состояние объекта.
  • Методы: Действия или функции, которые определяют поведение объекта.
  • Конструкторы: Специальные методы для инициализации объектов при их создании.
  • Модификаторы доступа: Указывают доступность элементов класса (например, public, private, protected).
  • Статические элементы: Методы или поля, принадлежащие всему классу, а не конкретным объектам.

Типы классов

Тип класса Описание Примеры
Обычные классы Основные классы, которые используются для описания сущностей. Person, Animal
Абстрактные классы Классы, которые не могут быть инстанцированы и служат базой для других классов. Shape, Vehicle
Вложенные (Nested) классы Классы, определенные внутри других классов. Делятся на статические и нестатические. Outer.Inner
Анонимные классы Классы без имени, которые обычно используются для переопределения методов интерфейсов. Реализация Runnable
Финальные классы Классы, которые нельзя наследовать (объявляются с ключевым словом final). String
Классы-оболочки (Wrapper) Классы, которые оборачивают примитивные типы данных, чтобы работать с ними как с объектами. Integer, Double
Иммутабельные классы Классы, состояние объектов которых не может быть изменено после создания. String, BigDecimal

Преимущества использования классов

  • Инкапсуляция данных и поведения.
  • Повышение повторного использования кода за счет наследования.
  • Легкость создания и работы с объектами.
  • Улучшение структурирования программы.

🔄 К содержанию - главы
🔼 К содержанию


🔹 Множественное наследование - Multiple Inheritance🔟

Множественное наследование в Java: Почему интерфейсы, а не классы?

Что такое множественное наследование?

Множественное наследование — это когда один класс может брать свойства и методы сразу от нескольких родительских классов. Это как если бы вы могли научиться одновременно готовить, петь и танцевать, используя умения из разных людей.

Почему Java не поддерживает множественное наследование через классы? Казалось бы, множественное наследование — это удобно, но оно может привести к серьезным проблемам:

Конфликты методов : Если два родителя имеют методы с одинаковым названием, какой из них выбрать? Компьютер запутается. Проблема алмазной формы: Представьте такую цепочку: Класс A — общий предок. От него наследуются классы B и C. Класс D наследуется от обоих B и C. Если B и C переопределяют один и тот же метод из A, то как понять, чья версия нужна в D? Это вызывает путаницу. Из-за этих сложностей Java решила отказаться от множественного наследования через классы.

Как интерфейсы помогают решить эту проблему? Вместо классов Java предлагает использовать интерфейсы для множественного наследования. Интерфейсы — это как список правил, которые класс должен выполнить, но сам интерфейс не содержит конкретного кода.

Интерфейсы содержат только "шаблоны" методов : В них нет готового кода, только описание того, какие методы должны быть. Класс, который реализует этот интерфейс, сам решает, как именно будет летать. Нет состояния (полей) : Интерфейсы не хранят данные, поэтому не возникает конфликтов из-за полей. Методы по умолчанию (начиная с Java 8) : Теперь интерфейсы могут иметь методы с готовым кодом, помеченные как default. Но если несколько интерфейсов предоставляют одинаковые методы, класс должен явно выбрать, какой использовать. Это исключает неоднозначность.

🔄 К содержанию - главы
🔼 К содержанию


🏗️ Архитектурные подходы и паттерны проектирования

🔸 Группы паттернов проектирования

Паттерны проектирования можно классифицировать на три основные группы: порождающие, структурные и поведенческие. Ниже представлены эти группы в виде таблиц.

1. Порождающие паттерны (Creational Patterns)

Паттерн Описание
Singleton Обеспечивает наличие единственного экземпляра класса и предоставляет к нему глобальный доступ.
Factory Method Определяет интерфейс для создания объектов, позволяя подклассам решать, какие объекты создавать.
Abstract Factory Предоставляет интерфейс для создания семейств связанных или зависимых объектов.
Builder Разделяет процесс конструирования сложного объекта от его представления.
Prototype Создает новые объекты путем копирования существующих (прототипов).

2. Структурные паттерны (Structural Patterns)

Паттерн Описание
Adapter Преобразует интерфейс одного класса в другой, ожидаемый клиентом.
Bridge Разделяет абстракцию и реализацию, позволяя им изменяться независимо.
Composite Позволяет создавать древовидные структуры объектов с единым интерфейсом.
Decorator Динамически добавляет дополнительное поведение объектам без изменения их класса.
Facade Предоставляет упрощенный интерфейс к сложной системе классов.
Flyweight Минимизирует использование памяти за счет разделяемых данных.
Proxy Предоставляет объект-заместитель для контроля доступа к другому объекту.

3. Поведенческие паттерны (Behavioral Patterns)

Паттерн Описание
Chain of Responsibility Создает цепочку обработчиков для передачи запросов до тех пор, пока он не будет обработан.
Command Преобразует запросы в объекты для параметризации клиентов различными запросами.
Interpreter Определяет грамматику для языка и интерпретирует предложения этого языка.
Iterator Предоставляет способ последовательного доступа ко всем элементам коллекции.
Mediator Инкапсулирует взаимодействие между множеством объектов.
Memento Сохраняет состояние объекта для его восстановления в будущем.
Observer Определяет зависимость между объектами для автоматического уведомления при изменении состояния.
State Позволяет объекту изменять свое поведение в зависимости от внутреннего состояния.
Strategy Определяет семейство алгоритмов, инкапсулируя каждый из них и делая их взаимозаменяемыми.
Template Method Определяет скелет алгоритма, позволяя подклассам переопределить некоторые шаги.
Visitor Позволяет определить новую операцию над каждым элементом структуры данных.

🔄 К содержанию - главы
🔼 К содержанию


🔸 Термины - SOLID

SOLID

Принципы SOLID:

  • S - Single Responsibility Principle (Принцип единственной ответственности): Каждый класс должен иметь одну и только одну причину для изменения.
  • O - Open/Closed Principle (Принцип открытости/закрытости): Код должен быть открыт для расширения, но закрыт для изменения.
  • L - Liskov Substitution Principle (Принцип подстановки Барбары Лисков): Объекты базового класса могут быть заменены объектами его подкласса без изменения корректности программы.
  • I - Interface Segregation Principle (Принцип разделения интерфейсов): Интерфейс не должен заставлять реализовывать методы, которые не используются.
  • D - Dependency Inversion Principle (Принцип инверсии зависимостей): Модули верхнего уровня не должны зависеть от модулей нижнего уровня; оба должны зависеть от абстракций.

DRY

Don't Repeat Yourself (Не повторяй себя):

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

KISS

Keep It Simple, Stupid (Делай проще):

  • Этот принцип гласит, что система должна быть настолько простой, насколько возможно.
  • Избегай ненужной сложности в дизайне и реализации.
  • Простота облегчает понимание, поддержку и улучшение системы.

YAGNI

You Aren't Gonna Need It (Тебе это не понадобится):

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

GRASP

General Responsibility Assignment Software Patterns (Шаблоны назначения ответственности):

  • Предлагает подходы к распределению обязанностей между классами и объектами в программной системе.
  • Включает такие шаблоны, как "Контроллер", "Информационный эксперт" и другие.

CoC

Convention over Configuration (Конвенция вместо конфигурации):

  • В систему заложены стандартные настройки, которые используются по умолчанию.
  • Это уменьшает количество ручной конфигурации и делает процесс разработки более эффективным.

Encapsulation

Инкапсуляция:

  • Скрытие внутренней реализации и данных класса от внешнего мира.
  • Обеспечивает доступ к функциональности класса через строго определённые интерфейсы.

Polymorphism

Полиморфизм:

  • Возможность использования одного интерфейса для различных реализаций.
  • Это позволяет создавать код, независимый от конкретных типов.

🔄 К содержанию - главы
🔼 К содержанию