Java for Beginner
673 subscribers
541 photos
155 videos
12 files
827 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

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

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Глубокое изучение типа float в Java: сравнение с double и целочисленными типами

Тип float — один из двух примитивных типов с плавающей точкой в Java. Он используется для хранения чисел с десятичной частью и обеспечивает определённый баланс между точностью и потреблением памяти. Несмотря на свою "простоту", float имеет множество нюансов, особенно в сравнении с double и целочисленными типами (int, long и т. д.), и может вести себя неожиданно, если не понимать его природу.

Что такое float в Java

float — это 32-битный (4 байта) тип данных, реализующий стандарт IEEE 754 для представления чисел с плавающей точкой.

Это означает, что число хранится в следующем формате:
1 бит — знак числа
8 бит — экспонента
23 бита — мантисса (дробная часть)


Таким образом, float может хранить числа приблизительно в диапазоне от ±1.4 × 10^-45 до ±3.4 × 10^38 с точностью около 6–7 значащих десятичных цифр.

Чтобы обозначить литерал как float, нужно явно указать f или F:
float pi = 3.1415927f;
Без этого литерал будет воспринят как double по умолчанию, что приведет к ошибке компиляции при попытке неявного присваивания.


Сравнение с double

double — это 64-битный тип, также реализующий IEEE 754, но имеющий:
1 бит для знака
11 бит для экспоненты
52 бита для мантиссы

Он способен хранить числа от ±4.9 × 10^-324 до ±1.7 × 10^308, с точностью около 15–16 значащих цифр.

То есть:
float — быстрее, но менее точен, занимает меньше памяти
double — точнее, но требует больше памяти и может быть чуть медленнее в вычислениях на некоторых архитектурах


В реальной практике предпочтение обычно отдают double, особенно в финансовых, статистических или инженерных вычислениях, где важна точность. float чаще применяется в графике (например, координаты вершин), машинном обучении, играх и устройствах с ограниченными ресурсами (встраиваемые системы, Android до определённых API-уровней).

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

Целочисленные типы (byte, short, int, long) хранят точные значения и не допускают погрешностей. Они идеальны для подсчётов, индексов, флагов, битовых масок и всего, что не связано с дробями.
В отличие от них, float и double — не точные типы.


Это означает:
Результаты вычислений могут быть неточными из-за ограниченной точности представления дробных чисел.
Сравнение значений на равенство (==) — рискованно и почти всегда плохая идея.


Простые на вид операции могут давать неожиданный результат:
float a = 0.1f + 0.2f;
System.out.println(a == 0.3f); // false
Это связано с тем, что не все десятичные дроби можно точно представить в двоичной системе.


Работа с памятью и производительность

Обе переменные — float и double — примитивные типы и, следовательно, при размещении в стеке (например, внутри метода) не требуют участия сборщика мусора. Они быстро выделяются и удаляются вместе с фреймом стека. Однако, если переменные — поля объекта, то они хранятся в куче, и их "жизненный цикл" зависит от объекта.

С точки зрения производительности:
На современных процессорах разница между float и double минимальна.
Некоторые GPU и встраиваемые процессоры всё ещё используют float как основной тип с плавающей точкой.
На JVM оба типа оптимизируются, но float может быть чуть быстрее при большом объеме операций и памяти.


#Java #для_новичков #beginner #float
Особенности и подводные камни

Погрешность и потеря точности
Каждое присваивание или операция с float может сопровождаться потерей точности. Например:
float a = 1_000_000;
float b = a + 0.0001f;
System.out.println(a == b); // true — потерялась дробная часть


Нормализованные и денормализованные числа

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

NaN, Infinity и -Infinity

float поддерживает специальные значения:
Float.NaN — результат недопустимых операций (например, 0.0f / 0.0f)
Float.POSITIVE_INFINITY и Float.NEGATIVE_INFINITY — результат переполнения или деления на 0
Эти значения не вызывают исключений, и с ними можно работать, но это требует осторожности.


Сравнение на равенство

Из-за округлений не следует использовать == для сравнения двух float.


Вместо этого используют допустимую погрешность:
float a = 0.1f + 0.2f;
float b = 0.3f;
if (Math.abs(a - b) < 1e-6) {
System.out.println("Равны с учетом погрешности");
}


Приведение типов

При смешанных операциях с float и целочисленными типами Java автоматически приводит меньший тип к float.

Например:
int x = 3;
float y = 2.5f;
float result = x + y; // x преобразован в float
Это не вызывает проблем, но может повлиять на точность, если целое число очень большое.


Двоичное представление и неожиданное округление

Некоторые десятичные дроби (например, 0.1, 0.2) не могут быть точно представлены в двоичной системе. Это приводит к накапливающимся погрешностям, особенно при работе с циклами или большими массивами данных.


Когда использовать float, а когда — double


Используй float, если:
Работаешь в среде с ограниченной памятью или производительностью (например, Android, микроконтроллеры)
Требуется снизить объем данных (например, передача координат в 3D-движке)
Максимальная точность не критична


Используй double, если:
Точность важна (финансовые расчеты, физические симуляции)
Объёмы данных позволяют использовать больше памяти
Не хочешь постоянно контролировать потерю точности


#Java #для_новичков #beginner #float
Продолжаем выбирать темы для разбора и голосовать за рассмотрение предложенных! 🤓

Голосуем за тему к рассмотрению в эти выходные!

Выбираем новую тему!
(можете предложить что-то из того что предлагали на прошлой неделе и что проигрывает в голосовании!)

Не стесняемся! ✌️
Please open Telegram to view this post
VIEW IN TELEGRAM
Что выведет код?

public class Task280525 {
public static void main(String[] args) {
float f = 16777216f;
System.out.println(f == (f + 1f));
}
}


#Tasks
Как я память искал (Часть I)

Совсем недавно стал свидетелем неочевидной проблемы, когда вроде бы полностью протестированный стабильный сервис, по непонятным причинам падает на проде с ошибкой OutOfMemory.

Причинами и способами решения, сегодня я решил поделиться с Вами
.

Вводные данные:
🔵Основная сущность (к примеру User) и связанная с ней через one-to-many, вторичная сущность (пусть будет Car).
🔵Сущности описаны по стандарту Spring JPA в коде.
🔵По запросу бизнеса, при получении списка основных сущностей должна применяться пагинация, фильтрация и сортировка (для корректного отображения на web-странице).
🔵Само собой при получении основных сущностей, нужно подгружать все связанные, а так же иметь возможность фильтрации и сортировки по содержащихся в них данных.

Предложенное решение (как показала практика - неверное):
🔵Для решения подобного запроса использовать стандартные средства Spring, такие как Pageable и для формирования сложного SQL запроса средствами Java - Specification

Примерный код:

🧍 User
@Entity
public class User {
//стандартные поля
@OneToMany(mappedBy = "owner", fetch = FetchType.LAZY)
private List<Car> cars = new ArrayList<>();
}


🚗 Car
@Entity
public class Car {
//стандартные поля
@ManyToOne
@JoinColumn(name = "owner_id")
private User owner;
}


Пагинация + fetch join через Specification (без фильтров)

Page<User> users = userRepository.findAll(specification, PageRequest.of(0, 10));


настройка Specification
return (root, query, cb) -> {
root.fetch("cars", JoinType.LEFT);
query.distinct(true);
return cb.conjunction();
};


Проблема:
🔵Хотя предложенное выше решение и выглядит очевидным при озвученном кейсе требований и решении проблемы N+1, оно создает проблему о которой Вам не расскажут на хайповых курсах и видео уроках.

Вот примеры:
Пример 1
Пример 2

🔵Суть проблемы в том, что когда вы используете JOIN FETCH (например, через Specification), Hibernate подгружает связанные сущности в один SQL-запрос (чтобы избежать N+1).

Однако в сочетании с пагинацией (Pageable) Hibernate теряет корректность подсчёта количества строк и может загрузить всю таблицу в память, чтобы затем вручную "отрезать" нужную страницу на уровне Java, тем самым использовав ВСЮ выделенную JVM память для хранения
.

О последствиях такого непредсказуемого поведения можете посудить сами. 😱


Что происходит, подробно?

Когда вы вызываете, например:
Page<User> users = userRepository.findAll(specification, PageRequest.of(0, 10));


Hibernate должен выполнить:
SELECT COUNT(*) ... — чтобы узнать общее количество строк.
SELECT ... LIMIT 10 OFFSET 0 — чтобы получить только первую страницу.


❗️Но fetch join меняет семантику запроса

Когда вы пишете JOIN FETCH, например:
root.fetch("cars", JoinType.LEFT);

Или:
criteriaQuery.distinct(true);


Hibernate генерирует SQL примерно такого вида:
SELECT u.*, r.* FROM users u
LEFT JOIN roles r ON r.user_id = u.id


При этом:
Если у одного пользователя 3 cars, то он появится 3 раза в результате SQL-запроса.
Hibernate потом вручную собирает дубликаты в одну сущность User, у которой будет List<Car> с 3 элементами.
LIMIT/OFFSET применяются к строкам SQL, а не к "собранным" сущностям — и это вызывает проблемы.


⚠️ Проблема: LIMIT работает до агрегации

Hibernate не может корректно объединить дубликаты после применения LIMIT, потому что:

При использовании fetch join, результат SQL-разворачивается в несколько строк (по связям).
Но LIMIT обрезает эти строки до того, как Hibernate агрегирует их в объекты Java.
Поэтому Hibernate игнорирует LIMIT в SQL, чтобы корректно собрать сущности → он загружает все строки в память, затем отрезает нужную страницу на уровне Java.


🤯 Что я получил в результате использования неверного решения

🔜 Упавший сервис на проде 😨 (так как на тестовых средах, объем загружамых данных был в разы меньше).
🔜 Бесценный опыт, которым сейчас делюсь с Вами ☺️

А завтра я расскажу как решить данный кейс и как не попасть в подобную ловушку неочевидного поведения Hibernate ...

Понравился стиль подачи материала?

Отправь другу и ставь -
🔥

#Java #fetch #autor
Please open Telegram to view this post
VIEW IN TELEGRAM
В чем разница между ArrayList и LinkedList? 🤓

Ответ:

ArrayList использует динамический массив, быстрый доступ по индексу (O(1)), но медленные вставки/удаления в середине (O(n)).

LinkedList использует двухсвязный список, быстрые вставки/удаления (O(1)), но медленный доступ по индексу (O(n)).


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Структура и основные команды Liquibase

1. Файл конфигурации (liquibase.properties)

Файл liquibase.properties содержит настройки для подключения к БД и управления Liquibase.

Основные параметры:
# Подключение к БД  
url=jdbc:postgresql://localhost:5432/mydb
username=user
password=pass
driver=org.postgresql.Driver

# Настройки changelog
changeLogFile=db/changelog/db.changelog-master.xml

# Дополнительные параметры
liquibase.hub.mode=off # Отключение Liquibase Hub (если не используется)


Разбор параметров:
url – JDBC-URL базы данных (зависит от СУБД).
username и password – учетные данные для подключения.
driver – класс JDBC-драйвера (например, org.postgresql.Driver для PostgreSQL).
changeLogFile – путь к главному файлу changelog.


Где размещается?

В корне проекта (рядом с pom.xml/build.gradle).

Или указывается явно при запуске:
liquibase --defaults-file=config/liquibase.properties update


2. Основные команды CLI


Liquibase предоставляет консольные команды для управления миграциями.

update – применение изменений

Применяет все невыполненные changeSet’ы из changelog.
liquibase update


Что происходит?
Liquibase проверяет таблицу DATABASECHANGELOG.
Находит changeSet’ы, которых нет в этой таблице.
Применяет их в порядке указания в changelog.

rollback – откат изменений

Возвращает БД к предыдущему состоянию.
# Откат до тега v1.0  
liquibase rollback v1.0

# Откат последнего changeSet’а
liquibase rollbackCount 1

# Откат до определенной даты
liquibase rollbackToDate 2024-01-01


status – проверка состояния БД

Показывает, какие changeSet’ы не применены.
liquibase status


Вывод:
2 changesets have not been applied to mydb@jdbc:postgresql://localhost:5432/mydb


validate – проверка корректности changelog

Проверяет синтаксис changelog без применения изменений.
liquibase validate


Если есть ошибки, выведет сообщение, например:
ERROR: ChangeSet db/changelog/changes/001-create-table.xml::1::alex failed. Reason: Table 'users' already exists


3. Жизненный цикл изменений


Как Liquibase применяет changeSet’ы?

Парсинг changelog:
Liquibase читает главный файл (например, db.changelog-master.xml).
Загружает все вложенные changeSet’ы.


Проверка DATABASECHANGELOG:
Сравнивает список changeSet’ов с теми, что уже выполнены (хранятся в таблице DATABASECHANGELOG).

Применение изменений:
Невыполненные changeSet’ы применяются в порядке их объявления.
После успешного выполнения информация о changeSet’е записывается в DATABASECHANGELOG.


Контроль версий и порядок выполнения

Уникальность changeSet’а определяется по:
id (например, "1").
author (например, "alex").
Путь к файлу changelog.


Порядок выполнения:
<databaseChangeLog>
<include file="db/changelog/changes/001-create-users.xml"/>
<include file="db/changelog/changes/002-add-email.xml"/>
</databaseChangeLog>


Liquibase выполнит 001-create-users.xml раньше, чем 002-add-email.xml.


Атрибуты, влияющие на выполнение:

runOnChange="true" – повторно выполнит changeSet, если его содержимое изменилось.
failOnError="false" – пропустит ошибку (например, если таблица уже существует).


#Java #middle #Liquibase
Что выведет код?

public class Task290525 {
public static void main(String[] args) {
System.out.println(1.0 / 0.0);
}
}


#Tasks
Please open Telegram to view this post
VIEW IN TELEGRAM
Чем отличается HashSet от TreeSet? 🤓

Ответ:

HashSet хранит элементы в хэш-таблице, не упорядочивает их, но работает быстрее (O(1) для добавления/поиска).

TreeSet хранит элементы в
отсортированном виде (на основе красно-черного дерева), но операции медленнее (O(log n)).


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Глубокое изучение типа данных char в Java

Тип char в Java — это единственный примитивный тип, предназначенный для хранения символов. Однако под «символом» в контексте Java подразумевается не просто буква или цифра, а 16-битное значение, соответствующее одному коду Unicode. Это делает char более универсальным, чем аналогичные типы в других языках (например, char в C/C++ — это просто байт).

Тем не менее, с учетом особенностей кодировки Unicode, поведения в выражениях и сравнении с другими примитивами, char — не такой уж простой тип.

Что представляет собой char

В Java char — это целое беззнаковое значение, хранящееся в 16 битах (2 байта). Диапазон значений — от 0 до 65 535. Это соответствует возможным значениям Unicode Code Units, используемым для представления символов в кодировке UTF-16.

В отличие от byte, short, int и других числовых типов, char:
Не поддерживает отрицательные значения.
Не используется в арифметике напрямую (но может быть преобразован в int).
Представляет не число, а код символа, хотя технически это просто целое значение.


Хранение в памяти

Как и все примитивные типы, char хранится в стеке, если используется как локальная переменная, и в куче — если является полем объекта. В обоих случаях он занимает ровно 2 байта (16 бит).

Символ и кодировка Unicode

Java изначально проектировалась с поддержкой международного текста, поэтому char хранит значение по спецификации Unicode. Однако важно понимать: char хранит одну кодовую единицу UTF-16, а не обязательно один графический символ.

Некоторые символы Unicode (например, эмодзи или древние алфавиты) имеют коды за пределами 16 бит. Такие символы представлены в UTF-16 двумя char, известными как суррогатная пара.
Это значит, что один char — это не всегда "один символ", что может привести к ошибкам при разборе строк, итерации по символам или определении длины строки.

Пример:
String s = "😀";
System.out.println(s.length()); // 2, а не 1
Здесь length() возвращает 2, потому что символ представлен двумя char.


Экранирование символов

Литералы char записываются в одиночных кавычках:
char c = 'A';


Java поддерживает экранированные символы, например:
'\n' — перевод строки
'\t' — табуляция
'\'' — апостроф
'\\' — обратный слеш
'\u0041' — Unicode-последовательность (в данном случае это символ 'A')


Unicode-последовательности (\uXXXX) можно использовать даже в имени переменной или в коде, до начала компиляции — они обрабатываются на уровне лексического анализатора, еще до построения синтаксического дерева.

#Java #для_новичков #beginner #char
Поведение в выражениях и преобразования

Несмотря на то, что char — это не числовой тип, он может участвовать в арифметике. В любых выражениях с участием char он неявно преобразуется в int.
char c = 'A';
int x = c + 1; // x == 66


Но обратное требует явного приведения:
char next = (char)(c + 1); // 'B'
Аналогично, попытка присвоить результат арифметики напрямую переменной типа char вызовет ошибку компиляции без приведения.


Сравнение с другими примитивными типами

В отличие от byte, short, int, char — беззнаковый.
Он занимает 2 байта, как short, но short — знаковый (-32 768 до 32 767), а char — 0 до 65 535.
В арифметике char ведет себя как int, автоматически повышая тип.
В отличие от String, char представляет одну кодовую единицу, а не последовательность символов.
В отличие от boolean, char может участвовать в арифметических выражениях и преобразованиях.


Инициализация и значения по умолчанию

Локальные переменные char должны быть явно инициализированы.
Поля классов по умолчанию получают значение \u0000, что соответствует нулевому символу Unicode (не отображается в консоли).


Частые ошибки и подводные камни

Суррогатные пары. При работе со строками, содержащими символы за пределами BMP (Basic Multilingual Plane), важно использовать методы вроде codePointAt() и Character.toChars(), чтобы не потерять часть символа.
Арифметика с char. Все выражения приводятся к int, что может привести к неожиданному переполнению или необходимости явного приведения.
Сравнение символов. Операции c1 < c2 работают корректно, но сравниваются числовые значения Unicode, а не "лексикографическое положение" в каком-либо языке.
Обработка управляющих символов. Символы вроде '\0', '\n', '\r' видны в коде, но не обязательно видны в выводе — их эффект зависит от среды (консоль, IDE, файл и т.д.).


#Java #для_новичков #beginner #char
Что выведет код?

public class Task300525 {
public static void main(String[] args) {
char c = 'A';
c += 1.5;
System.out.println(c);
}
}


#Tasks
Варианты ответа:
Anonymous Quiz
4%
'A'
12%
'B'
12%
'C'
72%
Ошибка компиляции
Продолжаем выбирать темы для разбора и голосовать за рассмотрение предложенных! 🤓

Голосуем за тему к рассмотрению в эти выходные!

Выбираем новую тему!
(можете предложить что-то из того что предлагали на прошлой неделе и что проигрывает в голосовании!)

Не стесняемся! ✌️
Please open Telegram to view this post
VIEW IN TELEGRAM