Java | Фишки и трюки
7.21K subscribers
182 photos
29 videos
6 files
40 links
Java: примеры кода, интересные фишки и полезные трюки

Купить рекламу: https://telega.in/c/java_tips_and_tricks

✍️По всем вопросам: @Pascal4eg
Download Telegram
🔄 Что такое рекурсия?

Рекурсия — это техника программирования, когда метод вызывает сам себя для решения подзадачи.

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

1️⃣ Упрощает код, особенно для задач, которые естественно описываются рекурсивно (например, вычисление факториала, обход дерева).
2️⃣ Позволяет заменять циклы более читаемым кодом.

✔️ Пример:


public class Factorial {
static int factorial(int n) {
return (n == 0) ? 1 : n * factorial(n - 1);
}

public static void main(String[] args) {
System.out.println(factorial(5)); // 120
}
}


💡 Совет: Всегда определяйте базовый случай, иначе получите StackOverflowError.

#java #recursion #factorial
Please open Telegram to view this post
VIEW IN TELEGRAM
👍91
⌨️ Применение LinkedBlockingQueue в многозадачных приложениях

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

📌 Преимущества LinkedBlockingQueue:
1️⃣ Потокобезопасность: поддерживает безопасное добавление и извлечение элементов из разных потоков.
2️⃣ Блокировка: потоки, извлекающие элементы, могут блокироваться до появления элементов, а потоки, добавляющие элементы, могут блокироваться, если очередь полна.
3️⃣ Гибкость: поддерживает как ограниченные, так и неограниченные очереди.

📌 Когда использовать LinkedBlockingQueue?
- Для реализации паттернов "производитель-потребитель", когда один или несколько потоков производят данные, а другие их потребляют.
- Когда требуется синхронизация между потоками с блокировкой при переполнении или пустой очереди.
- В задачах, требующих управления очередностью выполнения потоков (например, в очередях задач).

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

import java.util.concurrent.*;

public class LinkedBlockingQueueExample {
public static void main(String[] args) throws InterruptedException {
LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);

// Производитель
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 20; i++) {
System.out.println("Производитель добавил: " + i);
queue.put(i); // Блокирует, если очередь полна
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});

// Потребитель
Thread consumer = new Thread(() -> {
try {
for (int i = 0; i < 20; i++) {
int item = queue.take(); // Блокирует, если очередь пуста
System.out.println("Потребитель забрал: " + item);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});

producer.start();
consumer.start();

producer.join();
consumer.join();
}
}


📌 Как это работает:
1️⃣ Производитель добавляет элементы в очередь с помощью put(). Если очередь полна, поток будет блокироваться.
2️⃣ Потребитель извлекает элементы с помощью take(). Если очередь пуста, поток будет блокироваться до появления элементов.

Когда использовать LinkedBlockingQueue?
- Для решения задач с ограниченным количеством ресурсов (например, пул потоков).
- В многозадачных приложениях, где важно организовать очередь обработки данных между потоками.
- Когда необходимо сбалансировать нагрузку между потоками, эффективно распределяя задачи и ресурсы.

💡 Совет: Используйте LinkedBlockingQueue для решения задач с конкурентными потоками и синхронизацией, где важно поддержание порядка и эффективное управление потоками.

#Java #LinkedBlockingQueue #Потокобезопасность #Многозадачность
Please open Telegram to view this post
VIEW IN TELEGRAM
👍81
⌛️ Зачем использовать synchronized?

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

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

1️⃣ Синхронизация метода:
public synchronized void increment() {
count++;
}


2️⃣ Синхронизация блока кода:
public void increment() {
synchronized (this) {
count++;
}
}


✔️ Особенности:

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

💡 Совет: Используйте synchronized только там, где это действительно необходимо, чтобы избежать узких мест.

#java #synchronized #multithreading
Please open Telegram to view this post
VIEW IN TELEGRAM
👍131
⌨️ Как реализовать Lazy Initialization через volatile и synchronized

Lazy Initialization — это паттерн, при котором объект создаётся только при первом обращении к нему, а не в момент запуска приложения. Это полезно для оптимизации производительности, особенно если объект может быть дорогим в создании, а его использование не всегда необходимо.

В Java можно реализовать Lazy Initialization с помощью ключевого слова volatile и синхронизации (synchronized), чтобы гарантировать правильное поведение в многозадачных приложениях.

📌 Что такое
volatile и synchronized?
1️⃣ volatile: Гарантирует, что все потоки видят актуальное значение переменной, без кэширования её в локальной памяти.
2️⃣ synchronized: Блокирует доступ к коду, чтобы только один поток мог выполнить его в определённый момент времени.

Пример:


public class LazyInitialization {

// Переменная с модификатором volatile
private static volatile SomeClass instance;

// Метод для получения экземпляра класса
public static SomeClass getInstance() {
// Первый контроль (без блокировки), если объект уже инициализирован
if (instance == null) {
synchronized (LazyInitialization.class) {
// Второй контроль с блокировкой для создания объекта
if (instance == null) {
instance = new SomeClass();
}
}
}
return instance;
}

public static void main(String[] args) {
SomeClass obj = LazyInitialization.getInstance();
System.out.println(obj);
}
}

class SomeClass {
// Пример класса для инициализации
public SomeClass() {
System.out.println("Объект SomeClass создан!");
}
}


📌 Как это рабо
тает:
1️⃣ При первом обращении к getInstance(), проверяется, создан ли уже объект. Если нет — блокируется доступ для других потоков с помощью synchronized.
2️⃣ Внутри блока synchronized снова проверяется, не был ли объект создан другим потоком, чтобы избежать повторной инициализации.
3️⃣ volatile гарантирует, что создание объекта будет завершено до того, как другие потоки получат доступ к переменной.

📌 Когда использовать такую реализацию?

- Для обеспечения потокобезопасности при ленивой инициализации объектов.
- В многозадачных приложениях, где объект должен быть создан только один раз.

💡 Совет: Эта техника подходит для случаев, когда объект может быть дорогим в создании и его создание должно происходить только при первом обращении.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍122👎1🔥1
🛠 Разница между StringBuilder и StringBuffer

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

➡️ Основные различия:

✔️ StringBuilder быстрее, но не потокобезопасен.
✔️ StringBuffer потокобезопасен, но медленнее из-за синхронизации.

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

StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");
System.out.println(sb); // Hello World


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

StringBuffer sb = new StringBuffer("Hello");
sb.append(" World");
System.out.println(sb); // Hello World


💡 Совет: Если ваш код выполняется в одном потоке, используйте StringBuilder для лучшей производительности.

#java #stringbuilder #stringbuffer
Please open Telegram to view this post
VIEW IN TELEGRAM
👍183🔥2
⌨️ Простое сетевое взаимодействие через DatagramChannel

DatagramChannel из java.nio.channels — это мощный инструмент для работы с UDP-соединениями в Java. Этот канал позволяет отправлять и принимать датаграммы (пакеты данных), что делает его идеальным для создания лёгких и быстрых сетевых приложений.

🔍 Особенности DatagramChannel:
1️⃣ Поддерживает неблокирующий режим.
2️⃣ Простое взаимодействие без установки соединения.
3️⃣ Отлично подходит для передачи небольших сообщений.

📌 Пример отправки и получения сообщения:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

public class DatagramChannelExample {
public static void main(String[] args) {
new Thread(() -> {
// Создаём канал для получения
try (DatagramChannel receiver = DatagramChannel.open()) {
receiver.bind(new InetSocketAddress(9999));
ByteBuffer buffer = ByteBuffer.allocate(1024);
receiver.receive(buffer);
buffer.flip();
byte[] receivedData = new byte[buffer.limit()];
buffer.get(receivedData);
System.out.println("Получено сообщение: " + new String(receivedData));
} catch (IOException e) {
throw new RuntimeException(e);
}
}).start();

// Создаём канал для отправки
try (DatagramChannel sender = DatagramChannel.open()) {
String message = "Привет, мир!";
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
sender.send(buffer, new InetSocketAddress("localhost", 9999));
System.out.println("Сообщение отправлено.");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}


🔗 Ключевые моменты:
1️⃣ Отправка: Используйте send для передачи данных по указанному адресу.
2️⃣ Получение: Метод receive позволяет читать входящие датаграммы.
3️⃣ Порт: Убедитесь, что отправитель и получатель используют один и тот же порт.

Когда использовать?
- Для приложений с низкой задержкой, где важна скорость.
- Для отправки сообщений в локальной сети.
- В системах, где не требуется постоянное соединение.

💡 Совет: Используйте DatagramChannel для лёгких и быстрых решений, например, для игровых серверов или мониторинга систем.

#Java #DatagramChannel #UDP
Please open Telegram to view this post
VIEW IN TELEGRAM
👍94🔥3👏1
🔍 Что такое var и когда его использовать?

С появлением Java 10 был добавлен var – ключевое слово для вывода типа переменной на основе присвоенного значения.

Позволяет сократить код, убрав явное указание типа.
Работает только для локальных переменных (нельзя использовать для аргументов метода или полей класса).

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


var message = "Hello, Java!"; // Компилятор автоматически определит тип String
var number = 42; // int
var list = List.of("A", "B", "C"); // List<String>


Нельзя использовать var так:


var x; // Ошибка, требуется инициализация


💡 Совет: Используйте var, если тип очевиден, но не злоупотребляйте, чтобы не терять читаемость кода.

#java #var #java10
Please open Telegram to view this post
VIEW IN TELEGRAM
👍191👎1
Что такое Record в Java?

Record – это новый тип класса, который появился в Java 14 и предназначен для создания неизменяемых объектов с минимальным кодом.

Автоматически создаёт equals(), hashCode() и toString().
Подходит для DTO (Data Transfer Object) и неизменяемых объектов.

✔️ Пример:

public record Person(String name, int age) {}

public class Main {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
System.out.println(person); // Person[name=Alice, age=30]
}
}


💡 Совет: Используйте Record, если вам нужен неизменяемый объект с автогенерацией методов.

#java #record #java14
Please open Telegram to view this post
VIEW IN TELEGRAM
👍122
⌨️ Что такое WeakReference?

WeakReference – это специальный тип ссылки, который не предотвращает сборщик мусора от удаления объекта.

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

✔️ Пример:


import java.lang.ref.WeakReference;

public class WeakRefExample {
public static void main(String[] args) {
String str = new String("WeakReference");
WeakReference<String> weakRef = new WeakReference<>(str);
str = null; // Теперь объект доступен для GC
System.gc();
System.out.println(weakRef.get()); // Может быть null
}
}


💡 Совет: Используйте WeakReference, если объект должен автоматически удаляться при нехватке памяти.

#java #weakreference
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍2🔥1🙏1
💡 Как управлять потоками с помощью ExecutorCompletionService

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

📌 Преимущества ExecutorCompletionService:
1️⃣ Удобное управление задачами: позволяет управлять и отслеживать несколько асинхронных задач одновременно.
2️⃣ Получение результатов по мере выполнения: результаты задач можно получать в том порядке, в котором они завершены.
3️⃣ Обработка ошибок: позволяет перехватывать исключения, возникающие в процессе выполнения задач.

📌 Пример использования ExecutorCompletionService:
import java.util.concurrent.*;

public class ExecutorCompletionServiceExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(3);
ExecutorCompletionService<Integer> completionService = new ExecutorCompletionService<>(executor);

// Добавляем задачи в очередь
for (int i = 0; i < 5; i++) {
final int taskId = i;
completionService.submit(() -> {
// Задача выполняется
Thread.sleep(1000); // Эмулируем работу
return taskId * 2;
});
}

// Получаем результаты по мере их готовности
for (int i = 0; i < 5; i++) {
Future<Integer> result = completionService.take(); // Возвращает первый завершившийся результат
System.out.println("Задача завершена с результатом: " + result.get());
}

executor.shutdown();
}
}


📌 Как это работает:
1️⃣ Создаём ExecutorCompletionService, передавая ему пул потоков.
2️⃣ Задачи отправляются в очередь с помощью метода submit().
3️⃣ Метод take() извлекает завершённую задачу и её результат.
4️⃣ Метод get() позволяет получить результат выполнения задачи.

📌 Когда использовать ExecutorCompletionService?
- Когда необходимо параллельно выполнить несколько задач и получить результаты по мере их завершения.
- В ситуациях, когда задачи могут занять разное время на выполнение.
- Для эффективного управления потоками в многозадачных приложениях.

💡 Совет: Используйте ExecutorCompletionService для повышения производительности, когда важно обрабатывать результаты задач в порядке их завершения.

#Java #ExecutorCompletionService #ПараллельноеВыполнение #Многозадачность
👍153
⚙️ Что такое CompletableFuture в Java?

CompletableFuture — это улучшенная альтернатива Future, появившаяся в Java 8, позволяющая работать с асинхронными вычислениями.

➡️ Почему стоит использовать?

1️⃣ Позволяет запускать задачи в фоне и получать результат позже.
2️⃣ Поддерживает цепочки операций .thenApply(), .thenAccept().
3️⃣ Может выполняться параллельно без блокировки основного потока.

✔️ Пример:

import java.util.concurrent.CompletableFuture;

public class AsyncExample {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> "Hello, Async!")
.thenAccept(System.out::println);
}
}


💡 Совет: Используйте CompletableFuture, если хотите писать неблокирующий и эффективный код.

#java #completablefuture #async
Please open Telegram to view this post
VIEW IN TELEGRAM
👍132
⚡️ Разница между ExecutorService и ForkJoinPool

Оба механизма предназначены для работы с многопоточностью, но у них разные задачи:

ExecutorService – управляет пулом потоков, подходит для выполнения Runnable и Callable.
ForkJoinPool – оптимизирован для рекурсивных задач, использует алгоритм work-stealing.

✔️ Пример ExecutorService:


ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> System.out.println("Task executed"));
executor.shutdown();


✔️ Пример ForkJoinPool:


import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;

class SumTask extends RecursiveTask<Integer> {
private final int[] array;
private final int start, end;
private final int threshold = 10;

public SumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}

@Override
protected Integer compute() {
if (end - start <= threshold) {
// Базовый случай: небольшая задача
int sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
} else {
// Разделяем задачу
int mid = (start + end) / 2;
SumTask leftTask = new SumTask(array, start, mid);
SumTask rightTask = new SumTask(array, mid, end);

leftTask.fork(); // Асинхронно запускаем левую подзадачу
int rightResult = rightTask.compute(); // Синхронно вычисляем правую подзадачу
int leftResult = leftTask.join(); // Ждем завершения левой подзадачи

return leftResult + rightResult;
}
}
}

public class ForkJoinExample {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
SumTask task = new SumTask(array, 0, array.length);
int result = pool.invoke(task);
System.out.println("Сумма: " + result);
}
}


ForkJoinPool динамически управляет количеством потоков, при необходимости создавая новые. Обычно это количество соответствует числу процессоров, доступных в системе.

💡 Совет: Используйте ForkJoinPool для рекурсивных задач, а ExecutorService – для классической многопоточности.

#java #executorservice #forkjoinpool #multithreading
Please open Telegram to view this post
VIEW IN TELEGRAM
👍104🔥1👏1
🔍 Разница между TreeSet и HashSet

Оба класса реализуют Set, но у них разные свойства:

HashSet – хранит элементы в случайном порядке, обеспечивает быстрые операции add() и remove().
TreeSet – хранит элементы в отсортированном порядке, но работает медленнее.

✔️ Пример:

Set<Integer> hashSet = new HashSet<>(Set.of(3, 1, 2)); // [3, 1, 2] (порядок может быть разным)
Set<Integer> treeSet = new TreeSet<>(Set.of(3, 1, 2)); // [1, 2, 3] (отсортировано)


💡 Совет: Используйте HashSet для быстрого доступа и TreeSet, если важен порядок элементов.

#java #treeset #hashset #collections
Please open Telegram to view this post
VIEW IN TELEGRAM
👍102🔥1
⚙️ Разница между deep copy и shallow copy

При копировании объектов в Java различают два типа копий:

Shallow copy – копирует только ссылки на вложенные объекты, изменения в одном объекте затронут копию.
Deep copy – создаёт полную копию, включая все вложенные объекты.

✔️ Пример shallow copy:


class Person implements Cloneable {
String name;

Person(String name) {
this.name = name;
}

public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}


Для создания полной копии можно использовать конструктор копирования или сериализацию/десериализацию.

✔️ Пример deep copy:


import java.io.*;

public class DeepCopyExample {
public static void main(String[] args) {
Person person = new Person("John", 30);
Person deepCopy = deepCopy(person);

System.out.println("Original: " + person.getName() + ", " + person.getAge());
System.out.println("Deep copy: " + deepCopy.getName() + ", " + deepCopy.getAge());
}

public static <T extends Serializable> T deepCopy(T object) {
try {
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOutputStream);
objectOutputStream.writeObject(object);

ByteArrayInputStream byteInputStream = new ByteArrayInputStream(byteOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteInputStream);
T copy = (T) objectInputStream.readObject();

return copy;
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}

class Person implements Serializable {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}
}


💡 Совет: Используйте deep copy, если объект содержит изменяемые вложенные объекты, иначе изменения в одном месте могут неожиданно повлиять на копию.

#java #deepcopy #shallowcopy #clone
Please open Telegram to view this post
VIEW IN TELEGRAM
👍91
🔗 Что такое try-with-resources?

Конструкция try-with-resources, появившаяся в Java 7, позволяет автоматически закрывать ресурсы (например, файлы, потоки).

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

1️⃣ Облегчает управление ресурсами.
2️⃣ Избегает утечек памяти.

Пример:
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
System.out.println(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}


💡 Совет: Все ресурсы, используемые в try-with-resources, должны реализовывать интерфейс AutoCloseable.

#java #trywithresources #java7
Please open Telegram to view this post
VIEW IN TELEGRAM
👍161🔥1
⚡️ Оптимизация работы с большими коллекциями в Java

Работа с большими List и Map в Java может привести к высоким затратам памяти и снижению производительности. Как этого избежать?

Ошибка: использование ArrayList без задания начального размера

List<String> list = new ArrayList<>(); // По умолчанию – 10 элементов
for (int i = 0; i < 1000000; i++) {
list.add("data");
}


➡️ Решение: укажите размер сразу, если он известен

List<String> list = new ArrayList<>(1000000);


Ошибка: HashMap без настройки initialCapacity и loadFactor

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

➡️ Решение: настройка параметров

Map<String, String> map = new HashMap<>(1000000, 0.9f);


Ошибка: использование LinkedList вместо ArrayList для поиска

List<String> list = new LinkedList<>();
list.get(5000); // O(n)


➡️ Решение: используйте ArrayList, если часто выполняете get()

💡 Совет: Профилируйте код с -XX:+PrintGCDetails и jcmd GC.heap_info, чтобы следить за лишними выделениями памяти.

#java #collections #performance
Please open Telegram to view this post
VIEW IN TELEGRAM
👍19🔥52
🔍 5 фишек Stream API, которые упростят код

Stream API – мощный инструмент, но в нём есть интересные методы, о которых многие забывают.

🟢takeWhile() – берёт элементы, пока условие true.
🟢dropWhile() – пропускает элементы, пока условие true.
🟢iterate() – создаёт бесконечный поток.
🟢flatMap() – преобразует вложенные структуры в плоский поток.
🟢collect(Collectors.toMap()) – собирает Stream в Map.

✔️ Пример:


List<Integer> numbers = List.of(1, 2, 3, 4, 5);
numbers.stream()
.takeWhile(n -> n < 4)
.forEach(System.out::println); // 1, 2, 3


💡 Совет: Используйте эти методы, чтобы писать более лаконичный код без лишних for.

#java #streamapi #functionalprogramming
Please open Telegram to view this post
VIEW IN TELEGRAM
👍263🔥1
🔍 Что такое switch с выражениями в Java 12+?

В Java 12 появился улучшенный switch, который позволяет возвращать значения и использовать case без break.

Код стал короче и читабельнее.
Можно присваивать результат switch переменной.
Поддерживает yield для возврата значений.

✔️ Пример:


String result = switch (day) {
case "Monday", "Tuesday" -> "Рабочий день";
case "Saturday", "Sunday" -> "Выходной";
default -> "Неизвестный день";
};

System.out.println(result);


💡 Совет: Используйте новый switch для улучшения читаемости кода и уменьшения дублирования break.

#java #switch #java12
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18🔥32
🔗 Разница между List, Set и Map

Эти три интерфейса из java.util используются для хранения коллекций данных, но имеют разные свойства:

List – упорядоченная коллекция, допускает дубликаты (ArrayList, LinkedList).

Set – уникальные элементы, порядок может быть произвольным (HashSet, TreeSet).

Map – хранит пары "ключ-значение" (HashMap, TreeMap).

✔️ Пример:


List<String> list = new ArrayList<>(List.of("A", "B", "A")); // [A, B, A]
Set<String> set = new HashSet<>(Set.of("A", "B", "A")); // [A, B]
Map<Integer, String> map = new HashMap<>(Map.of(1, "One", 2, "Two")); // {1=One, 2=Two}


💡 Совет: Используйте List, если важен порядок и дубликаты, Set – если важна уникальность, Map – для работы с парами ключ-значение.

#java #collections #list #set #map
Please open Telegram to view this post
VIEW IN TELEGRAM
👍152
⌨️ Что такое текстовые блоки (Text Blocks)?

Text Blocks (""") позволяют удобно работать с многострочными строками без необходимости экранировать кавычки.

Улучшают читаемость кода.
Поддерживают форматирование и перенос строк.
Упрощают работу с JSON, SQL и HTML.

✔️ Пример:


String json = """
{
"name": "Alice",
"age": 30
}
""";

System.out.println(json);


💡 Совет: Используйте Text Blocks для удобного написания больших строковых данных.

#java #textblocks #java15
Please open Telegram to view this post
VIEW IN TELEGRAM
👍22🔥21💯1