Когда в Java использовать Record вместо Class и наоборот

Источник: Medium Изучив это руководство, вы научитесь точно определять, в каких ситуациях стоит использовать Record вместо Class и наоборот. Кофе-брейк #242. Когда в Java использовать Record вместо Class и наоборот. Интерфейсы Consumer, Predicate и Supplier в Java - 1Недавно я получил комментарий, в котором предлагалось преобразовать определенный Class в Record. Я задумался, в каких же случаях нам следует использовать Record вместо Class, а когда Class вместо Record. Сегодня мне хотелось бы изложить по этому поводу свои наблюдения.

Что такое Record в Java?

Я предполагаю, что вы уже осведомлены о записи (Record) в Java, но в любом случае я кратко напомню о ней. Records впервые представлены ​​в Java 14 в качестве предварительной версии, а затем они стали стандартной функцией в Java 16.

// Как определить record
public record Person(String name, int age) { }

// Как использовать record.
Person person = new Person("Tommy", 21);
// методы получения record.
System.out.println(person.name(), person.age());
Исходя из названия, Record предназначена для хранения неизменяемой записи (то есть, определенной информации). Например, я хочу сохранить имя человека и его дату рождения или страну и ее столицу. Это неизменяемая информация. Раньше я бы создал POJO (обычный старый объект Java) или Hashmap. Очевидно, это были не самые чистые решения. Теперь я могу просто создать запись (Record):

public record ApiResponse (int statusCode, String message) {}
Record имеет встроенные функции toString(), equals() и hashCode().

Person tommy = new Person("Tommy", 12);
Person bomma = new Person("Bomma", 18);
Person bomma2 = new Person("Bomma", 18);

// встроенная toString()
System.out.println(tommy);
// будет напечатано: Person[name=Tommy, age=12]

// встроенная функция equals() для проверки равенства двух записей
System.out.println(tommy.equals(bomma));// false
System.out.println(bomma2.equals(bomma));// true
Я не буду вдаваться в преимущества Record над Class или наоборот, потому что у обоих есть свои варианты использования. Нужно лишь понять, когда их применять в зависимости от ситуации.

Когда мы должны использовать Record вместо Class

Итак, давайте поговорим об основной цели этой статьи. Когда использовать Record? Базовый вариант использования записи — хранение неизменяемых данных. Например: point(x,y), api-response(code, message), country(name, capital, continent) и так далее. Когда данный Class не имеет никакого метода setter или каких-либо дополнительных поведенческих методов, мы можем использовать Record. Если этот Class имеет только конструктор и метод getter, то он должен быть преобразован в Record. Как уже указано выше, запись неизменяема. Поля по умолчанию final. Как и в случае со String, неизменность в Record помогает при случайных изменениях в параллельном коде, а также помогает в обеспечении безопасности. Мы должны использовать Record всякий раз, когда это возможно, потому что он предлагает более лаконичный и понятный код. В нем уже есть по умолчанию toString(), equals() и hashCode(). Следовательно, это улучшает читаемость за счет сокращения стандартного кода.

Когда мы должны использовать Class вместо Record

Большую часть времени мы будем использовать class. Когда у вас есть поведение, изменение состояния экземпляра и так далее, вы должны использовать class. Например, у нас есть класс Person и такие методы, как updateAddress(), jump(), run() и другие. Если же вам нужно настроить equals(), hashCode() или toString(), вам также придется использовать Class, где вы можете написать свои собственные настраиваемые методы. Запомните правило: когда вы видите, что класс имеет только методы конструктора и метод getter и у него отсутствуют методы setter или какие-либо другие поведенческие методы, то вы можете преобразовать класс в запись. Когда есть потребность в методе setter, изменении состояния экземпляра или любых поведенческих методах, то тогда, очевидно, вам придется использовать класс.

Интерфейсы Consumer, Predicate и Supplier в Java

Источник: Medium Благодаря этой публикации вы узнаете, где в языке Java нужны функциональные интерфейсы Consumer, Predicate и Supplier. В последние годы в мире Java-разработки значительную популярность приобрело функциональное программирование. С выходом Java 8 в языке появились функциональные интерфейсы, которые позволяют писать более лаконичный и выразительный код. Среди основных функциональных интерфейсов, предоставляемых пакетом java.util.function, стоит выделить Consumer, Predicate и Supplier. Эти интерфейсы предоставляют мощные инструменты для использования, фильтрации и создания объектов, позволяя разработчикам писать чистый, модульный и повторно используемый код.

1. Consumer (Потребитель)

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

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
Интерфейс Consumer является частью пакета java.util.function и имеет единственный абстрактный метод accept. Еще один фрагмент кода демонстрирует использование метода andThen, который позволяет объединять несколько Consumer в цепочку.

Consumer<String> printUpperCase = str -> System.out.println(str.toUpperCase());
printUpperCase.accept("hello"); 

Consumer<String> printLowerCase = str -> System.out.println(str.toLowerCase());
Consumer<String> printBoth = printUpperCase.andThen(printLowerCase);
printBoth.accept("WORLD"); 
Вывод:
HELLO WORLD world

2. Predicate (Предикат)

Интерфейс Predicate представляет собой функцию с логическим значением, которая принимает входные данные и возвращает логическое значение на основе указанного условия. Предикаты — это мощные инструменты для фильтрации и условной обработки объектов. Предикаты можно комбинировать с помощью логических операторов, таких как AND, OR и NOT, для создания более сложных условий. Они полезны для таких задач, как фильтрация данных, проверка и условная обработка.

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
Интерфейс Predicate предоставляет несколько методов, включая test, and, negate, or и isEqual. Использование предиката в Java:

Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(4)); // Вывод: true
System.out.println(isEven.test(7)); // Вывод: false


Predicate<Integer> isGreaterThanTen = num -> num > 10;
Predicate<Integer> isEvenAndGreaterThanTen = isEven.and(isGreaterThanTen);
System.out.println(isEvenAndGreaterThanTen.test(12)); // Вывод: true

Predicate<Integer> isOddOrLessThanFive = isEven.negate().or(num -> num < 5);
System.out.println(isOddOrLessThanFive.test(3)); // Вывод: true
Вывод:
true false true true

3. Supplier (Поставщик)

Интерфейс Supplier не принимает никаких аргументов, но выдает результат определенного типа. Поставщики полезны в сценариях, где объекты необходимо динамически генерировать. Они позволяют разработчикам инкапсулировать логику создания объектов и предоставляют гибкий способ создания экземпляров по требованию. Это может быть особенно полезно при создании объектов, требующих сложной инициализации, или при работе со стратегиями ленивой инициализации.

package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
    T get();
}
Интерфейс Supplier прост, он определяет единственный вызываемый метод get, который возвращает произведенный результат типа T. Использование Supplier в Java:

Supplier<Double> randomNumberSupplier = Math::random;
System.out.println(randomNumberSupplier.get()); 

Supplier<String> greetingsSupplier = () -> {
    String[] greetings = {"Hello", "Bonjour", "Hola", "Namaste"};
    int randomIndex =  (((int) (Math.random() * 10)) % greetings.length );
    return greetings[randomIndex];
};
System.out.println(greetingsSupplier.get()); 
Вывод:
0.8706263533820684 Hola