Пользовательские методы в записях
Добавляйте методы экземпляра и статические фабричные методы, расширяя возможности записей.
«Пользовательские методы в записях» — бесплатный урок Java Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.
Пользовательские методы записей
Записи могут содержать методы экземпляра, статические методы и статические поля — любое поведение, основанное на компонентах записи или связанное с ними.
Методы экземпляра в записях
Добавляйте в записи методы для вычисления производных значений или предоставления полезных операций на основе компонентов.
record Money(long cents, String currency) {
public double amount() { return cents / 100.0; }
public String display() { return String.format("%s %.2f", currency, amount()); }
public Money add(Money other) {
if (!currency.equals(other.currency))
throw new IllegalArgumentException("Currency mismatch");
return new Money(cents + other.cents, currency);
}
public Money multiply(int factor) { return new Money(cents * factor, currency); }
}
Money price = new Money(1999, "USD");
Money tax = new Money(160, "USD");
System.out.println(price.add(tax).display()); // USD 21.59Статические методы-фабрики
Статические методы-фабрики предоставляют именованные и понятные способы создания записей, скрывая вызов обычного конструктора, когда он недостаточно выразителен.
import java.time.*;
record DateRange(LocalDate start, LocalDate end) {
DateRange { if (start.isAfter(end)) throw new IllegalArgumentException(); }
public static DateRange of(LocalDate start, LocalDate end) {
return new DateRange(start, end);
}
public static DateRange thisMonth() {
LocalDate now = LocalDate.now();
return new DateRange(now.withDayOfMonth(1),
now.withDayOfMonth(now.lengthOfMonth()));
}
public long durationDays() {
return ChronoUnit.DAYS.between(start, end) + 1;
}
}
System.out.println(DateRange.thisMonth().durationDays());Методы-предикаты
Записи часто предоставляют методы-предикаты, возвращающие логическое значение для проверки условий, связанных с хранящимися в них данными.
record User(String email, String plan, boolean emailVerified) {
public boolean isPro() { return "PRO".equals(plan); }
public boolean canPublish() { return isPro() && emailVerified; }
public boolean isFree() { return "FREE".equals(plan); }
public User withPlan(String newPlan) {
return new User(email, newPlan, emailVerified); // "update"
}
}
User u = new User("alice@example.com", "FREE", true);
System.out.println(u.canPublish()); // false
User upgraded = u.withPlan("PRO");
System.out.println(upgraded.canPublish()); // trueМетоды создания копии с изменением
Поскольку записи неизменяемы, «обновление» означает создание новой записи. Методы создания копии с изменением следуют шаблону withX(newValue).
record ServerConfig(String host, int port, boolean ssl, int timeout) {
public ServerConfig withHost(String h) { return new ServerConfig(h, port, ssl, timeout); }
public ServerConfig withPort(int p) { return new ServerConfig(host, p, ssl, timeout); }
public ServerConfig withSsl(boolean s) { return new ServerConfig(host, port, s, timeout); }
public ServerConfig withTimeout(int t) { return new ServerConfig(host, port, ssl, t); }
}
ServerConfig dev = new ServerConfig("localhost", 8080, false, 30);
ServerConfig prod = dev
.withHost("api.example.com")
.withPort(443)
.withSsl(true)
.withTimeout(60);
System.out.println(prod);Записи, реализующие функциональные интерфейсы
Запись, которая также реализует функциональный интерфейс, может напрямую передаваться вместо лямбда-выражения.
import java.util.function.*;
record Validator<T>(Predicate<T> rule, String message)
implements Predicate<T> {
public boolean test(T value) { return rule.test(value); }
public String validate(T value) {
return test(value) ? null : message;
}
}
Validator<String> notEmpty = new Validator<>(
s -> !s.isBlank(), "Must not be blank");
Validator<String> validEmail = new Validator<>(
s -> s.contains("@"), "Invalid email");
System.out.println(notEmpty.validate("")); // Must not be blank
System.out.println(validEmail.validate("a@b.c")); // null (valid)Методы агрегации
Записи, содержащие коллекции, могут предоставлять методы агрегации на основе потоков.
import java.util.*;
record SalesReport(String region, List<Double> dailySales) {
SalesReport { dailySales = List.copyOf(dailySales); }
public double total() { return dailySales.stream().mapToDouble(Double::doubleValue).sum(); }
public double average() { return dailySales.stream().mapToDouble(Double::doubleValue).average().orElse(0); }
public double max() { return dailySales.stream().mapToDouble(Double::doubleValue).max().orElse(0); }
public int days() { return dailySales.size(); }
}
SalesReport r = new SalesReport("North", List.of(1200.0, 1450.0, 980.0, 1600.0));
System.out.printf("Total: %.0f, Avg: %.0f, Max: %.0f%n", r.total(), r.average(), r.max());
// Total: 5230, Avg: 1307, Max: 1600Переопределение toString
Автоматически создаваемый toString удобен для отладки, но для отображения пользователю может потребоваться его переопределить.
record Temperature(double celsius) {
@Override
public String toString() {
return String.format("%.1f\u00b0C / %.1f\u00b0F",
celsius, toFahrenheit());
}
public double toFahrenheit() { return celsius * 9.0/5.0 + 32; }
public double toKelvin() { return celsius + 273.15; }
public static Temperature fromFahrenheit(double f) {
return new Temperature((f - 32) * 5.0/9.0);
}
}
Temperature boiling = new Temperature(100.0);
System.out.println(boiling); // 100.0°C / 212.0°FЗаписи как объекты-значения в DDD
В предметно-ориентированном проектировании записи естественным образом соответствуют объектам-значения — неизменяемым типам, определяемым их атрибутами, а не идентичностью.
// Value objects in a banking domain
record AccountId(String value) {
AccountId { if (!value.matches("ACC-[0-9]{8}")) throw new IllegalArgumentException(); }
}
record Amount(long cents, String currency) {
public static Amount usd(long cents) { return new Amount(cents, "USD"); }
public boolean isPositive() { return cents > 0; }
}
record TransactionId(java.util.UUID value) {
public static TransactionId generate() {
return new TransactionId(java.util.UUID.randomUUID());
}
}
AccountId id = new AccountId("ACC-12345678");
Amount amount = Amount.usd(5000);
TransactionId txId = TransactionId.generate();
System.out.println(id + " " + amount + " " + txId);Сравнение записей с Comparator
Записи можно сортировать с помощью Comparator.comparing и ссылок на методы, указывающих на методы доступа.
import java.util.*;
record Employee(String name, String dept, double salary) {}
List<Employee> employees = List.of(
new Employee("Alice", "Engineering", 95000),
new Employee("Bob", "Marketing", 72000),
new Employee("Charlie", "Engineering", 105000),
new Employee("Diana", "Marketing", 81000)
);
// Sort by dept, then salary descending
employees.stream()
.sorted(Comparator.comparing(Employee::dept)
.thenComparing(Comparator.comparingDouble(Employee::salary).reversed()))
.forEach(e -> System.out.printf("%-10s %-15s %.0f%n",
e.name(), e.dept(), e.salary()));Статические вспомогательные методы записей
Записи могут содержать статические методы, работающие с несколькими экземплярами записей, что полезно для преобразований и сравнений.
import java.util.*;
import java.util.stream.*;
record Product(String id, String category, double price) {
public static Map<String, DoubleSummaryStatistics> statsByCategory(
List<Product> products) {
return products.stream().collect(
Collectors.groupingBy(Product::category,
Collectors.summarizingDouble(Product::price)));
}
public static Optional<Product> cheapest(List<Product> products) {
return products.stream()
.min(Comparator.comparingDouble(Product::price));
}
}Быстрая проверка
Вы хотите «обновить» поле записи. Какой подход является правильным?
Итоги: пользовательские методы записей
Главное:
- Записи могут содержать методы экземпляра, статические методы и статические поля
- Добавляйте вычисляемые свойства в виде методов (area(), total(), display())
- Используйте статические методы-фабрики для понятных именованных конструкторов
- Реализуйте методы создания копии с изменением (withX) для неизменяемых обновлений
- Переопределяйте toString() для отображения пользователю вместо отладочного вывода
- Записи естественным образом соответствуют объектам-значениям в предметно-ориентированном проектировании
Часто задаваемые вопросы
Урок «Пользовательские методы в записях» бесплатный?
Да — полный текст урока «Пользовательские методы в записях» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.
Чему я научусь в уроке «Пользовательские методы в записях»?
Добавляйте методы экземпляра и статические фабричные методы, расширяя возможности записей. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Java Academy?
Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Пользовательские методы в записях»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Java Academy?
Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Знакомство с записями
- Компактные конструкторы и проверка данных
- Пользовательские методы в записях
- Записи, классы и Lombok