Java for Beginner
870 subscribers
1.01K photos
275 videos
14 files
1.69K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

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

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Что такое Optional.ifPresentOrElse() и Optional.or()? 🤓

Ответ:

В Java 9 добавлены новые методы Optional: 

ifPresentOrElse(Consumer action, Runnable emptyAction) — выполняет одно действие, если значение есть, и другое, если отсутствует. 

or(Supplier<Optional>) — позволяет "подменить" пустой Optional другим Optional. Также в Java 10 появился orElseThrow() (который стал синонимом get()).

Эти методы делают работу с Optional более функциональной и избавляют от использования isPresent()/get(), что считается анти-паттерном.



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
История технологии сегодня — 7 июля

ℹ️ Кто родился в этот день

Ю́рий Серге́евич О́сипов (род. 7 июля 1936, Тобольск) — советский и российский математик и механикпрофессор. Специалист в области прикладной математики и механики. Основные научные исследования посвящены теории управления, дифференциальным уравнениям и их приложениям.
Создал законченную теорию стабилизации стационарных и периодических динамических систем, описываемых дифференциальными уравнениями с запаздывающим аргументом.
Разработал теорию дифференциальных игр для динамических систем с запаздывающим аргументом.
Открыл конструктивный локальный принцип управления системами с запаздыванием.
Построил теорию антагонистических позиционных дифференциальных игр для другого класса конфликтно управляемых систем с бесконечномерными пространствами фазовых состояний — систем, описываемых дифференциальными уравнениями с частными производными, в первую очередь параболическими.
Цикл работ посвящён созданию теории устойчивого динамического обращения управляемых систем.
Получил серию результатов в смежных направлениях: им выполнен ряд работ по оптимизации форм, в одной из которых был, в частности, дан ответ на вопрос о существовании оптимума, поставленный Ж.-Л. Лионсом.


🌐 Знаковые события

1891 — компания «Американ Экспресс» запатентовала дорожные чеки, послужившие прообразом современных кредитных карт.


#Biography #Birth_Date #Events #07июля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Раздел 11. Работа с файлами, I/O и сетью (NIO.2)

Глава 2. Современный NIO.2 (java.nio.file)

Интерфейс Path – путь к файлу/директории

Интерфейс Path — это ключевая абстракция в Java NIO.2 (New I/O 2), представленная в Java 7 (2011 год, пакет java.nio.file). Он заменил устаревший класс java.io.File и предоставляет платформонезависимый способ работы с путями к файлам и директориям. В отличие от File, который напрямую обращался к файловой системе ОС, Path — это чистая абстракция: объект Path может существовать в памяти JVM, даже если соответствующего файла на диске нет.


Что такое Path

Path (от англ. "путь") — это интерфейс, представляющий иерархическое расположение файла или директории в файловой системе. Главное отличие от старого File в том, что Path — это не файл, а именно путь. Он не знает, существует ли файл, доступен ли он для чтения, и т.д. — эти вопросы решаются через другие классы NIO.2, например Files.

Абстракция — в программировании это способ представления сложной реальности в упрощённом виде, скрывая детали реализации. Path абстрагирует различия между файловыми системами: в Windows путь разделяется обратным слешем C:\Users\file.txt, в Linux и macOS — прямым /home/user/file.txt. Path нормализует эти различия на уровне API.


Создание объекта Path

Основной способ создания — статический метод фабрики Paths.get(). Класс Paths (во множественном числе) — это утилитарный класс-фабрика, который делегирует создание Path текущей файловой системе по умолчанию.

Способ 1: единая строка пути
import java.nio.file.Path;
import java.nio.file.Paths;

// Linux / macOS
Path linuxPath = Paths.get("/home/user/documents/report.txt");

// Windows
Path windowsPath = Paths.get("C:\\Users\\Admin\\file.txt");

При передаче строки с обратными слешами в Windows Paths.get() корректно парсит путь. Важно: в Java-строке обратный слеш — это escape-символ, поэтому в литерале пишется \\, но при чтении из внешнего источника (конфиг, ввод пользователя) это не требуется.

Способ 2: сегменты пути (varargs)
Path path = Paths.get("/home", "user", "documents", "report.txt");
// Результат: /home/user/documents/report.txt

Этот способ удобен при динамической сборке пути из частей. Метод get(String first, String... more) принимает первый сегмент и переменное число дополнительных сегментов (varargs — механизм Java для передачи произвольного количества аргументов одного типа). Сегменты автоматически соединяются разделителем, специфичным для ОС.

Способ 3: относительный путь
Path relative = Paths.get("src", "main", "java", "App.java");
// Результат: src/main/java/App.java (на Unix) или src\main\java\App.java (на Windows)

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

Способ 4: через FileSystem
import java.nio.file.FileSystems;

Path path = FileSystems.getDefault().getPath("/tmp", "data.txt");

FileSystems.getDefault() возвращает объект FileSystem — абстракцию над конкретной файловой системой. Этот подход используется при работе с нестандартными файловыми системами (например, ZIP-архив как файловая система через FileSystems.newFileSystem()).


Ключевые методы интерфейса Path

toString() — строковое представление
Path p = Paths.get("/home/user/file.txt");
System.out.println(p.toString());
// Вывод: /home/user/file.txt

Метод возвращает строку пути в нативном формате ОС. Это не просто геттер — он выполняет конкатенацию сегментов с правильным разделителем.

getFileName() — имя файла или последняя директория
Path p = Paths.get("/home/user/documents/report.txt");
Path fileName = p.getFileName(); // report.txt

Path dir = Paths.get("/home/user/documents");
Path dirName = dir.getFileName(); // documents

Возвращает последний сегмент пути как новый объект Path. Если путь — корень (/), возвращает null.

getParent() — родительская директория
Path p = Paths.get("/home/user/documents/report.txt");
Path parent = p.getParent(); // /home/user/documents

Path root = Paths.get("/");
Path rootParent = root.getParent(); // null

Возвращает путь без последнего сегмента. Если у пути нет родителя (корень или относительный путь из одного сегмента), возвращает null.

getRoot() — корневая часть пути
Path absolute = Paths.get("/home/user/file.txt");
Path root = absolute.getRoot(); // /

Path relative = Paths.get("src/main/java");
Path relRoot = relative.getRoot(); // null

Для абсолютного пути возвращает корневой элемент (/ на Unix, C:\ на Windows). Для относительного — null, так как корень не определён.

getNameCount() — количество сегментов

Path p = Paths.get("/home/user/documents/report.txt");
int count = p.getNameCount(); // 4 (home, user, documents, report.txt)

Возвращает число имен (сегментов) в пути, исключая корень. Корень не считается именем — это отправная точка иерархии.

getName(int index) — сегмент по индексу
Path p = Paths.get("/home/user/documents/report.txt");
Path segment = p.getName(1); // user

Индексация начинается с 0. Индекс 0 — первый сегмент после корня. Выход за границы выбрасывает IllegalArgumentException.

subpath(int beginIndex, int endIndex) — подпуть
Path p = Paths.get("/home/user/documents/report.txt");
Path sub = p.subpath(1, 3); // user/documents

Возвращает подпуть от beginIndex (включительно) до endIndex (исключительно). Индексы отсчитываются от корня, но сам корень в результат не включается.

isAbsolute() — проверка на абсолютность
Path abs = Paths.get("/etc/passwd");
boolean isAbs = abs.isAbsolute(); // true

Path rel = Paths.get("config/app.properties");
boolean isRel = rel.isAbsolute(); // false

Абсолютный путь содержит корневой элемент и однозначно указывает местоположение в файловой системе. Относительный — зависит от текущей директории.

toAbsolutePath() — преобразование в абсолютный
Path rel = Paths.get("config/app.properties");
Path abs = rel.toAbsolutePath();
// Результат: /current/working/dir/config/app.properties

Если путь уже абсолютный — возвращает себя. Иначе резолвит относительно текущей рабочей директории.

normalize() — нормализация пути
Path p = Paths.get("/home/user/../documents/./report.txt");
Path norm = p.normalize(); // /home/documents/report.txt

Удаляет избыточные сегменты: . (текущая директория) и .. (родительская директория). Не проверяет реальное существование файловой системы — работает чисто на уровне строк.

resolve(Path other) — резолюция пути
Path base = Paths.get("/home/user");
Path resolved = base.resolve("documents/report.txt");
// Результат: /home/user/documents/report.txt

Если other абсолютный — возвращает other. Если относительный — присоединяет к base. Эквивалент операции "base/other".

relativize(Path other) — относительный путь между двумя путями
Path base = Paths.get("/home/user");
Path target = Paths.get("/home/user/documents/report.txt");
Path rel = base.relativize(target); // documents/report.txt

Вычисляет, как добраться от base до target относительным путём. Оба пути должны быть либо абсолютными, либо относительными.

startsWith(Path other) / endsWith(Path other)
Path p = Paths.get("/home/user/documents");
boolean starts = p.startsWith(Paths.get("/home")); // true
boolean ends = p.endsWith(Paths.get("documents")); // true

Сравнивают сегменты пути, а не строки. startsWith проверяет, начинается ли путь с указанных сегментов с учётом иерархии.


#Java #для_новичков #beginner #IO #NIO #Path
👍4
toFile() — обратная совместимость
Path path = Paths.get("/tmp/data.txt");
java.io.File legacyFile = path.toFile();

Преобразует Path в старый java.io.File. Полезно при интеграции с legacy-кодом или библиотеками, которые ещё не перешли на NIO.2.

iterator() — итерация по сегментам
Path p = Paths.get("/home/user/file.txt");
for (Path segment : p) {
System.out.println(segment);
}
// Вывод: home, user, file.txt

Path реализует Iterable<Path>, что позволяет использовать for-each для обхода сегментов. Корень в итерацию не включается.

compareTo(Path other) — лексикографическое сравнение
Path p1 = Paths.get("/a/b/c");
Path p2 = Paths.get("/a/b/d");
int result = p1.compareTo(p2); // отрицательное число (c < d)

Сравнивает пути посегментно, учитывая регистр (на системах с case-sensitive файловыми системами). Реализует Comparable<Path>, что позволяет сортировать пути.


Путь байтов в памяти JVM при работе с Path

Когда вы пишете Paths.get("/home/user/file.txt"), в памяти JVM происходит следующее:

1. Создание строки в пуле строк (String Pool)
Литерал "/home/user/file.txt" — это объект String.

При компиляции он попадает в пул строк (String Pool) — специальная область в Metaspace (начиная с Java 8; ранее — PermGen).
Пул строк — это кэш строковых литералов, где одинаковые строки представлены единственным экземпляром. Это экономит память, так как строки в Java иммутабельны (неизменяемы).
Metaspace — область памяти JVM, выделенная под метаданные классов, константы, пул строк и другие внутренние структуры.

В отличие от старого PermGen, Metaspace использует нативную память (native memory) вне кучи и расширяется динамически.

2. Вызов Paths.get() и создание объекта Path
Метод Paths.get() вызывает FileSystems.getDefault().getPath(first, more).
Файловая система по умолчанию (обычно UnixFileSystem на Linux/macOS или WindowsFileSystem на Windows) создаёт конкретную реализацию Path — например, UnixPath или WindowsPath.
Эти объекты создаются в куче (Heap) — основной области памяти для объектов Java.

Объект Path хранит:
Ссылку на файловую систему (FileSystem)
Массив байтов или строк, представляющих сегменты пути
Хэш-код для кэширования

3. Работа GC с объектами Path
Объекты Path — это обычные Java-объекты в куче.
GC (Garbage Collector, сборщик мусора) — это механизм JVM, автоматически освобождающий память от объектов, на которые больше нет ссылок.
Когда переменная Path выходит из области видимости (например, метод завершился, и локальная переменная недоступна), объект становится кандидатом на сборку мусора.
Однако сама строка-путь в пуле строк остаётся — пул строк не подлежит обычной сборке мусора, так как JVM предполагает, что строковые литералы могут понадобиться снова.

Если Path создаётся динамически (например, Paths.get(base, userInput)), и входные данные уникальны при каждом вызове, строки не попадают в пул автоматически (если только не вызван intern()). Такие строки создаются в куче и полностью управляются GC. При частом создании временных путей (например, в цикле обработки 10 000 файлов) объекты Path и связанные строки накапливаются в Young Generation (молодое поколение) кучи.

Young Generation — часть кучи, где размещаются новые объекты. Она делится на Eden (пространство, куда попадают все новые объекты) и два Survivor-пространства (S0, S1). Когда Eden заполняется, запускается Minor GC — быстрая сборка мусора, перемещающая живые объекты в Survivor. Объекты Path, созданные внутри короткоживущего метода, обычно уничтожаются на этом этапе.
Если объект Path кэшируется (например, сохраняется в статическое поле или долгоживущую коллекцию), он переходит в Old Generation (старое поколение, Tenured). Там он живёт до Major GC (или Full GC) — более тяжёлой сборки, которая затрагивает всю кучу. Major GC запускается, когда Old Generation заполняется.

Важно: Path — лёгкий объект. Он не держит открытых файловых дескрипторов (в отличие от FileInputStream). Поэтому его сборка мусора безопасна в любой момент. Однако если Path используется для открытия ресурса (через Files.newInputStream(path)), то сам поток уже требует явного закрытия через try-with-resources, иначе дескриптор останется открытым на уровне ОС, даже если объект Path собран GC.

4. Байты на уровне ОС
Когда Path передаётся в метод Files.readAllBytes(path), JVM через JNI (Java Native Interface — механизм вызова нативного кода) обращается к системному вызову ОС. Путь преобразуется в массив байтов в кодировке, понятной файловой системе (обычно UTF-8). Эти байты передаются в системный вызов open() (POSIX) или CreateFile() (Windows). Прочитанные данные возвращаются в JVM как массив байтов в куче (heap-allocated byte array).

#Java #для_новичков #beginner #IO #NIO #Path
👍5
Интеграции и практические примеры

Пример 1: Обход директории с фильтрацией
import java.nio.file.*;
import java.io.IOException;

public class DirectoryScanner {
public static void scanDirectory(Path root) throws IOException {
// Files.walk — рекурсивный обход дерева директорий
try (Stream<Path> paths = Files.walk(root, 3)) { // глубина 3 уровня
paths.filter(Files::isRegularFile) // только файлы
.filter(p -> p.toString().endsWith(".java"))
.forEach(p -> System.out.println(p.toAbsolutePath()));
}
}
}

Files.walk() возвращает Stream<Path> — ленивый поток (lazy stream), который читает директории по мере необходимости. Это предотвращает загрузку всех путей в память сразу, что критично для больших файловых деревьев.

Пример 2: Резолюция путей в конфигурации приложения
public class ConfigResolver {
private final Path baseDir;

public ConfigResolver(String basePath) {
this.baseDir = Paths.get(basePath).toAbsolutePath().normalize();
}

public Path resolveConfig(String configName) {
// resolve объединяет baseDir с относительным путём
Path resolved = baseDir.resolve("config").resolve(configName);

// безопасность: проверяем, что результирующий путь всё ещё внутри baseDir
if (!resolved.startsWith(baseDir)) {
throw new SecurityException("Path traversal attempt detected");
}
return resolved;
}
}

Этот паттерн защищает от Path Traversal (атака обхода пути), когда злоумышленник передаёт ../../../etc/passwd в качестве configName. Метод startsWith() гарантирует, что результирующий путь остаётся внутри разрешённой директории.

Пример 3: Копирование с проверкой
public void safeCopy(Path source, Path target) throws IOException {
// normalize убирает . и .. перед проверкой
Path normalizedSource = source.toAbsolutePath().normalize();
Path normalizedTarget = target.toAbsolutePath().normalize();

// getParent возвращает директорию назначения
Path targetDir = normalizedTarget.getParent();
if (targetDir != null && !Files.exists(targetDir)) {
Files.createDirectories(targetDir); // создаём всю цепочку директорий
}

// StandardCopyOption — перечисление, определяющее поведение копирования
Files.copy(normalizedSource, normalizedTarget,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.COPY_ATTRIBUTES);
}

StandardCopyOption — это enum (перечисление), тип данных в Java, представляющий фиксированный набор констант. REPLACE_EXISTING разрешает перезапись существующего файла, COPY_ATTRIBUTES копирует метаданные (права доступа, временные метки).

Пример 4: Работа с временными файлами
public Path createTempFileWithPath() throws IOException {
// getDefault() возвращает файловую систему по умолчанию
Path tempDir = FileSystems.getDefault().getPath(System.getProperty("java.io.tmpdir"));

// createTempFile создаёт файл с префиксом и суффиксом
Path tempFile = Files.createTempFile(tempDir, "app_", ".tmp");

System.out.println("Created: " + tempFile.toAbsolutePath());
return tempFile;
}

System.getProperty("java.io.tmpdir") возвращает путь к временной директории ОС. Files.createTempFile гарантирует уникальность имени через атомарную операцию на уровне файловой системы.


Path vs File: ключевые различия

Класс java.io.File (существует с Java 1.0) имеет ряд архитектурных недостатков:
Смешение ответственности: File одновременно представляет и путь, и файл, и директорию. Path разделяет эти концепции.
Ограниченная поддержка символических ссылок: File не различает символическую ссылку и целевой файл. Path в сочетании с Files предоставляет полный контроль.
Отсутствие нормализации: File не предоставляет методов для работы с . и ... Path.normalize() решает это.
Нет поддержки файловых систем, отличных от дефолтной: Path работает с любой FileSystem — ZIP, память, сетевые ФС.
Методы File возвращают boolean вместо исключений: file.delete() возвращает false при ошибке, не объясняя причину. Files.delete(path) выбрасывает IOException с деталями.


#Java #для_новичков #beginner #IO #NIO #Path
👍5
Что выведет код?

import java.nio.file.Path;
import java.nio.file.Paths;

public class Task070726 {
public static void main(String[] args) {
Path p1 = Paths.get("/foo");
Path p2 = Paths.get("/bar");
Path p3 = p1.resolve(p2);
Path p4 = p1.resolve("bar");
System.out.print(p3 + " " + p4);
}
}


#Tasks
👍3
👍3
Что такое Stream.toList() и чем отличается от collect(Collectors.toList())? 🤓

Ответ:

В Java 16 добавлен метод Stream.toList() (без параметров), который возвращает неизменяемый список (immutable).

В отличие от collect(Collectors.toList()), который возвращает мутабельный список (обычно ArrayList). toList() гарантирует, что список нельзя изменить (выбросит UnsupportedOperationException).

Это экономит память и улучшает безопасность. Для мутабельного списка всё ещё используйте collect(Collectors.toCollection(ArrayList::new)) или collect(Collectors.toList()).


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
История технологии сегодня — 8 июля

ℹ️ Кто родился в этот день

И́горь Евге́ньевич Тамм (26 июня (8 июля) 1895 года, Владивосток — 12 апреля 1971, Москва) — советский физик-теоретик, лауреат Нобелевской премии по физике (1958). Основные направления научного творчества Тамма относятся к квантовой механикефизике твёрдого тела, теории излучения, ядерной физикефизике элементарных частиц, а также к решению ряда прикладных задач. В 1932 году опубликовал работу, в которой теоретически предсказал существование поверхностных состояний на поверхности твёрдого тела (этот вид поверхностных состояний сейчас известен как состояния Тамма).

Пётр Леони́дович Капи́ца (26 июня [8 июля] 1894, Кронштадт — 8 апреля 1984, Москва) — советский физик, инженер и инноватор, нобелевский лауреат (1978). Лауреат Нобелевской премии по физике за открытие явления сверхтекучести жидкого гелия, ввёл в научный обиход термин «сверхтекучесть». Известен также работами в области физики низких температур, изучении сверхсильных магнитных полей и удержания высокотемпературной плазмы. Разработал высокопроизводительную промышленную установку для сжижения воздуха на базе турбодетандера.
(Почитайте биографию, как решение властей СССР изменило жизнь человека во благо страны)

Никола́й Никола́евич Бенардо́с (8 (26) июня 1842, Бенардосовка, Херсонская губерния — 21 (8) сентября 1905, Фастов, Киевская губерния)русский инженер и изобретатель греческого происхождения, создатель электрической дуговой сварки (метод «Электрогефест», 1881). Автор около 200 изобретений в различных областях техники, ему также принадлежит разработка точечной и шовной контактной сварки.

Граф Фердина́нд Адо́льф Ге́нрих А́вгуст фон Це́ппелин (нем. Ferdinand Adolf Heinrich August Graf von Zeppelin; 8 июля 1838, Констанц, Великое герцогство Баден, Германский союз — 8 марта 1917, Берлин, Германская империя) — немецкий изобретатель и военный деятель, конструктор многочисленных дирижаблей.


🌐 Знаковые события

2011 — начался последний полёт шаттла «Атлантис», завершивший эксплуатацию кораблей системы «Space Shuttle».


#Biography #Birth_Date #Events #08июля
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1
[Совет по Java #058]

Тема: ReentrantLock не освобождается автоматически (в отличие от synchronized).

Проблема: ReentrantLock требует явного вызова метода unlock() для освобождения блокировки.

Если разработчик забывает это сделать или между lock() и unlock() возникает исключение, блокировка остается захваченной навсегда. Это приводит к взаимоблокировкам (deadlock) и остановке потоков, которые пытаются захватить эту блокировку.

В отличие от synchronized, который автоматически освобождает монитор при выходе из блока (включая исключения), ReentrantLock не предоставляет такой гарантии. Ошибки ручного управления особенно опасны в сложных цепочках вызовов, где легко пропустить unlock. Это делает ReentrantLock более гибким, но и более рискованным инструментом.

Решение: Всегда обрамляйте вызов lock() конструкцией try-finally, где unlock() вызывается в блоке finally.

Это гарантирует, что блокировка будет освобождена даже при возникновении исключения. Для операций с таймаутами используйте tryLock(long, TimeUnit), который возвращает boolean, и проверяйте результат. В высоконагруженных системах применяйте tryLock() с таймаутом и повторными попытками для предотвращения бесконечного ожидания.

При работе с Condition также требуется try-finally. Для упрощения кода можно использовать вспомогательные методы, но они не отменяют необходимости явного освобождения.

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class ReentrantLockFinally {

private final ReentrantLock lock = new ReentrantLock();

//Антипаттерн: без finally — риск deadlock
public void badMethod() {
lock.lock();
// Если здесь произойдет исключение, lock не освободится
doWork();
lock.unlock(); // Может не выполниться
}

//Правильно: try-finally
public void goodMethod() {
lock.lock();
try {
doWork();
} finally {
lock.unlock(); // Гарантированно вызовется
}
}

//tryLock с таймаутом
public boolean tryLockWithTimeout() throws InterruptedException {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
doWork();
return true;
} finally {
lock.unlock();
}
} else {
System.out.println("Не удалось захватить блокировку");
return false;
}
}

//Использование Condition (тоже с finally)
public void conditionExample() throws InterruptedException {
var condition = lock.newCondition();
lock.lock();
try {
condition.await(1, TimeUnit.SECONDS);
} finally {
lock.unlock();
}
}

private void doWork() {
// критическая секция
}
}

Объяснение: ReentrantLock — это явная блокировка, предоставляющая расширенные возможности: прерывание ожидания, таймауты, опрос, несколько условий. Однако эти возможности требуют ответственного управления.

Блок finally гарантирует, что unlock() будет вызван независимо от того, завершится ли защищаемый код успешно или с ошибкой. Если блокировка не освобождена, другие потоки, вызывающие lock(), будут заблокированы навсегда. В отличие от synchronized, где компилятор и JVM обеспечивают освобождение, в ReentrantLock это полностью на совести разработчика.

#Java #советы
👍5
Что выведет код?

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class Task080726 {
private static final ReentrantLock lock = new ReentrantLock();

public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
lock.lock();
System.out.println("Thread1 acquired lock");
});

Thread t2 = new Thread(() -> {
try {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("Thread2 acquired lock");
lock.unlock();
} else {
System.out.println("Thread2 failed to acquire lock");
}
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
});

t1.start();
Thread.sleep(100);
t2.start();
t2.join();
}
}


#Tasks
👍2
Что такое Collectors.groupingBy() и partitioningBy()? 🤓

Ответ:

Collectors.groupingBy(Function classifier)
 — собирает элементы в Map, где ключ — результат классификатора, значение — список элементов. Есть перегрузки с downstream коллектором (например, counting(), summingInt()). 

Collectors.partitioningBy(Predicate) — разбивает на две части (true/false), возвращая Map<Boolean, List>.

Оба мощные инструменты для статистики и отчетов. Пример: группировка сотрудников по департаментам, подсчет количества, суммы зарплат и т.д. Основа для data-driven операций.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История технологии сегодня — 9 июля

ℹ️ Кто родился в этот день

Бенджамин (Бен) Рой Мо́ттельсон (англ. Benjamin Roy Mottelson; 9 июля 1926, Чикаго, Иллинойс — 13 мая 2022, Копенгаген) — американо-датский физик-теоретик и физик-ядерщик. Лауреат Нобелевской премии по физике «за открытие взаимосвязи между коллективным движением и движением отдельной частицы в атомном ядре и развитие теории строения атомного ядра, базирующейся на этой взаимосвязи».

Томас Дэвенпорт (англ. Thomas Davenport; 9 июля 1802 — 6 июля 1851) — американский кузнец, изобретатель. В 1833 году сконструировал первый роторный электродвигатель постоянного тока, создал приводимую им в движение модель поезда. В 1837 году получил патент на электромагнитную машину.


🌐 Знаковые события

1957 — открыт 102-й элемент таблицы Менделеева — нобелий.


#Biography #Birth_Date #Events #09июля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Раздел 11. Работа с файлами, I/O и сетью (NIO.2)

Глава 2. Современный NIO.2 (java.nio.file)

Класс Files — утилитный класс для работы с файлами

Класс Files из пакета java.nio.file — это центральная утилита в NIO.2, появившаяся в Java 7. Его роль в файловом API аналогична роли Collections для коллекций или Arrays для массивов: он предоставляет исключительно статические методы, которые выполняют операции над объектами Path. Если Path — это адрес, то Files — это набор инструментов, которые позволяют по этому адресу что-либо сделать.

Утилитный класс (utility class) — это класс, содержащий только статические методы и константы, не предназначенный для создания экземпляров. Обычно он имеет приватный конструктор, чтобы предотвратить инстанцирование. Files именно такой: вы не пишете new Files(), а вызываете Files.exists(path).


Архитектура: почему Files лучше java.io.File

Старый класс java.io.File существует с первой версии Java (1996 год) и несёт в себе архитектурные компромиссы того времени. Класс Files вместе с Path решает эти проблемы системно.

Разделение ответственности
java.io.File смешивал в себе три роли: представление пути, проверку метаданных и выполнение операций. Path отвечает только за путь, а Files — только за операции. Это соответствует принципу SRP (Single Responsibility Principle, принцип единственной ответственности): каждый класс должен иметь только одну причину для изменения.

Исключения вместо молчаливых ошибок
Методы File возвращали boolean при неудаче, не объясняя причину:
java.io.File oldFile = new java.io.File("/etc/shadow");
boolean deleted = oldFile.delete(); // false — но почему? Нет прав? Файл занят? Файла нет?

Методы Files выбрасывают checked-исключения (проверяемые исключения) — подклассы IOException, которые содержат точную причину отказа: NoSuchFileException, AccessDeniedException, DirectoryNotEmptyException и другие. Это позволяет писать надёжный код с ветвлением по конкретным ошибкам.

Checked-исключение — это исключение в Java, которое компилятор требует либо обработать в блоке try-catch, либо объявить в сигнатуре метода через throws. Модель checked/unchecked исключений в Java разделяет ошибки, от которых вызывающий код теоретически может восстановиться (checked), и программные ошибки (unchecked, наследники RuntimeException).

Поддержка символических ссылок
File не различал символическую ссылку (symlink) и целевой файл. Files предоставляет методы, явно работающие с ссылками: isSymbolicLink(), readSymbolicLink(), а также варианты методов с параметром LinkOption.NOFOLLOW_LINKS, который указывает, что операция должна применяться к самой ссылке, а не к тому, на что она указывает.

Символическая ссылка (symbolic link, symlink) — это специальный файл в файловой системе, содержащий путь к другому файлу или директории. В отличие от жёсткой ссылки, символическая ссылка может указывать на несуществующий объект и может пересекать границы файловых систем.

Атомарность и конкурентность
Многие методы Files гарантируют атомарность на уровне файловой системы. Например, Files.move() с опцией ATOMIC_MOVE выполняет перемещение как неделимую операцию, что невозможно было реализовать через старый File.renameTo().

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


Проверка существования и типа файла

Files.exists(Path path, LinkOption... options)

Метод проверяет, существует ли файл, директория или символическая ссылка по указанному пути. Возвращает true, если файл существует, false — если нет или если при проверке произошла ошибка (например, отказ в доступе).
Path configPath = Paths.get("/app/config.properties");
boolean exists = Files.exists(configPath);

Важный нюанс: Files.exists() возвращает false не только когда файла нет, но и когда нет прав на проверку. Это делает метод неидеальным для однозначных выводов. Если вам нужно различать "файла нет" и "нет прав", используйте Files.notExists() в паре с Files.exists().

Параметр LinkOption... options — это varargs массив опций. Передача LinkOption.NOFOLLOW_LINKS означает, что если путь — символическая ссылка, метод проверит существование самой ссылки, а не целевого файла.

Files.notExists(Path path, LinkOption... options)
Этот метод возвращает true, если файл точно не существует. Он отличается от простой инверсии !Files.exists() тем, что различает два состояния:
Файл не существует — возвращает true.
Файл существует, но нет прав на проверку — возвращает false.
Произошла другая ошибка ввода-вывода — выбрасывает IOException.
Path secret = Paths.get("/root/.ssh/id_rsa");

if (Files.notExists(secret)) {
// Мы уверены, что файла нет (или нет прав, но тогда exists тоже вернул бы false)
System.out.println("Файл не существует");
} else if (Files.exists(secret)) {
// Мы уверены, что файл есть
System.out.println("Файл существует");
} else {
// Files.exists() == false И Files.notExists() == false
// Это возможно только при ошибке доступа — состояние неопределённости
System.out.println("Невозможно определить существование файла");
}

Эта пара методов реализует трёхзначную логику существования: DEFINITELY_EXISTS, DEFINITELY_NOT_EXISTS, UNKNOWN. Такой подход необходим в системах, где безопасность критична: например, при проверке перед созданием файла, чтобы избежать перезаписи существующего файла, к которому у вас нет прав на чтение, но есть права на запись.

#Java #для_новичков #beginner #IO #NIO #Files
👍3
Files.isDirectory(Path path, LinkOption... options)
Проверяет, является ли путь директорией. Возвращает false, если путь — файл, символическая ссылка на файл, или если проверка невозможна. Для корректной работы требуется, чтобы файл существовал: метод не выбрасывает исключение при отсутствии файла, но возвращает false.
Path dir = Paths.get("/var/log");
if (Files.isDirectory(dir)) {
// Это директория
}


Files.isRegularFile(Path path, LinkOption... options)
Проверяет, является ли путь обычным файлом. Обычный файл (regular file) — это файл, который не является директорией, символической ссылкой, именованным каналом, сокетом домена Unix или устройством. Это файл с данными, который можно читать и писать.
Path file = Paths.get("/etc/passwd");
if (Files.isRegularFile(file)) {
// Это обычный файл, можно безопасно читать
}


Files.isSymbolicLink(Path path)
Проверяет, является ли путь символической ссылкой. Этот метод не следует по ссылке — он проверяет сам объект файловой системы. Если путь — symlink, метод вернёт true даже если целевой файл удалён (висячая ссылка).
Path link = Paths.get("/usr/bin/java");
if (Files.isSymbolicLink(link)) {
Path target = Files.readSymbolicLink(link); // куда указывает ссылка
System.out.println("Ссылка ведёт на: " + target);
}



Практический пример: инициализация конфигурации

Рассмотрим сценарий, в котором приложение при старте проверяет файл конфигурации. Если файл отсутствует — создаёт его с дефолтными настройками. Если файл существует, но является директорией или символической ссылкой — обрабатывает это как ошибку.
import java.nio.file.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class ConfigInitializer {

private static final Path CONFIG_PATH = Paths.get("app.conf");
private static final String DEFAULT_CONFIG =
"# Default configuration\n" +
"server.port=8080\n" +
"db.url=jdbc:h2:mem:test\n";

public void initialize() throws IOException {
// Шаг 1: проверяем, существует ли путь
if (Files.notExists(CONFIG_PATH)) {
// Файла точно нет — создаём с дефолтами
createDefaultConfig();
System.out.println("Конфигурация создана по умолчанию");
return;
}

// Шаг 2: файл существует — проверяем, что это обычный файл
if (!Files.isRegularFile(CONFIG_PATH)) {
if (Files.isDirectory(CONFIG_PATH)) {
throw new IOException(
"Конфигурационный путь \"" + CONFIG_PATH + "\" является директорией"
);
}
if (Files.isSymbolicLink(CONFIG_PATH)) {
// Разрешаем символическую ссылку, но проверяем целевой объект
Path target = Files.readSymbolicLink(CONFIG_PATH);
if (!Files.isRegularFile(target, LinkOption.NOFOLLOW_LINKS)) {
throw new IOException(
"Символическая ссылка конфигурации указывает на не-файл: " + target
);
}
// Ссылка валидна — продолжаем
return;
}
throw new IOException("Неизвестный тип файла конфигурации");
}

// Шаг 3: обычный файл существует — проверяем читаемость
if (!Files.isReadable(CONFIG_PATH)) {
throw new AccessDeniedException("Нет прав на чтение конфигурации");
}

System.out.println("Конфигурация загружена из существующего файла");
}

private void createDefaultConfig() throws IOException {
// getParent() может вернуть null, если путь в текущей директории
Path parent = CONFIG_PATH.getParent();
if (parent != null && Files.notExists(parent)) {
// createDirectories создаёт всю иерархию, включая промежуточные директории
Files.createDirectories(parent);
}
// write записывает массив байтов в файл, создавая его атомарно
Files.write(CONFIG_PATH, DEFAULT_CONFIG.getBytes(StandardCharsets.UTF_8));
}
}

В этом примере последовательность проверок критична: сначала notExists гарантирует, что мы не перезапишем существующий файл. Затем isRegularFile защищает от директорий и ссылок. Files.isReadable() — дополнительная проверка, которая не выбрасывает исключение, а возвращает boolean, что удобно для ветвления логики.


Путь байтов в памяти JVM при работе с Files


Каждый вызов метода Files порождает цепочку преобразований данных от Java-объекта до системного вызова ОС и обратно. Разберём этот путь на примере Files.readAllBytes(path).

1. Объект Path в куче
Объект Path, переданный в метод, уже находится в куче. Как описано в предыдущем уроке, Path — это лёгкая структура, хранящая сегменты пути и ссылку на FileSystem. При вызове Files.readAllBytes(path) JVM помещает ссылку на Path в стек вызовов текущего потока — это область памяти, где хранятся локальные переменные и параметры методов. Стек потока выделяется в нативной памяти и не управляется GC напрямую.

2. Переход в нативный код через JNI
Реализация Files.readAllBytes() в OpenJDK делегирует работу классу FileChannel или прямому нативному вызову через JNI (Java Native Interface — механизм взаимодействия Java-кода с библиотеками, написанными на C/C++ или ассемблере). На этом этапе строковое представление пути извлекается из объекта Path и кодируется в массив байтов в кодировке UTF-8.
Этот массив байтов создаётся в куче как временный объект byte[] или char[]. Затем он копируется в нативную память (native memory, память вне кучи JVM) через JNI-функции, так как системные вызовы ОС работают с C-строками (char* или wchar_t*), а не с Java-объектами.

3. Системный вызов и кэш страниц ОС
ОС получает путь в виде байтов и выполняет системный вызов open() (POSIX) или NtCreateFile() (Windows). Ядро ОС преобразует путь в структуру inode (на Unix) или MFT-запись (на NTFS), проверяет права доступа и открывает файловый дескриптор.
Данные файла читаются не напрямую с диска, а через кэш страниц (page cache) — область оперативной памяти ядра, где хранятся недавно использованные блоки файлов. Если данные есть в кэше, чтение происходит без обращения к диску. Если нет — происходит промах кэша, и данные загружаются с диска в кэш страниц.

4. Возврат данных в кучу JVM
Прочитанные байты копируются из кэша страниц ОС через нативную память обратно в кучу JVM. Метод readAllBytes() выделяет byte[] точного размера файла. Этот массив создаётся в Young Generation (молодом поколении) кучи, в области Eden. Если файл большой (например, 100 МБ), массив может оказаться слишком крупным для Young Generation и будет сразу размещён в Old Generation (старом поколении) — такие объекты называются humongous objects (гигантские объекты) в G1 GC.
G1 GC (Garbage-First Garbage Collector) — это сборщик мусора, используемый по умолчанию с Java 9. Он делит кучу на регионы фиксированного размера (обычно 1–32 МБ). Объект, занимающий более половины региона, считается humongous и размещается непрерывным блоком в Old Generation.

5. Работа GC с файловыми данными
После возврата из readAllBytes() массив байтов остаётся в куче. Если он присвоен локальной переменной внутри метода и метод завершился, ссылка на массив исчезает из стека, и массив становится мусором. Minor GC (молодая сборка) соберёт его при следующем проходе, если он находится в Eden. Если массив большой и попал в Old Generation, он дождётся Major GC (полной сборки).

Важно: сам файловый дескриптор, открытый ОС, закрывается нативным кодом до возврата в Java. Но если вы используете потоковое чтение (Files.newInputStream()), дескриптор остаётся открытым до вызова close(). Если забыть закрыть поток, дескриптор утечёт на уровне ОС, даже если Java-объект InputStream будет собран GC. GC не знает о ресурсах ОС — он управляет только памятью кучи. Поэтому try-with-resources обязателен:
// try-with-resources гарантирует вызов close() даже при исключении
// AutoCloseable — интерфейс, требующий реализации метода close()
try (InputStream in = Files.newInputStream(path)) {
// работа с потоком
} // close() вызывается автоматически здесь


#Java #для_новичков #beginner #IO #NIO #Files
👍5
6. Метаданные и временные объекты
При вызовах Files.exists(), isDirectory() и подобных методов данные файла не читаются. Вместо этого выполняется системный вызов stat() (POSIX) или GetFileAttributes() (Windows), который возвращает метаданные: тип файла, права доступа, размер, временные метки. Эти метаданные возвращаются в виде структуры в нативной памяти, из которой JVM извлекает нужные поля и создаёт примитивы (boolean, long) на стеке. Никаких объектов в куче при этом не создаётся, поэтому такие проверки — "бесплатные" с точки зрения GC.


Чтение и запись данных

Files.readAllBytes(Path) и Files.readAllLines(Path, Charset)
// Чтение всего файла как массива байтов
byte[] rawData = Files.readAllBytes(Paths.get("data.bin"));

// Чтение текстового файла в список строк
List<String> lines = Files.readAllLines(
Paths.get("log.txt"),
StandardCharsets.UTF_8
);

readAllBytes() удобен для небольших файлов, но опасен для больших: он загружает весь файл в память. Для больших файлов используйте Files.newBufferedReader() или Files.lines().

Files.lines() возвращает Stream<String>ленивый поток (lazy stream), который читает файл построчно по мере обхода. Это экономит память, но требует закрытия потока:
// Stream<String> должен быть закрыт, так как держит файловый дескриптор
try (Stream<String> stream = Files.lines(Paths.get("huge.log"))) {
long errorCount = stream.filter(line -> line.contains("ERROR")).count();
}

Ленивый поток — это поток данных, элементы которого вычисляются или читаются по требованию, а не все сразу. В Java Stream API операции промежуточные (filter, map) ленивы, а терминальные (count, collect) — энергичные (запускают вычисление).

Files.write(Path, byte[]/Iterable, OpenOption...)
Path target = Paths.get("output.txt");
byte[] data = "Hello, World".getBytes(StandardCharsets.UTF_8);

// CREATE — создать файл, если не существует
// TRUNCATE_EXISTING — очистить существующий файл перед записью
Files.write(target, data, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

OpenOption — это интерфейс-маркер, который реализуют перечисления StandardOpenOption и LinkOption. Он определяет, как именно открывать или создавать файл.
Копирование, перемещение и удаление

Files.copy(Path source, Path target, CopyOption... options)
Path src = Paths.get("/tmp/source.dat");
Path dst = Paths.get("/backup/source.dat");

Files.copy(src, dst,
StandardCopyOption.REPLACE_EXISTING, // перезаписать, если есть
StandardCopyOption.COPY_ATTRIBUTES // скопировать права и временные метки
);

Метод поддерживает копирование между разными файловыми системами (например, из ZIP-архива на диск), так как работает через абстракцию FileSystemProvider.

FileSystemProvider — это сервис-провайдер (SPI, Service Provider Interface), который реализует конкретную файловую систему. Для каждой ФС существует свой провайдер: UnixFileSystemProvider, WindowsFileSystemProvider, ZipFileSystemProvider и т.д. Files.copy() делегирует операцию провайдеру источника и провайдеру назначения.

Files.move(Path source, Path target, CopyOption... options)
Files.move(src, dst, StandardCopyOption.ATOMIC_MOVE);

ATOMIC_MOVE гарантирует, что операция будет атомарной в пределах одной файловой системы. Если источник и назначение на разных ФС, метод выбросит AtomicMoveNotSupportedException.

Files.delete(Path path) и Files.deleteIfExists(Path path)
// delete бросает NoSuchFileException, если файла нет
Files.delete(Paths.get("temp.tmp"));

// deleteIfExists возвращает boolean, не бросая исключение при отсутствии
boolean deleted = Files.deleteIfExists(Paths.get("temp.tmp"));


Files.delete()
требует, чтобы директория была пустой. Для рекурсивного удаления используйте обход дерева:
// walk возвращает Stream<Path> всех файлов и директорий, начиная с root
// sorted(Comparator.reverseOrder()) — сначала файлы, потом родительские директории
Files.walk(root)
.sorted(Comparator.reverseOrder())
.forEach(p -> {
try {
Files.delete(p);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});



Работа с метаданными

Files.getAttribute и Files.setAttribute
// Чтение произвольного атрибута файловой системы
Object creationTime = Files.getAttribute(path, "creationTime");

// Установка POSIX-прав доступа (только на Unix)
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-r--r--");
Files.setPosixFilePermissions(path, perms);

POSIX (Portable Operating System Interface) — это семейство стандартов, определяющих интерфейс между ОС и приложениями. В контексте файлов POSIX определяет модель прав доступа (владелец, группа, остальные) и набор операций над файлами.

Files.getFileStore(Path)
FileStore store = Files.getFileStore(path);
long usableSpace = store.getUsableSpace();
long totalSpace = store.getTotalSpace();

FileStore — это абстракция над хранилищем файловой системы (раздел диска, том, сетевой диск). Она позволяет узнать общий и доступный объём, тип ФС и другие характеристики.


Продвинутые паттерны использования

Паттерн "Write to temp, then move"

Для атомарной записи файла пишите во временный файл в той же директории, а затем атомарно перемещайте:
public void atomicWrite(Path target, byte[] data) throws IOException {
// Создаём временный файл в той же директории, что и target
// это важно, чтобы move был атомарным в пределах одного FileStore
Path temp = Files.createTempFile(target.getParent(), "tmp_", ".dat");
try {
Files.write(temp, data);
// Атомарное переименование: либо старый файл заменён, либо нет
Files.move(temp, target, StandardCopyOption.ATOMIC_REPLACE);
} catch (IOException e) {
// При ошибке удаляем временный файл
Files.deleteIfExists(temp);
throw e;
}
}

Этот паттерн гарантирует, что читатели никогда не увидят частично записанный файл. Временный файл создаётся через Files.createTempFile(), который генерирует уникальное имя атомарно.

Паттерн "Directory watch"
WatchService watchService = FileSystems.getDefault().newWatchService();
Path dir = Paths.get("/app/inbox");

// Регистрируем директорию на отслеживание создания и модификации файлов
dir.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY
);

// WatchService — механизм мониторинга изменений в файловой системе
// Он использует нативные API ОС (inotify на Linux, FSEvents на macOS, ReadDirectoryChangesW на Windows)

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

#Java #для_новичков #beginner #IO #NIO #Files
👍5
Что выведет код?

import java.nio.file.*;
import java.io.IOException;

public class Task090726 {
public static void main(String[] args) throws IOException {
Path p1 = Paths.get("");
Path p2 = Paths.get(".");
System.out.println(Files.isSameFile(p1, p2));
System.out.println(p1.equals(p2));
System.out.println(p1.normalize().equals(p2.normalize()));
}
}


#Tasks
👍2
👍2
Ребят, продолжаю набор желающих обучиться Java. 🤓

Гарантий трудоустройства нет, но на уровень мидла точно выведу) Недорого)))

Пишите в лс! @Oleborn 📞
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Что такое Collectors.toUnmodifiableList() и toUnmodifiableSet()? 🤓

Ответ:

Java 10 ввел коллекторы для создания неизменяемых коллекций: Collectors.toUnmodifiableList(), toUnmodifiableSet(), toUnmodifiableMap().

Они возвращают коллекции, которые не поддерживают изменения (в отличие от toList() из Java 16, который также неизменяем, но реализован проще).

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



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5