Java for Beginner
759 subscribers
739 photos
212 videos
12 files
1.22K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Раздел 6. Коллекции в Java

Глава 4. Queue и Deque

Практика:
В «Библиотеке» добавить очередь читателей (Queue<String>) для книги. Когда книга возвращается — выдать её первому из очереди

Откройте проект «Библиотека»

Запустите IntelliJ IDEA и откройте существующий проект LibraryProject. Убедитесь, что классы Book и Library существуют: Book с полями title, author, year (и геттерами), Library с массивом книг, счетчиком bookCount, методом addBook и Set для авторов.

Импортируйте необходимые пакеты:
В файлах, где будете использовать Queue, убедитесь, что импортированы java.util.Queue и java.util.LinkedList (или ArrayDeque — выберите реализацию для FIFO). IDE подскажет, когда вы начнете писать код — используйте Ctrl+Enter для автодобавления импорта.

Выберите реализацию Queue:
Для этого задания используйте LinkedList<String> как Queue — она проста для FIFO и позволяет операции на концах. Альтернатива — ArrayDeque для большей эффективности, если хотите поэкспериментировать.


Обновление класса Book для очереди читателей

Поскольку очередь читателей относится к конкретной книге (кто ждет эту книгу), добавим её в класс Book.

Добавьте поле для Queue<String>:
Откройте файл Book.java.
Объявите приватное поле readerQueue типа Queue<String>, инициализированное как new LinkedList<> (или new ArrayDeque<>).
Это множество будет хранить имена читателей в порядке очереди (FIFO: первый добавленный — первый получит книгу).



Добавьте методы для работы с очередью

Создайте публичный метод addToQueue(String readerName), который:
Добавляет имя читателя в конец очереди с помощью offer (readerQueue.offer(readerName)).
Можно вывести сообщение, например, "Читатель [readerName] добавлен в очередь за книгой [title]".


Создайте публичный метод returnBook(), который моделирует возврат книги:
Проверяет, пуста ли очередь (readerQueue.isEmpty()).
Если пуста — выводит сообщение "Книга свободна, очередь пуста".
Если не пуста — извлекает первого читателя с помощью poll (String nextReader = readerQueue.poll();).
Выводит сообщение "Книга выдана следующему читателю: [nextReader]".
(Опционально: Если книга была занята, здесь можно обновить статус, но для простоты пока опустим).


Обновите конструктор Book:
В конструкторе Book убедитесь, что readerQueue инициализируется (если не сделали при объявлении поля).


Интеграция с классом Library

Теперь обновим Library, чтобы при добавлении книги учитывать очередь, но поскольку очередь в Book, Library будет работать с объектами Book.

Обновите метод addBook(Book book):
В методе addBook оставьте добавление в массив книг, но добавьте проверку: Если очередь читателей в книге не пуста (book.getReaderQueue().isEmpty() == false), выведите сообщение "Книга добавлена, но имеет очередь читателей".
(Это опционально, но поможет связать с практикой).



Добавьте метод для возврата книги

Создайте публичный метод returnBookByTitle(String title), который:
Ищет книгу в массиве по title (перебор с equals).
Если найдена — вызывает returnBook() на объекте Book.
Если не найдена — выводит сообщение "Книга не найдена".



Обновление класса Main для тестирования

Теперь протестируем новую функциональность в Main.

Создайте объекты и добавьте книги:
В методе main создайте объект Library.
Создайте объект Book (например, с title "Война и мир", author "Толстой", year 1869).
Вызовите addBook на Library.

Добавьте читателей в очередь:
Через объект Book вызовите addToQueue несколько раз с разными именами читателей (например, "Иван", "Мария", "Петр").
Вызовите returnBook() на Book — первый читатель ("Иван") должен получить книгу, остальные остаются в очереди.


Протестируйте возврат:
Вызовите returnBook() еще раз — следующий читатель ("Мария") получит.
Продолжите, пока очередь не опустеет — последний вызов вернет null или сообщение о пустоте.



#Java #для_новичков #beginner #Collections #Deque #ArrayDeque #LinkedList
👍2
Тестирование и отладка

После реализации протестируйте, чтобы убедиться в правильной работе очереди.

Запустите проект:
Правой кнопкой на Main.java → Run 'Main.main()'.
В консоли увидите сообщения о добавлении читателей и выдаче книги по порядку (FIFO: первый добавленный — первый получает).


Проверьте FIFO:

Убедитесь, что читатели выдаются в порядке добавления (offer в конец, poll из начала).
Попробуйте добавить null как читателя — проверьте поведение (LinkedList позволит).


Отладка:
Установите breakpoint в методе returnBook перед poll и после — шагайте (F8) и смотрите размер очереди (readerQueue.size()).
Если ошибки: NullPointerException (если очередь не инициализирована или книга не найдена) — добавьте проверки if (readerQueue != null && !readerQueue.isEmpty()).
ArrayIndexOutOfBoundsException в массиве книг — расширьте массив или используйте динамический список (позже заменим на List).


Эксперименты:
Измените реализацию Queue на ArrayDeque — проверьте, работает ли аналогично (да, но эффективнее по памяти).
Добавьте метод isQueueEmpty() в Book для проверки пустоты — используйте в Library для статистики.



Полезные советы для новичков

Инициализация Queue: Всегда инициализируйте в конструкторе Book (readerQueue = new LinkedList<>();), чтобы избежать NullPointerException.
Проверка возвращаемого: Poll возвращает null при пустой — используйте if (nextReader != null) для сообщений.
Расширение: Подумайте о статусе книги (boolean isAvailable) — при возврате устанавливайте true, при выдаче — false.
Массив книг: Пока используем массив, но заметьте ограничения — в следующих уроках заменим на List<Book>.
Thread-safety: Если проект вырастет, подумайте о ConcurrentLinkedQueue для многопоточности.



#Java #для_новичков #beginner #Collections #Deque #ArrayDeque #LinkedList
👍2
Что такое gRPC и зачем он нужен

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

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

Зачем изучать gRPC в 2025 году? Потому что мир софта ушел от простых сайтов к сложным системам: миллиарды устройств в интернете вещей, тысячи микросервисов в компаниях вроде Netflix или Uber. Обычные веб-API (как REST) здесь тормозят — gRPC ускоряет всё в 7–10 раз, экономит трафик и упрощает разработку.


Что такое RPC: Простое объяснение концепции удаленного вызова процедур

Удаленный вызов процедур (RPC — Remote Procedure Call) — это идея, где программа А "звонит" программе Б и говорит: "Выполни эту функцию и верни результат". Всё выглядит как обычный вызов локальной функции: передаешь данные, ждешь ответ — и готово. Никаких сложностей с сетью на уровне кода.

Пример из жизни: Вы в приложении нажимаете "Получить погоду" — ваше мобильное app вызывает функцию на сервере метеослужбы. Сервер считает, возвращает данные. В gRPC это пишется так же просто:
Клиент (ваше app): ответ = сервер.ПолучитьПогоду(город="Москва")
Сервер: выполняет расчет и отдает результат.


Без RPC пришлось бы вручную формировать запросы, парсить ответы — утомительно. gRPC автоматизирует всё: генерирует готовый код, управляет соединением и ошибками. В итоге — код чище, быстрее и надежнее.


Разница gRPC с REST: Почему RPC побеждает в скорости и гибкости

REST (Representational State Transfer — архитектурный стиль для веб-API) — популярный подход, где API строится вокруг эндпоинтов для получения данных. Использует HTTP-методы (GET, POST), текст в формате JSON. Просто читать человеку, но медленно для машин.

Вот ключевые различия:

Формат данных: REST — текст (JSON, большой и медленный), gRPC — бинарный (компактный, в 3–10 раз меньше). JSON "Привет, мир!" — 15 байт, в gRPC — 5 байт.
Скорость: gRPC на HTTP/2 (мультиплексирование — много запросов по одному каналу) в 7 раз быстрее при приеме данных, в 10 раз — при отправке. Идеально для мобильного интернета.
Гибкость общения: REST — только "запрос-ответ". gRPC поддерживает стриминг (поток сообщений): сервер шлет обновления в реальном времени (как чат), клиент загружает файлы порциями, или оба общаются одновременно.
Языки и платформы: REST — универсален, но код пишется вручную. gRPC генерирует код для 10+ языков (Python, Go, Java) из одного описания — никаких несоответствий.
Отладка: REST читаем в браузере, gRPC — бинарный, но есть инструменты вроде gRPCurl (аналог curl).

REST хорош для публичных API (сайты, простые apps). gRPC — для внутренних систем, где важна производительность. В 2025 году компании комбинируют: REST снаружи, gRPC внутри.


#Java #middle #gRPC
👍2👎1
Почему Google создал gRPC: От внутренних нужд к мировому стандарту

Google — гигант с миллионами микросервисов (маленькие программы, работающие вместе). С 2001 года они использовали внутренний фреймворк Stubby для их связи. Но Stubby был закрытым, и партнерам (Android, YouTube API) приходилось писать свои библиотеки.

В 2015 году Google открыл gRPC: эволюцию Stubby на HTTP/2 и Protocol Buffers (protobuf — бинарный формат для данных).

Цели:
Объединить сервисы в дата-центрах и на устройствах.
Поддержка стриминга для реал-тайма.
Автоматическая генерация кода — один .proto-файл для всех языков.

К 2025 году gRPC — проект CNCF (Cloud Native Computing Foundation), используется в Google Cloud, Netflix (стриминг видео), Uber (поездки в реальном времени), Cisco. За 10 лет обработал триллионы вызовов — доказанная надежность.


Где применяется gRPC: От микросервисов до умных устройств

gRPC — король сценариев с высокой нагрузкой:

Микросервисы: Тысячи маленьких сервисов в Kubernetes (оркестратор контейнеров). Netflix использует для рекомендаций фильмов — миллиарды запросов в секунду без задержек. Внутренняя связь: сервис оплаты "звонит" сервису доставки.
Интернет вещей (IoT): Миллиарды устройств (умные лампочки, датчики). gRPC соединяет их с облаком: низкий трафик, стриминг данных (температура в реальном времени). Пример: умный дом от Google Nest.
Внутренние API: В компаниях — связь backend'ов. Uber: расчет маршрутов между сервисами. Банки: обработка транзакций. Не для клиентов (там REST), а внутри — для скорости.

В 2025: gRPC в AI (TensorFlow), играх (реал-тайм мультиплеер), авто (Tesla — связь машин с облаком).


#Java #middle #gRPC
👍3
Раздел 6. Коллекции в Java

Глава 5. Map — отображения (словари)

Интерфейс Map<K, V> — это часть Java Collections Framework (JCF) из пакета java.util, который представляет структуру для хранения ассоциативных данных. В отличие от других коллекций, таких как List или Set, которые хранят отдельные элементы, Map хранит пары, где каждый ключ (K) связан с значением (V). Это позволяет быстро находить значение по ключу, как в словаре или телефонной книге.

Основные понятия:
Ключ (Key): Уникальный идентификатор для доступа к значению. Ключи не могут дублироваться — если добавить пару с существующим ключом, значение перезапишется.
Значение (Value): Данные, ассоциированные с ключом. Значения могут дублироваться.
Пара (Entry): Единица хранения в Map — комбинация ключа и значения.


Map моделирует математическое отображение (mapping), где каждый ключ maps to ровно одно значение. Это делает Map идеальным для сценариев, где нужна ассоциация, например, ID пользователя — профиль, слово — перевод.

Отличия от Collection:
Map не расширяет Collection<E> — это отдельная ветвь JCF.
Collection — последовательность элементов, Map — ассоциативный массив.
В Map нет индексации (нет get(int index)), доступ только по ключу.
Размер Map — количество пар, не элементов.


Generics в Map: <K, V> обеспечивает типобезопасность: ключи одного типа (например, Integer), значения другого (String). Без generics (raw Map) — устарело и небезопасно.


Хранение пар «ключ–значение»: Особенности

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

Уникальность ключей:
Ключи всегда уникальны: Map не позволяет дубликаты ключей. Если ключ уже существует, значение обновляется.
Уникальность определяется методами equals() и hashCode() (в hash-based реализациях) или compareTo() (в sorted).
Нюанс: Для custom ключей обязательно переопределите equals() и hashCode() — иначе уникальность по ссылке, не по значению.


Дубликаты значений:
Значения могут повторяться: Несколько ключей могут ссылаться на одно значение.
Нюанс: Если значение — mutable объект, изменения в одном месте отразятся везде (по ссылке).


Null в Map:
Ключи: Большинство реализаций позволяют один null-ключ (HashMap, LinkedHashMap), но TreeMap — нет (NullPointerException).
Значения: Null разрешен всегда.


Порядок в Map:
В общем случае нет (зависит от реализации): Не полагайтесь на порядок пар.
Нюанс: Map не упорядочен, как List, но некоторые реализации добавляют порядок.


Размер и емкость:
Размер (size()) — количество пар.
Емкость: В hash-based — initial capacity и load factor (например, 0.75 — при заполнении >75% ресайз).


Производительность:
В среднем O(1) для доступа по ключу в hash-based, O(log n) в tree-based.
Нюанс: Зависит от качества hashCode() — плохие хэши приводят к деградации до O(n).



Полезные советы для новичков

Выбор типов K/V: Ключи — immutable (String, Integer), чтобы избежать изменений, влияющих на хэш.
Custom ключи: Переопределяйте equals/hashCode (IDE поможет: Generate → equals() and hashCode()).
Map vs другие коллекции: Используйте Map для ассоциаций, Set для уникальных элементов, List для последовательностей.


#Java #для_новичков #beginner #Map
👍4
Архитектура gRPC: как всё работает под капотом

gRPC — это современный фреймворк удалённого вызова процедур (RPC, Remote Procedure Call), разработанный Google. Он позволяет приложениям общаться друг с другом как будто они вызывают локальные функции, хотя на самом деле взаимодействие идёт по сети. Чтобы понять, почему gRPC так эффективен, нужно разобрать его архитектуру и то, что происходит «под капотом».

1. Концепция gRPC: RPC-модель нового поколения

RPC (Remote Procedure Call) — это подход, при котором одна программа может вызвать функцию, которая физически исполняется на другом сервере.
В классической модели RPC разработчик просто вызывает метод, а инфраструктура берёт на себя всё — упаковку данных, передачу по сети и распаковку на другой стороне.


gRPC реализует эту идею, но в современном, высокопроизводительном виде — поверх HTTP/2 и с использованием Protocol Buffers для сериализации.

2. Основные участники архитектуры

Клиент (Client)
Это программа, которая инициирует вызов удалённого метода. Она не знает деталей того, как сервер устроен внутри.
Клиент работает с client stub — это локальный объект, который выглядит как обычный класс с методами, но при вызове каждого метода на самом деле выполняется сетевое обращение к серверу.


Сервер (Server)
Это приложение, которое реализует интерфейс, описанный в .proto файле. Сервер принимает запросы от клиентов, обрабатывает их и отправляет ответы.


Client Stub и Server Stub

Что такое Stub
Stub — это «заглушка», или точнее — сгенерированный код, который связывает ваш код с gRPC-инфраструктурой.

Client Stub (клиентская заглушка) — это класс, который содержит методы, соответствующие сервисам, определённым в .proto.
Когда вы вызываете метод stub.buyCar(request), gRPC автоматически:

Сериализует объект request в бинарный формат.


Отправляет его по сети через HTTP/2.

Получает ответ, десериализует и возвращает его как обычный объект
Java.

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


Пример:
// .proto файл
syntax = "proto3";

service CarService {
rpc BuyCar (CarRequest) returns (CarResponse);
}

message CarRequest {
string model = 1;
int32 budget = 2;
}

message CarResponse {
string status = 1;
}


После генерации protoc создаёт классы:
CarServiceGrpc.CarServiceImplBase — Server Stub

CarServiceGrpc.CarServiceBlockingStub и CarServiceGrpc.CarServiceFutureStub — Client Stubs


Реализация сервера:
public class CarServiceImpl extends CarServiceGrpc.CarServiceImplBase {
@Override
public void buyCar(CarRequest request, StreamObserver<CarResponse> responseObserver) {
String result = "You bought: " + request.getModel();
CarResponse response = CarResponse.newBuilder()
.setStatus(result)
.build();

responseObserver.onNext(response);
responseObserver.onCompleted();
}
}


Клиент:
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
.usePlaintext()
.build();

CarServiceGrpc.CarServiceBlockingStub stub = CarServiceGrpc.newBlockingStub(channel);

CarRequest request = CarRequest.newBuilder()
.setModel("Tesla Model 3")
.setBudget(50000)
.build();

CarResponse response = stub.buyCar(request);
System.out.println(response.getStatus());



#Java #middle #gRPC
👍2
3. Роль Protocol Buffers (protobuf)

Protocol Buffers — это бинарный формат сериализации данных, разработанный Google. Он выполняет две функции:

Описание структуры данных (через .proto файл).
Это аналог схемы JSON или XML, но строгий и типизированный.

Сериализация и десериализация (преобразование объектов в компактную бинарную форму и обратно).

Пример .proto файла не только описывает сообщения, но и определяет сервис (то есть API интерфейс).

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


4. Как происходит сериализация и десериализация


Сериализация — это процесс превращения объекта в поток байтов для передачи по сети.
Десериализация — обратный процесс.


В gRPC:
Клиент вызывает метод stub.method(request).
request сериализуется с помощью Protocol Buffers в бинарный поток.
Поток отправляется через HTTP/2.
Сервер принимает поток, десериализует его обратно в объект CarRequest.
После обработки сервер сериализует ответ (CarResponse) и отправляет обратно.

Важно: gRPC сам управляет сериализацией. Вам не нужно ничего кодировать вручную — всё делает сгенерированный stub.


5. Что делает protoc и зачем нужны плагины

protoc — это компилятор Protocol Buffers. Он принимает .proto файл и генерирует исходный код для нужного языка.

Например:
protoc --java_out=./build/generated proto/car.proto


gRPC добавляет ещё один плагин — --grpc-java_out, который генерирует код для stub'ов.
protoc --plugin=protoc-gen-grpc-java=path/to/protoc-gen-grpc-java \
--grpc-java_out=./build/generated \
--java_out=./build/generated \
proto/car.proto


Таким образом, protoc создаёт:
Классы-сообщения (CarRequest, CarResponse)
gRPC классы (CarServiceGrpc, Stub и ImplBase)


Для каждого языка есть свой плагин:
--grpc-java_out для Java
--grpc-python_out для Python
--grpc-go_out для Go
и т. д.


Это и есть причина, почему gRPC мультиплатформенный — интерфейс описывается один раз в .proto, а код для всех языков генерируется автоматически.

6. Почему gRPC быстрее REST

gRPC построен поверх HTTP/2, а REST — чаще всего поверх HTTP/1.1. Разница принципиальна.

Ключевые причины производительности:
HTTP/2 поддерживает мультиплексирование — можно отправлять несколько запросов в одном соединении без блокировки.
Сжатие заголовков (HPACK) уменьшает накладные расходы.
Бинарная сериализация (protobuf) — меньше данных, быстрее парсинг.
Постоянное соединение — нет затрат на открытие/закрытие TCP для каждого запроса.
Streaming — можно передавать поток данных, а не ждать полного ответа (например, поток логов или большого файла).


7. Суммарно: что происходит при вызове метода в gRPC

Пошагово:
Клиент вызывает метод stub.someMethod(request).
Stub сериализует объект через protobuf.
Сериализованные данные упаковываются в HTTP/2 фрейм и отправляются на сервер.
Сервер принимает фрейм, десериализует данные.
Вызвается метод реализации (ImplBase).
Сервер формирует ответ, сериализует через protobuf.
Ответ отправляется обратно по тому же соединению.
Клиент получает и десериализует ответ.


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



#Java #middle #gRPC
👍2
Раздел 6. Коллекции в Java

Глава 5. Map — отображения (словари)

Реализации: HashMap, LinkedHashMap, TreeMap и остальные

JCF предоставляет богатый набор реализаций Map, каждая оптимизирована под конкретные нужды.

Все они реализуют Map<K, V>, но различаются по:
Хранению: Хэш-таблица (HashMap), связный список + хэш (LinkedHashMap), красно-черное дерево (TreeMap).
Порядку: Нет (HashMap), вставки (LinkedHashMap), сортировка по ключам (TreeMap).
Производительности: O(1) для хэш, O(log n) для дерево.
Дополнительно: Специальные для enum, слабых ссылок и т.д.



1. HashMap<K, V>: Основная реализация

Описание: HashMap — сердце Map в Java, основанная на хэш-таблице. Хранит пары в массиве "бакетов" (buckets), где каждый бакет — список узлов (node) с ключом, значением и хэш-кодом.

Внутренняя структура:
Массив Node<K,V>[] table (initial capacity 16, load factor 0.75).
При put: Вычисляет hash = key.hashCode() ^ (hash >>> 16) для равномерности.
Индекс бакета: hash & (table.length - 1).
Коллизия: LinkedList или Tree (с
Java 8, если > 8 узлов — дерево для O(log n)).
Ресайз: При >75% заполнения — удваивает размер, перехэширует все элементы.


Big O:
put/get/remove/containsKey: O(1) средний, O(n) worst (плохие хэши).
Итерация: O(capacity).


Особенности:
Порядок: Нет (iteration order не гарантирован).
Null: Один null-ключ, несколько null-значений.
Thread-safe: Нет (ConcurrentModificationException при параллельном доступе).
Initial capacity/load factor: new HashMap<>(16, 0.75f) для оптимизации.


Нюансы и ловушки:
hashCode/equals: Критичны! Плохой hashCode — деградация до O(n). Изменение ключа после put — потеря элемента.
Ресайз: O(n) время, но редко.
Java 8+: Tree nodes для коллизий (>8 узлов).
Remove: Если null-ключ — специальная обработка.
Итерация: entrySet(), keySet(), values() — O(capacity), не size().


Пример кода:
javaimport java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) {
Map<String, Integer> ages = new HashMap<>();
ages.put("Алексей", 35);
ages.put("Мария", 28);
ages.put("Алексей", 36); // Перезапись

System.out.println(ages.get("Алексей")); // 36
ages.put(null, 0); // Null-ключ
System.out.println(ages.size()); // 3

// Итерация
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
Когда использовать: 99% случаев — быстрый поиск/кэш (userId -> User).


2. LinkedHashMap<K, V>: HashMap с порядком

Описание: Расширение HashMap с двусвязным списком для сохранения порядка вставки (insertion-order) или доступа (access-order для LRU-кэша).

Внутренняя структура: HashMap + Entry с prev/next ссылками. Два режима: INSERTION_ORDER (default) или ACCESS_ORDER (constructor flag).

Big O: O(1) для put/get/remove, как HashMap.

Особенности:
Порядок: Вставки (по умолчанию) или доступа (get/put обновляет позицию).
Null: Да.
Thread-safe: Нет.

Нюансы:
LRU кэш: new LinkedHashMap<>(16, 0.75f, true) — access-order, removeEldestEntry для eviction.
Итерация: В порядке вставки/доступа.
Ресайз: Как HashMap, но сохраняет порядок.


Пример:
javaimport java.util.LinkedHashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) {
Map<String, Integer> map = new LinkedHashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("A", 3); // Обновляет, но порядок сохраняется

for (String key : map.keySet()) {
System.out.println(key); // A, B — порядок вставки
}

// Access-order
Map<String, Integer> lru = new LinkedHashMap<>(16, 0.75f, true);
lru.put("A", 1);
lru.get("A"); // "A" перемещается в конец
}
}
Когда использовать: Кэш с порядком (LRU), когда нужен predictable iteration.



#Java #для_новичков #beginner #Map #HashMap #LinkedHashMap #TreeMap #Hashtable #IdentityHashMap #EnumMap #WeakHashMap
👍2
3. TreeMap<K, V>: Отсортированная Map

Описание: Реализация SortedMap<K, V> на красно-черном дереве (red-black tree). Ключи всегда отсортированы.

Внутренняя структура: Дерево узлов с left/right/child ссылками, цветом для баланса.

Big O:
O(log n) для put/get/remove/containsKey.

Особенности:
Порядок: Сортировка по ключам (Comparable или Comparator).
Null-ключ: Нет (NPE).
Null-значение: Да.
Thread-safe: Нет.


Нюансы:
Comparator: new TreeMap<>(comparator) для custom сортировки.
Дополнительные методы: firstKey(), lastKey(), headMap(K to), tailMap(K from), subMap.
Custom ключи: Comparable<K> или Comparator.
Баланс: Автоматический, O(log n) гарантировано.


Пример:
javaimport java.util.TreeMap;
import java.util.Map;

public class Main {
public static void main(String[] args) {
Map<Integer, String> map = new TreeMap<>();
map.put(3, "Три");
map.put(1, "Один");
map.put(2, "Два");

for (Integer key : map.keySet()) {
System.out.println(key + ": " + map.get(key)); // 1: Один, 2: Два, 3: Три
}

System.out.println(map.firstKey()); // 1
}
}
Когда использовать: Отсортированные ключи (диапазонные запросы, алфавитный словарь).



Остальные реализации: Hashtable, IdentityHashMap, EnumMap, WeakHashMap

Hashtable<K, V> (устаревшая)

Описание: Первая Map в Java (1.0), synchronized версия HashMap.
Особенности: Thread-safe (synchronized методы), нет null, порядок нет.
Big O: O(1).
Нюансы: Медленнее HashMap (locks на каждый метод), legacy — используйте ConcurrentHashMap.
Когда: Только legacy код.


IdentityHashMap<K, V>

Описание: HashMap с == вместо equals/hashCode (по ссылке).
Особенности: Для объектов, где важна идентичность (graph algorithms).
Big O: O(1).
Нюансы: Load factor 0.5, double hash для коллизий.
Когда: Редко, для identity сравнения.


EnumMap<K extends Enum<K>, V>

Описание: Оптимизированная Map для enum ключей (массив вместо хэш).
Особенности: O(1), порядок enum, нет null-ключа.
Нюансы: Ключи — enum, values any.
Когда: State machines, enum configs.


WeakHashMap<K, V>

Описание: HashMap с weak keys (GC может удалить entry, если ключ недостижим).
Особенности: Для кэшей, где память критична.
Big O: O(1).
Нюансы: Values strong, cleanup не мгновенный.
Когда: Canonicalizing mappings (interning).


Полезные советы для новичков

HashMap 95% случаев: Начните с неё.
LinkedHashMap для кэша: removeEldestEntry для LRU.
TreeMap для сортировки: Comparator для reverse.
Custom ключи: IDE генерирует equals/hashCode.
Initial capacity: new HashMap<>(expectedSize) для избежания ресайза.
Ошибки: NPE в TreeMap с null-ключом; ClassCast в TreeMap без Comparable.


#Java #для_новичков #beginner #Map #HashMap #LinkedHashMap #TreeMap #Hashtable #IdentityHashMap #EnumMap #WeakHashMap
👍3
Protocol Buffers: сердце gRPC

Если gRPC — это двигатель взаимодействия сервисов, то Protocol Buffers (protobuf) — это его сердце.
Именно protobuf определяет, как описываются данные, как они сериализуются, и как из одной схемы генерируются типобезопасные классы для разных языков.


Чтобы по-настоящему понимать gRPC, нужно уверенно работать с .proto-файлами.

1. Что такое .proto файл

.proto — это файл описания структуры данных и интерфейсов (API).

Он играет сразу три роли:
Документирует контракт между клиентом и сервером (описывает, какие методы и какие данные доступны).
Генерирует код для разных языков с помощью protoc (компилятора Protocol Buffers).
Определяет схему сериализации — то, как объекты превращаются в байты и обратно.
Фактически .proto — это единый источник правды для вашего API.



2. Базовая структура .proto файла

Пример простого файла:
syntax = "proto3";

package car;

option java_multiple_files = true;
option java_package = "com.example.car";
option java_outer_classname = "CarProto";

// Определение сообщений
message Car {
string model = 1;
int32 year = 2;
CarStatus status = 3;
}

// Перечисление (enum)
enum CarStatus {
ACTIVE = 0;
INACTIVE = 1;
}

// Определение сервиса
service CarService {
rpc BuyCar (CarRequest) returns (CarResponse);
}

message CarRequest {
string model = 1;
}

message CarResponse {
string confirmation = 1;
}



3. Ключевые элементы .proto

3.1. syntax

Первая строка файла:
syntax = "proto3";


Обязательно указывает версию синтаксиса.
На практике используется только proto3, потому что она проще, строже типизирована и лучше поддерживается в gRPC.


3.2. package

Задает логическое пространство имён, чтобы избежать конфликтов:
package car;


В
Java и других языках это превращается в пакеты/модули.

3.3. option

Позволяет задавать настройки генерации кода, например:
option java_package = "com.example.car";
option java_multiple_files = true;
option java_outer_classname = "CarProto";


Без этого весь код попадёт в один файл, что неудобно для больших схем.

3.4. message — описание структуры данных

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


Пример:
message User {
string name = 1;
int32 age = 2;
repeated string hobbies = 3;
}


string name = 1; — поле с типом string и номером 1.

int32 age = 2; — целочисленное поле.

repeated string hobbies = 3; — массив строк.

Важно: номер поля (= 1, = 2, = 3) — это не просто индекс. Это ключ в бинарной сериализации, который должен быть уникален и неизменен.


3.5. enum — перечисление значений

enum — это список допустимых констант.
enum CarStatus {
ACTIVE = 0;
INACTIVE = 1;
SOLD = 2;
}


Значение 0 обязательно — это значение по умолчанию.

При сериализации хранится не текстовое имя ("ACTIVE"), а его числовое значение (0), что делает protobuf компактным.


3.6. service — описание API

service определяет набор удалённых методов, которые сервер предоставляет клиенту.

Это аналог интерфейса в Java:
service CarService {
rpc BuyCar (CarRequest) returns (CarResponse);
rpc ListCars (Empty) returns (CarList);
}


Каждый rpc определяет:
имя метода (BuyCar),
входной тип (CarRequest),
выходной тип (CarResponse).


4. Типы данных в Protocol Buffers

Protobuf поддерживает ограниченный, но универсальный набор типов.

Некоторые часто используемые:

string - Текст
bool - Логическое значение
int32, int64 - Целые числа
float, double - Числа с плавающей точкой
bytes - Массив байтов
repeated - Массив
map<key, value> - Словарь

Пример:
message Garage {
map<string, Car> cars = 1;
}



#Java #middle #gRPC #proto
👍2
5. Нумерация полей — почему это критично

Каждое поле имеет свой уникальный номер — это его идентификатор в бинарном потоке.
message Car {
string model = 1;
int32 year = 2;
}


Если поменять номера, клиент и сервер перестанут понимать друг друга.
Например, если у старой версии клиента year = 2, а у новой year = 3, при сериализации они будут читать разные данные.



6. Почему важно резервировать поля

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


Пример:
message Car {
string model = 1;
reserved 2; // резервируем номер
reserved "status_old"; // резервируем имя
}


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


7. Эволюция и миграция схем (Schema Evolution)

Protobuf специально спроектирован так, чтобы позволять обновлять схемы без поломки совместимости.
Главное — соблюдать несколько правил.


Что можно делать безопасно:
Добавлять новые поля с новыми номерами.
Удалять поля (с их резервированием).
Изменять имя поля (номер должен остаться прежним).
Изменять порядок полей — не влияет на сериализацию.


Что делать нельзя:
Менять тип поля (например, int32 → string).
Менять номер поля.
Удалять поле без reserved.


Пример миграции

Старая версия:
message User {
string name = 1;
int32 age = 2;
}


Новая версия:
message User {
string name = 1;
reserved 2;
string email = 3;
}


Старый клиент, который не знает про email, просто проигнорирует это поле.
А новый клиент не столкнётся с конфликтом, потому что старый 2 зарезервирован.



8. Компиляция .proto файла и генерация кода

protoc — компилятор, который читает .proto и создаёт Java-классы.

Пример команды:
protoc \
--java_out=./build/generated \
--grpc-java_out=./build/generated \
proto/car.proto


Результат:
Для каждого message создаются классы с Builder-паттерном.
Для service создаются классы CarServiceGrpc, CarServiceImplBase, CarServiceStub.



9. Пример полного цикла

Файл car.proto:
syntax = "proto3";

service CarService {
rpc BuyCar (CarRequest) returns (CarResponse);
}

message CarRequest {
string model = 1;
int32 budget = 2;
}

message CarResponse {
string message = 1;
}


Сгенерированный код в Java (упрощённо):
// Отправитель (клиент)
CarRequest request = CarRequest.newBuilder()
.setModel("BMW")
.setBudget(20000)
.build();

CarResponse response = stub.buyCar(request);
System.out.println(response.getMessage());


Серверная реализация:
public class CarServiceImpl extends CarServiceGrpc.CarServiceImplBase {
@Override
public void buyCar(CarRequest request, StreamObserver<CarResponse> responseObserver) {
String msg = "Car purchased: " + request.getModel();
CarResponse response = CarResponse.newBuilder().setMessage(msg).build();

responseObserver.onNext(response);
responseObserver.onCompleted();
}
}


#Java #middle #gRPC #proto
👍3