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

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

✍️По всем вопросам: @Pascal4eg
Download Telegram
☕️Интеграция звуковых эффектов и музыки в игру на Java

😎Задача:
🟢При разработке игр на Java требуется работать со звуком и музыкой.

🟢Нужно подключить звуковые звуки (эффекты и музыку) к игровому проекту.

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

🟢Обеспечить громкость и возможность паузы/продолжения музыки.

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



😎Способы решения

🔢Использование стандартного Java Sound API


🔢Библиотека
🟢javax.sound.sampled позволяет работать с аудиофайлами формата WAV, AIFF и др.


🔢Использование различных библиотек
🟢Например, LWJGL OpenAL , TinySound , JOAL — более мощные и гибкие решения для работы со звуком в играх, 3D-звуке, резервных источниках и других возможностях.


🔢Интеграция с игровыми движками
🟢Если используются игровые движки (например, LibGDX), он обычно предоставляет собственный удобный API для работы со звуком.


😎Пример на Java с использованием стандартного Java Sound API


import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;

public class SoundPlayer {

    private Clip clip;

    // Загрузка и подготовка звукового файла
    public void loadSound(String filepath) {
        try {
            AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(new File(filepath));
            clip = AudioSystem.getClip();
            clip.open(audioInputStream);
        } catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {
            System.err.println("Ошибка загрузки звука: " + e.getMessage());
        }
    }

    // Воспроизведение звука
    public void play() {
        if (clip == null) return;
        if (clip.isRunning()) clip.stop();
        clip.setFramePosition(0);
        clip.start();
    }

    // Остановка звука
    public void stop() {
        if (clip != null && clip.isRunning()) {
            clip.stop();
        }
    }

    // Регулировка громкости (от 0.0 до 1.0)
    public void setVolume(float volume) {
        if (clip == null) return;
        FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
        float dB = (float)(Math.log10(volume) * 20);
        gainControl.setValue(dB);
    }

    public static void main(String[] args) {
        SoundPlayer sound = new SoundPlayer();
        sound.loadSound("jump.wav"); // Замените на путь к вашему звуковому файлу
        sound.setVolume(0.5f);
        sound.play();

        // Для демонстрации задержка перед завершением программы
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        sound.stop();
    }
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍115🔥1👏1
☕️Работа с сохранением и загрузкой прогресса игрока в Java: сериализация данных

😎Задача:
🟢Сохранить состояние игрового объекта (например, класс Player) в файл.

🟢Загрузить данные из файла при старте игры.

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

🟢Обработать возможные ошибки ввода-вывода.


😎Способы решения

🔢Стандартная сериализация Java (Serializable)

🟢Класс реализует интерфейс Serializable, и с помощью ObjectOutputStream и ObjectInputStream объекты записываются и читаются из файла.


🔢Использование JSON или XML

🟢Сериализация в человекочитаемый формат с помощью библиотек, например, Gson, Jackson (для JSON) или JAXB (для XML). Это удобно для отладки и совместимости.


🔢Кастомная сериализация

🟢Реализация собственных методов записи и чтения данных, например, через DataOutputStream/DataInputStream, что даёт больше контроля над форматом и размером файла.


😎Пример реализации на Java с использованием стандартной сериализации

import java.io.*;

class Player implements Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    int level;
    int score;

    public Player(String name, int level, int score) {
        this.name = name;
        this.level = level;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Player{name='" + name + "', level=" + level + ", score=" + score + '}';
    }
}

public class SaveLoadGame {
    public static void savePlayer(Player player, String filename) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
            oos.writeObject(player);
            System.out.println("Прогресс сохранён.");
        } catch (IOException e) {
            System.err.println("Ошибка при сохранении: " + e.getMessage());
        }
    }

    public static Player loadPlayer(String filename) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) {
            return (Player) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            System.err.println("Ошибка при загрузке: " + e.getMessage());
            return null;
        }
    }

    public static void main(String[] args) {
        Player player = new Player("Игрок1", 5, 1500);
        String saveFile = "player.dat";

        savePlayer(player, saveFile);

        Player loadedPlayer = loadPlayer(saveFile);
        if (loadedPlayer != null) {
            System.out.println("Загруженный игрок: " + loadedPlayer);
        }
    }
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍85🔥2
⌨️ Описание и назначение разных циклов

Цикл for со счетчиком

for (initialization; condition; update) {
// тело цикла
}

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

condition: проверяется перед каждой итерацией. Если условие истинно, выполняется тело цикла.

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


int[] numbers = {1, 2, 3, 4, 5};
for (int i = 0; i < numbers.length; i++) {
System.out.println(i + ": " + numbers[i]);
}

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


Цикл for-each

for (type element : collection) {
// тело цикла
}

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


int[] numbers = {1, 2, 3, 4, 5};
for (int number : numbers) {
System.out.println(number);
}

Применение:
Предпочтителен, когда не нужен доступ к индексам элементов.


Цикл while

while (condition) {
// тело цикла
}

Описание:
condition: проверяется перед каждой итерацией. Если условие истинно, выполняется тело цикла.


String code = UUID.randomUUID().toString();
boolean isCodeExists = repo.isCodeExists(code);
while (isCodeExists) {
code = UUID.randomUUID().toString();
isCodeExists = repo.isCodeExists(code);
}
repo.saveCode(code);

Применение:
Используется, когда количество итераций неизвестно заранее и определяется во время выполнения программы. Цикл может не выполниться ни одного раза.


Цикл do-while

do {
// тело цикла
} while (condition);

Описание:
Выполняет тело цикла хотя бы один раз, а затем проверяет условие condition. Если условие истинно, цикл повторяется.


String command;
do {
command = readCommand();
doCommand(command);
} while (!"EXIT".equals(command));

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

#java #cycles #for #while
Please open Telegram to view this post
VIEW IN TELEGRAM
👍81
⌨️ Методы compose и andThen интерфейса Function

Function<T,R> — функциональный интерфейс, который умеет принимать объект типа T и возвращать R. У него есть два метода-комбинатора:

default <V> Function<V,R> compose(Function<? super V,? extends T> before)
default <V> Function<T,V> andThen(Function<? super R,? extends V> after)


🔵f.compose(g) → сначала выполняет g, потом результат передаёт в f.
🔵f.andThen(h) → сначала выполняет f, потом h.

✔️ Пример:

import java.util.function.Function;

public class ComposeAndThenDemo {
public static void main(String[] args) {
Function<String, Integer> parse = Integer::parseInt;
Function<Integer, Double> half = i -> i / 2.0;
Function<Double, String> toStr = d -> "Result: " + d;

// Составляем: сначала parse, затем half, затем toStr
Function<String, String> pipeline =
toStr
.compose(half) // toStr(half(x))
.compose(parse); // toStr(half(parse(x)))

// То же самое через andThen:
Function<String, String> pipeline2 =
parse
.andThen(half) // half(parse(x))
.andThen(toStr); // toStr(half(parse(x)))

System.out.println(pipeline.apply("42")); // Result: 21.0
System.out.println(pipeline2.apply("42")); // Result: 21.0
}
}


compose строит цепочку справа-налево (parse → half → toStr).
andThen — слева-направо (parse → half → toStr).
Please open Telegram to view this post
VIEW IN TELEGRAM
👍104🔥1
⌨️ Что такое Heap и Stack память в Java? Какая разница между ними?

Heap (куча) используется Java Runtime для выделения памяти под объекты и классы. Создание нового объекта также происходит в куче. Это же является областью работы сборщика мусора. Любой объект, созданный в куче, имеет глобальный доступ и на него могут ссылаться из любой части приложения.

Stack (стек) это область хранения данных также находящееся в общей оперативной памяти (RAM). Всякий раз, когда вызывается метод, в памяти стека создается новый блок, который содержит примитивы и ссылки на другие объекты в методе. Как только метод заканчивает работу, блок также перестает использоваться, тем самым предоставляя доступ для следующего метода. Размер стековой памяти намного меньше объема памяти в куче. Стек в Java работает по схеме LIFO (Последний-зашел-Первый-вышел)

Различия между Heap и Stack памятью:

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

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

✔️ Объекты в куче доступны с любой точки программы, в то время как стековая память не может быть доступна для других потоков.

✔️ Стековая память существует лишь какое-то время работы программы, а память в куче живет с самого начала до конца работы программы.

✔️ Если память стека полностью занята, то Java Runtime бросает исключение java.lang.StackOverflowError. Если заполнена память кучи, то бросается исключение java.lang.OutOfMemoryError: Java Heap Space.

✔️ Размер памяти стека намного меньше памяти в куче.

✔️ Из-за простоты распределения памяти, стековая память работает намного быстрее кучи.

Для определения начального и максимального размера памяти в куче используются -Xms и -Xmx опции JVM. Для стека определить размер памяти можно с помощью опции -Xss.

#java #heap #stack #memory
Please open Telegram to view this post
VIEW IN TELEGRAM
👍92🔥1
⌨️ Что такое default методы интрефейса?

Java 8 позволяет добавлять неабстрактные реализации методов в интерфейс, используя ключевое слово default:

interface Example {
int process(int a);
default void show() {
System.out.println("default show()");
}
}



✔️Если класс реализует интерфейс, он может, но не обязан, реализовать методы по-умолчанию, уже реализованные в интерфейсе. Класс наследует реализацию по умолчанию.

✔️Если некий класс реализует несколько интерфейсов, которые имеют одинаковый метод по умолчанию, то класс должен реализовать метод с совпадающей сигнатурой самостоятельно. Ситуация аналогична, если один интерфейс имеет метод по умолчанию, а в другом этот же метод является абстрактным - никакой реализации по умолчанию классом не наследуется.

✔️Метод по умолчанию не может переопределить метод класса java.lang.Object.

✔️Помогают реализовывать интерфейсы без страха нарушить работу других классов.

✔️Позволяют избежать создания служебных классов, так как все необходимые методы могут быть представлены в самих интерфейсах.

✔️Дают свободу классам выбрать метод, который нужно переопределить.

✔️Одной из основных причин внедрения методов по умолчанию является возможность коллекций в Java 8 использовать лямбда-выражения.

Вызывать default метод интерфейса в реализующем этот интерфейс классе можно используя ключевое слово super вместе с именем интерфейса:

interface Paper {
default void show() {
System.out.println("default show()");
}
}

class Licence implements Paper {
public void show() {
Paper.super.show();
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
🤝10👍21🔥1
⌨️ Что такое static метод интерфейса?

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

✔️Статические методы в интерфейсе являются частью интерфейса без возможности переопределить их для объектов класса реализации;

✔️Методы класса java.lang.Object нельзя переопределить как статические;

✔️Статические методы в интерфейсе используются для обеспечения вспомогательных методов, например, проверки на null, сортировки коллекций и т.д.

Вызывать static метод интерфейса можно используя имя интерфейса:

interface Paper {
static void show() {
System.out.println("static show()");
}
}

class Licence {
public void showPaper() {
Paper.show();
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍161🔥1
⌨️ Switch Expressions

В Java 12 появился новый синтаксис для switch, который позволяет возвращать значение используя стрелки ->, что делает код более компактным и удобным. Теперь switch может использоваться как выражение, а не только как оператор, что упрощает его применение в логике.

Ранее switch использовался как оператор, и код мог выглядеть громоздко:

String day = "MONDAY";
int numLetters;
switch (day) {
case "MONDAY":
case "FRIDAY":
case "SUNDAY":
numLetters = 6;
break;
case "TUESDAY":
numLetters = 7;
break;
default:
numLetters = 8;
}
System.out.println(numLetters); // Вывод: 6


Со Switch Expressions можно переписать этот код более лаконично:

String day = "MONDAY";
int numLetters = switch (day) {
case "MONDAY", "FRIDAY", "SUNDAY" -> 6;
case "TUESDAY" -> 7;
default -> 8;
};
System.out.println(numLetters); // Вывод: 6


Теперь switch может вернуть значение, и нет необходимости в break.

#java #switch
Please open Telegram to view this post
VIEW IN TELEGRAM
👍223
⌨️ Использование java.util.stream.Collectors для обработки коллекций

Код:

import java.util.List;
import java.util.stream.Collectors;

public class StreamCollectorsExample {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Примеры использования Collectors
// Преобразование элементов коллекции в строку
String numbersAsString = numbers.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
System.out.println("Числа в виде строки: " + numbersAsString);

// Получение среднего значения всех элементов коллекции
double average = numbers.stream()
.collect(Collectors.averagingInt(Integer::intValue));
System.out.println("Среднее значение: " + average);

// Фильтрация элементов и сборка их в новую коллекцию
List<Integer> evenNumbers = numbers.stream()
.filter(num -> num % 2 == 0)
.collect(Collectors.toList());
System.out.println("Четные числа: " + evenNumbers);

// Группировка элементов по условию
System.out.println("Числа по остатку от деления на 3: " + numbers.stream()
.collect(Collectors.groupingBy(num -> num % 3)));
}
}


⚙️Этот пример демонстрирует использование класса Collectors из пакета java.util.stream для более эффективной обработки коллекций в Java. Методы класса Collectors предоставляют различные операции для сбора (collecting) элементов из потока данных в различные структуры данных или для выполнения агрегатных операций над элементами потока.
Please open Telegram to view this post
VIEW IN TELEGRAM
6❤‍🔥1👍1
Varargs (Variable Arguments List, изменяющийся список аргументов) — это способ создания методов, которые могут принимать произвольное количество аргументов одного типа (от нуля и более). Данная возможность появилась в JDK 5.

Запись вида Object... args и есть varargs.

При этом три точки после типа указывают, что метод в качестве аргумента может принимать как массив, так и любую последовательность аргументов, записанных через запятую, которая все равно преобразуется в одномерный массив - «под капотом» компилятор на уровне байт-кода неявно заменяет переданную последовательность массивом. Уже в методе аргумент varargs используется как одномерный массив.

Альтернативой varargs является перегрузка методов или передача в метод массива значений.

Varargs был создан с целью упрощения работы программиста, удобства и краткости кода.

📌 В качестве ограничения любой метод может использовать varargs только в единственном числе и строго последним аргументом.
👍113
⌨️ var в Java 10+: удобство или ловушка?

Java 10 принес с собой var — возможность объявлять переменные без явного указания типа. Это делает код короче, но может иметь последствия.

Пример:
var list = new ArrayList<String>();


➡️ Компилятор сам выводит тип. Вроде удобно. Но давай разберем плюсы и минусы.

✔️ Плюсы использования var:

1. 👍 Меньше шаблонного кода

Не надо повторять длинные типы:
   var map = new HashMap<String, List<Integer>>();


2. 👍 Лучшая читаемость при очевидных типах

Когда справа всё ясно — var делает код чище.

3. 👍 Удобно для циклов
   for (var entry : map.entrySet()) { ... }


➡️ особенно в сочетании с лямбдами и stream API.

⚠️ Подводные камни:

1. 🟢 Потеря явности типа
   var result = someMethod();  // Что за тип?


➡️ Нужно смотреть реализацию метода или IDE-подсказки.

2. 🟢 Повышается риск ошибок

Пример: можно случайно получить Object, если возвращается не тот тип.

3. 🟢 Плох для публичного API

В методах и сигнатурах var использовать нельзя — и это правильно: API должно быть прозрачным.

📌 Лучшие практики:

✔️ Используй var, когда тип очевиден
Избегай в сложных выражениях с неочевидным выводом типа
Не используй в публичных API или при работе в больших командах без согласованных правил

🗣️ Запомни: var — это инструмент, не костыль. Он не делает Java динамической. Используй его с умом — для читаемости, а не ради моды.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍82
⌨️ Есть ли какие-либо рекомендации о том, какие поля следует использовать при подсчете hashCode()?

Общий совет: выбирать поля, которые с большой долью вероятности будут различаться. Для этого необходимо использовать уникальные, лучше всего примитивные поля, например, такие как id, uuid. При этом нужно следовать правилу, если поля задействованы при вычислении hashCode(), то они должны быть задействованы и при выполнении equals().

#java #hashCode #equals
Please open Telegram to view this post
VIEW IN TELEGRAM
👨‍💻62
⌨️ Поддержка лямбда выражений в своём коде

В Java лямбда-выражения используются с функциональными интерфейсами, которые имеют ровно один абстрактный метод. Например, стандартный функциональный интерфейс Function из пакета java.util.function представляет собой типичный пример.

Для нашего примера используем встроенный функциональный интерфейс IntUnaryOperator. Он выглядит вот так:

@FunctionalInterface
public interface IntUnaryOperator {
int applyAsInt(int operand);
}


Этот интерфейс представляет функцию, которая принимает один аргумент типа int и возвращает значение типа int.


import java.util.function.IntUnaryOperator;

public class Main {
public static int applyOperation(int number, IntUnaryOperator operator) {
return operator.applyAsInt(number);
}

public static void main(String[] args) {
int number = 5;

// Лямбда-выражение для удвоения значения
IntUnaryOperator doubleOperation = x -> x * 2;
System.out.println("Double: " + applyOperation(number, doubleOperation)); // Double: 10

// Лямбда-выражение для увеличения значения на 10
IntUnaryOperator addTenOperation = x -> x + 10;
System.out.println("Add Ten: " + applyOperation(number, addTenOperation)); // Add Ten: 15
}
}

В нашем примере метод applyOperation принимает два параметра: число и функциональный интерфейс IntUnaryOperator. Лямбда-выражение используется для определения конкретной функции.

#java #lambda #IntUnaryOperator
Please open Telegram to view this post
VIEW IN TELEGRAM
👍62
☕️Использование метода computeIfAbsent() из интерфейса Map.

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

Пример кода с объяснением:


import java.util.HashMap;
import java.util.Map;

public class MapExample {
public static void main(String[] args) {
Map<String, Integer> wordLengths = new HashMap<>();
String word = "apple";

// Использование computeIfAbsent для вычисления и вставки значения в Map
wordLengths.computeIfAbsent(word, String::length);

// Вывод длины слова "apple", которая была вычислена и вставлена в Map
System.out.println("Длина слова apple: " + wordLengths.get(word));
}
}

🔒В этом примере мы используем метод computeIfAbsent() для wordLengths, чтобы вычислить и вставить длину слова "apple" в Map, если она отсутствует. Если значение уже присутствует, метод возвращает текущее значение.

Этот метод предоставляет удобный и эффективный способ добавления значений в мапу, если они отсутствуют, что может помочь упростить код и избежать лишних проверок на наличие ключей в Map.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍133👨‍💻1
⌨️ Generics и стирание типов

При компиляции все параметры обобщённых типов удаляются («стираются»), и на их место подставляется либо ограничивающий тип (T extends Number → Number), либо Object.
Это нужно для совместимости с «сырыми» классами до появления Generics в Java 5.

Последствия стирания типов:

1️⃣ Нельзя создавать массивы обобщённого типа:

// Ошибка компиляции!
List<String>[] array = new List<String>[10];


2️⃣ Нельзя проверить во время выполнения, является ли объект именно List<String>:

List<String> list = List.of("a", "b");
if (list instanceof List<String>) { … } // компилятор не пропустит


3️⃣ Перегрузка методов с разными параметрами-дженериками конфликтует:

// Ошибка name clash: стираются оба в process(Collection)
void process(Collection<String> c) { … }
void process(Collection<Integer> c) { … }


Стиpaниe типов позволяет Java Generics не увеличивать размер байт-кода и оставаться совместимой с ранними версиями платформы, но налагает ограничения на проверку и создание обобщённых типов во время выполнения.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍63
🧪 Тесты с JUnit 5 — основные аннотации

JUnit 5 — это не просто @Test. Он принёс гибкость, читаемость и мощные возможности для модульного тестирования.

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

1. @Test — запуск теста

@Test
void shouldReturnTrueWhenInputIsValid() {
assertTrue(MyService.validate("test"));
}


➡️ Основная аннотация. Методы с ней — это тесты.

2. 🧱 @BeforeEach и @AfterEach — подготовка и очистка

@BeforeEach
void init() {
db.connect();
}

@AfterEach
void cleanup() {
db.disconnect();
}


➡️ Выполняются до и после каждого теста. Удобно для настройки окружения.

3. 🏗 @BeforeAll и @AfterAll — один раз на весь класс

@BeforeAll
static void globalSetup() {
System.out.println("Запускаем все тесты");
}


➡️ Статические методы, которые выполняются один раз перед/после всех тестов.


4. 🎯 @DisplayName — читаемое имя теста

@DisplayName("Должен вернуть true при корректном email")
@Test
void validEmailTest() {
assertTrue(EmailValidator.isValid("test@example.com"));
}


➡️ Название будет красиво отображаться в отчётах и IDE.

5. 🧪 @Nested — группировка тестов

@Nested
class WhenUserIsAdmin {
@Test
void shouldHaveFullAccess() { ... }
}


➡️Логическая структура внутри тест-класса. Удобно для сценариев.

6. ⚙️ @ParameterizedTest + @ValueSource — параметризованные тесты

@ParameterizedTest
@ValueSource(strings = {"admin", "user", "guest"})
void roleShouldNotBeNull(String role) {
assertNotNull(role);
}


➡️ Запускает один и тот же тест с разными значениями.

7. 🚫 @Disabled — временно отключить тест

@Disabled("Фича ещё не реализована")
@Test
void futureFeatureTest() {
fail("не должно запускаться");
}


➡️ Полезно, когда фича в разработке, а тест уже написан.

🗣 Запомни:JUnit 5 — это про читаемость, контроль и минимум лишнего кода. Используй аннотации не ради галочки, а чтобы тесты были понятны даже спустя полгода.
Please open Telegram to view this post
VIEW IN TELEGRAM
14👍6❤‍🔥1🔥1
😁43👨‍💻3❤‍🔥1👍1👏1
🔄 stream().map().filter().collect() — читаемо и мощно

Java Stream API — это как grep | awk | sort в Linux, только в Java-стиле.
Самая частая связка: map → filter → collect. И она может заменить кучу шаблонного кода.

Допустим, у нас есть список:


List<String> names = List.of("Alice", "bob", "CHARLIE", "dave");


👉 Хочешь получить отфильтрованные, приведённые к верхнему регистру имена, которые длиннее 3 символов?

До Stream API:


List<String> result = new ArrayList<>();
for (String name : names) {
String upper = name.toUpperCase();
if (upper.length() > 3) {
result.add(upper);
}
}


С stream():

List<String> result = names.stream()
.map(String::toUpperCase)
.filter(s -> s.length() > 3)
.collect(Collectors.toList());


➡️ Читаемо, как SQL: "преобразуй → отфильтруй → собери".


✔️ Разберём подробнее:

1. `map()` — преобразование
   .map(String::trim)
.map(String::toLowerCase)


🔄 Применяет функцию ко всем элементам. Тип может измениться.

2. `filter()` — отбор
   .filter(s -> s.startsWith("a"))


Оставляет только те элементы, которые удовлетворяют условию.

3. `collect()` — сборка результата
   .collect(Collectors.toSet())
.collect(Collectors.joining(", "))


🧺 Собирает результат обратно в список, множество, строку и т.д.

📌 Реальный пример — фильтруем активных пользователей по возрасту:

List<User> users = getUsers();

List<String> activeAdults = users.stream()
.filter(User::isActive)
.filter(u -> u.getAge() >= 18)
.map(User::getName)
.collect(Collectors.toList());


➡️ Без лишних if, for и add() — код фокусируется на логике, а не на реализации.

🗣 Запомни: Цепочка map().filter().collect() — это про читаемый и декларативный код.
Не пиши как императивный робот, пиши как человек: что ты хочешь получить — а не как это сделать.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍153🔥2👨‍💻1
☕️ final в Java — стоп-сигнал для изменений

В Java final — это как «не трогай»: запрет на изменение.
Но что именно он запрещает? Всё зависит от того, где его поставить: переменная, метод, класс.

Разберёмся 👇

1. 📦 final переменные — нельзя переназначить

final int port = 8080;
port = 9090; // ошибка компиляции


➡️ Значение фиксируется один раз. Для примитивов — это само значение, для объектов — это ссылка.

final List<String> list = new ArrayList<>();
list.add("Hello"); // можно
list = new ArrayList<>(); // нельзя


🧠 Важно: final не делает объект неизменяемым. Только ссылку.

2. 🔒 final методы — нельзя переопределить в наследниках

class Animal {
final void sleep() {
System.out.println("Zzz...");
}
}

class Dog extends Animal {
// Нельзя переопределить sleep()
}


➡️ Полезно, если хочешь защитить логику метода от изменений в подклассах.

3. 🧱 final классы — нельзя наследовать вообще

final class Utility {
static void log(String msg) { ... }
}


class MyUtil extends Utility {} //  ошибка


➡️ Применяют для:

🟢 утилитарных классов (Math, String)
🟢 защиты от подмены логики (SecurityManager, System)

🎯 Частые применения в дикой природе:

🟢 final аргументы в методах → защищает от случайного переназначения
🟢 final поля + @Immutable → хорошая практика для DTO
🟢final классы → в record, enum, String, LocalDate и т.д.


🗣 Запомни:final = контроль. Это способ сказать «вот это трогать нельзя». Используй его осознанно — чтобы сделать поведение предсказуемым и безопасным.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥3👨‍💻21
☕️Text Blocks в Java: читаемые строки без \n и слёз

До Java 13 работа с многострочными строками была болью. JSON? HTML? SQL? Только через "\n" + и кучу экранирования.
Но с Text Blocks всё стало проще, понятнее и читаемо.

Сравни:

До:
String html = "<html>\n" +
" <body>Hello</body>\n" +
"</html>";


После (Text Block):

String html = """
<html>
<body>Hello</body>
</html>
""";


➡️ Стало чище, короче и как в оригинале. Это обычная String, просто оформленная красиво.

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

1. 👍 Читаемость на уровне "как есть"
Код больше не превращается в кашу. Особенно полезно для:

SQL-запросов:
     String query = """
SELECT id, name
FROM users
WHERE active = true
ORDER BY created_at DESC
""";


HTML/JSON шаблонов:

     String json = """
{
"name": "Alice",
"role": "admin"
}
""";


2. 👍 Меньше экранирования
Забудь про \", \\n, \\t — теперь можно писать почти как в блокноте.

3. 👍 Автоматическое выравнивание
Java сама уберёт начальные отступы на основе самой "узкой" строки.

Пример:
   String msg = """
Line 1
Line 2
Line 3
""";


➡️ Результат будет:
Line 1
Line 2
Line 3


4. 👍 Интеграция с .formatted()
Нельзя вставить переменные прямо в Text Block?
Используем .formatted() — коротко и читабельно:
String user = "Bob";
String template = """
{
"user": "%s",
"access": "granted"
}
""".formatted(user);
````
```java
String s = """
Hello""";
System.out.println(s.length()); // 6, а не 5 (есть \n)


3. 🟢 Нет интерполяции переменных
Нельзя писать так:
   String name = "Eve";
String wrong = """
Hello, $name!
"""; // не сработает


Вместо этого:
   String right = """
Hello, %s!
""".formatted(name);


🗣️ Запомни: Text Blocks делают строки в Java человечными. Это не просто сахар — это инструмент, чтобы твой код говорил с тобой на одном языке. Используй их для всего, что требует форматирования — и забудь про + "\n".
Please open Telegram to view this post
VIEW IN TELEGRAM
18👍4👨‍💻2🔥1