Collectors в Java
Collectors.toList
Collectors.toList() собирает элементы потока в List. Реализация по умолчанию возвращает ArrayList, но конкретная реализация не гарантируется и может измениться. Это один из самых простых и часто используемых коллекторов.
Пример:
Внутренне toList использует:
Supplier: new ArrayList<>()
Accumulator: list.add(element)
Combiner: list1.addAll(list2)
Finisher: возвращает сам аккумулятор (identity функция).
Особенности:
Сохраняет порядок элементов (если поток упорядочен).
Допускает дубликаты.
Не поддерживает настройку типа List (для этого есть toCollection).
Collectors.toSet
Collectors.toSet() собирает элементы потока в Set, по умолчанию возвращая HashSet. Как и в случае с toList, конкретная реализация не гарантируется.
Пример:
Внутренне toSet использует:
Supplier: new HashSet<>()
Accumulator: set.add(element)
Combiner: set1.addAll(set2)
Finisher: возвращает аккумулятор.
Особенности:
Удаляет дубликаты (Set не допускает повторяющихся элементов).
Не гарантирует порядок элементов (HashSet не сохраняет порядок).
Для упорядоченного множества можно использовать toCollection(TreeSet::new).
#Java #Training #Medium #Collectors #CollectorsToList #CollectorsToSet
Collectors.toList
Collectors.toList() собирает элементы потока в List. Реализация по умолчанию возвращает ArrayList, но конкретная реализация не гарантируется и может измениться. Это один из самых простых и часто используемых коллекторов.
Пример:
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ToListExample {
public static void main(String[] args) {
Stream<String> stream = Stream.of("apple", "banana", "orange");
List<String> list = stream.collect(Collectors.toList());
System.out.println(list); // [apple, banana, orange]
}
}
Внутренне toList использует:
Supplier: new ArrayList<>()
Accumulator: list.add(element)
Combiner: list1.addAll(list2)
Finisher: возвращает сам аккумулятор (identity функция).
Особенности:
Сохраняет порядок элементов (если поток упорядочен).
Допускает дубликаты.
Не поддерживает настройку типа List (для этого есть toCollection).
Collectors.toSet
Collectors.toSet() собирает элементы потока в Set, по умолчанию возвращая HashSet. Как и в случае с toList, конкретная реализация не гарантируется.
Пример:
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ToSetExample {
public static void main(String[] args) {
Stream<String> stream = Stream.of("apple", "banana", "apple");
Set<String> set = stream.collect(Collectors.toSet());
System.out.println(set); // [apple, banana]
}
}
Внутренне toSet использует:
Supplier: new HashSet<>()
Accumulator: set.add(element)
Combiner: set1.addAll(set2)
Finisher: возвращает аккумулятор.
Особенности:
Удаляет дубликаты (Set не допускает повторяющихся элементов).
Не гарантирует порядок элементов (HashSet не сохраняет порядок).
Для упорядоченного множества можно использовать toCollection(TreeSet::new).
#Java #Training #Medium #Collectors #CollectorsToList #CollectorsToSet
Collectors в Java
Collectors.toMap
Collectors.toMap – это мощный коллектор, преобразующий элементы потока в Map<K, V>, где ключи и значения вычисляются на основе элементов потока.
1. Базовая форма: toMap(keyMapper, valueMapper)
Преобразует поток в Map, где:
keyMapper – функция, вычисляющая ключ,
valueMapper – функция, вычисляющая значение.
Сигнатура:
Пример:
2. Обработка дубликатов: toMap(keyMapper, valueMapper, mergeFunction)
Если возможны повторяющиеся ключи, нужно указать mergeFunction – стратегию разрешения конфликтов.
Сигнатура:
Примеры:
2.1. Оставить старое значение
2.2. Объединить значения (например, конкатенация строк)
2.3. Суммирование значений при дублировании ключей
3. Выбор реализации Map: toMap(keyMapper, valueMapper, mergeFunction, mapFactory)
Позволяет указать конкретную реализацию Map (например, LinkedHashMap или TreeMap).
Сигнатура:
Примеры:
3.1. Сохранение порядка вставки (LinkedHashMap)
3.2. Сортировка по ключу (TreeMap)
#Java #Training #Medium #Collectors #CollectorsToMap
Collectors.toMap
Collectors.toMap – это мощный коллектор, преобразующий элементы потока в Map<K, V>, где ключи и значения вычисляются на основе элементов потока.
1. Базовая форма: toMap(keyMapper, valueMapper)
Преобразует поток в Map, где:
keyMapper – функция, вычисляющая ключ,
valueMapper – функция, вычисляющая значение.
Сигнатура:
public static <T, K, U> Collector<T, ?, Map<K, U>> toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper
)
Пример:
List<String> names = List.of("Alice", "Bob", "Charlie");
// Создаем Map: {имя -> длина строки}
Map<String, Integer> nameToLength = names.stream()
.collect(Collectors.toMap(
name -> name, // ключ – сама строка
name -> name.length() // значение – длина строки
));
// Результат: {"Alice":5, "Bob":3, "Charlie":7}
Что произойдет, если ключи повторяются?
→ Будет выброшено IllegalStateException!
2. Обработка дубликатов: toMap(keyMapper, valueMapper, mergeFunction)
Если возможны повторяющиеся ключи, нужно указать mergeFunction – стратегию разрешения конфликтов.
Сигнатура:
public static <T, K, U> Collector<T, ?, Map<K, U>> toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction
)
Примеры:
2.1. Оставить старое значение
List<String> names = List.of("Alice", "Bob", "Alice");
Map<String, Integer> nameToLength = names.stream()
.collect(Collectors.toMap(
name -> name,
name -> name.length(),
(oldValue, newValue) -> oldValue // при конфликте берем старое значение
));
// Результат: {"Alice":5, "Bob":3}
2.2. Объединить значения (например, конкатенация строк)
List<String> names = List.of("A", "B", "A", "C");
Map<String, String> mergedLetters = names.stream()
.collect(Collectors.toMap(
letter -> letter,
letter -> letter,
(oldVal, newVal) -> oldVal + newVal // "A" + "A" → "AA"
));
// Результат: {"A":"AA", "B":"B", "C":"C"}
2.3. Суммирование значений при дублировании ключей
record Product(String id, double price) {}
List<Product> products = List.of(
new Product("A", 10.0),
new Product("B", 20.0),
new Product("A", 15.0)
);
Map<String, Double> totalPriceById = products.stream()
.collect(Collectors.toMap(
Product::id,
Product::price,
Double::sum // 10.0 + 15.0 = 25.0
));
// Результат: {"A":25.0, "B":20.0}
3. Выбор реализации Map: toMap(keyMapper, valueMapper, mergeFunction, mapFactory)
Позволяет указать конкретную реализацию Map (например, LinkedHashMap или TreeMap).
Сигнатура:
public static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapFactory
)
Примеры:
3.1. Сохранение порядка вставки (LinkedHashMap)
Map<String, Integer> orderedMap = names.stream()
.collect(Collectors.toMap(
name -> name,
name -> name.length(),
(oldVal, newVal) -> oldVal,
LinkedHashMap::new
));
3.2. Сортировка по ключу (TreeMap)
Map<String, Integer> sortedMap = names.stream()
.collect(Collectors.toMap(
name -> name,
name -> name.length(),
(oldVal, newVal) -> oldVal,
TreeMap::new
));
#Java #Training #Medium #Collectors #CollectorsToMap
4. Особенности и подводные камни
4.1. Null-ключи и Null-значения
HashMap допускает один null-ключ, но если keyMapper возвращает null – будет NullPointerException.
Если valueMapper возвращает null, значение в Map будет null.
4.2. Параллельные стримы
toMap не оптимизирован для параллельных операций.
Вместо него лучше использовать Collectors.toConcurrentMap.
4.3. Изменяемые ключи
Если ключ изменяется после добавления в Map, это может нарушить работу HashMap (ключи должны быть immutable).
5. Альтернативы
5.1. Collectors.toUnmodifiableMap (Java 10+)
Создает неизменяемую Map:
5.2. Collectors.toConcurrentMap
Оптимизирован для многопоточности:
#Java #Training #Medium #Collectors #CollectorsToMap
4.1. Null-ключи и Null-значения
HashMap допускает один null-ключ, но если keyMapper возвращает null – будет NullPointerException.
Если valueMapper возвращает null, значение в Map будет null.
4.2. Параллельные стримы
toMap не оптимизирован для параллельных операций.
Вместо него лучше использовать Collectors.toConcurrentMap.
4.3. Изменяемые ключи
Если ключ изменяется после добавления в Map, это может нарушить работу HashMap (ключи должны быть immutable).
5. Альтернативы
5.1. Collectors.toUnmodifiableMap (Java 10+)
Создает неизменяемую Map:
Map<String, Integer> unmodifiableMap = names.stream()
.collect(Collectors.toUnmodifiableMap(
name -> name,
name -> name.length()
));
5.2. Collectors.toConcurrentMap
Оптимизирован для многопоточности:
ConcurrentMap<String, Integer> concurrentMap = names.stream()
.collect(Collectors.toConcurrentMap(
name -> name,
name -> name.length()
));
#Java #Training #Medium #Collectors #CollectorsToMap
Collectors в Java
groupingBy
Collectors.groupingBy – один из самых мощных коллекторов в Java Stream API, позволяющий группировать элементы потока по заданному критерию. Возвращает Map<K, List<T>>, где ключ (K) – это значение классификатора, а значение – список элементов, соответствующих этому ключу.
1. Базовая форма: groupingBy(classifier)
Группирует элементы по ключу, возвращаемому classifier (функцией классификации).
Сигнатура:
Пример:
Как это работает внутри?
Классификатор (String::length) вычисляет ключ для каждого элемента.
Элементы с одинаковым ключом добавляются в List<T>.
По умолчанию используется HashMap, а значения хранятся в ArrayList.
2. groupingBy(classifier, downstream) – с дополнительным коллектором
Позволяет указать, как собирать элементы в группы (не только в List).
Сигнатура:
Примеры:
2.1. Группировка в Set вместо List
2.2. Подсчет количества элементов в каждой группе
2.3. Объединение элементов в строку
3. groupingBy(classifier, mapFactory, downstream) – с выбором реализации Map
Позволяет указать конкретную реализацию Map (например, TreeMap или LinkedHashMap).
Сигнатура:
Пример:
4. Многокритериальная группировка (вложенные groupingBy)
Можно группировать по нескольким уровням, создавая сложные структуры данных.
Пример:
5. Производительность и особенности
Порядок элементов в группах зависит от исходного потока (если он ordered, как в List, порядок сохраняется).
Хэш-коллизии обрабатываются через HashMap.
Нюансы при параллельных стримах:
Если используется ConcurrentHashMap, группировка становится потокобезопасной.
В некоторых случаях groupingByConcurrent работает быстрее.
#Java #Training #Medium #Collectors #groupingBy
groupingBy
Collectors.groupingBy – один из самых мощных коллекторов в Java Stream API, позволяющий группировать элементы потока по заданному критерию. Возвращает Map<K, List<T>>, где ключ (K) – это значение классификатора, а значение – список элементов, соответствующих этому ключу.
1. Базовая форма: groupingBy(classifier)
Группирует элементы по ключу, возвращаемому classifier (функцией классификации).
Сигнатура:
public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(
Function<? super T, ? extends K> classifier
)
Пример:
List<String> names = List.of("Alice", "Bob", "Charlie", "Anna", "Alex");
Map<Integer, List<String>> groupedByLength = names.stream()
.collect(Collectors.groupingBy(String::length));
// Результат:
// {3=[Bob], 4=[Anna, Alex], 5=[Alice], 7=[Charlie]}
Как это работает внутри?
Классификатор (String::length) вычисляет ключ для каждого элемента.
Элементы с одинаковым ключом добавляются в List<T>.
По умолчанию используется HashMap, а значения хранятся в ArrayList.
2. groupingBy(classifier, downstream) – с дополнительным коллектором
Позволяет указать, как собирать элементы в группы (не только в List).
Сигнатура:
public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(
Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream
)
Примеры:
2.1. Группировка в Set вместо List
Map<Integer, Set<String>> groupedByLength = names.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.toSet()
));
2.2. Подсчет количества элементов в каждой группе
Map<Integer, Long> countByLength = names.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.counting()
));
// Результат: {3=1, 4=2, 5=1, 7=1}
2.3. Объединение элементов в строку
Map<Integer, String> joinedNames = names.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.joining(", ")
));
// Результат: {3="Bob", 4="Anna, Alex", 5="Alice", 7="Charlie"}
3. groupingBy(classifier, mapFactory, downstream) – с выбором реализации Map
Позволяет указать конкретную реализацию Map (например, TreeMap или LinkedHashMap).
Сигнатура:
public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(
Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream
)
Пример:
// Группировка с сохранением порядка (LinkedHashMap)
LinkedHashMap<Integer, List<String>> orderedGroups = names.stream()
.collect(Collectors.groupingBy(
String::length,
LinkedHashMap::new,
Collectors.toList()
));
// Группировка с сортировкой по ключу (TreeMap)
TreeMap<Integer, Set<String>> sortedGroups = names.stream()
.collect(Collectors.groupingBy(
String::length,
TreeMap::new,
Collectors.toSet()
));
4. Многокритериальная группировка (вложенные groupingBy)
Можно группировать по нескольким уровням, создавая сложные структуры данных.
Пример:
record Person(String name, String city, int age) {}
List<Person> people = List.of(
new Person("Alice", "NY", 25),
new Person("Bob", "LA", 30),
new Person("Charlie", "NY", 25),
new Person("David", "LA", 35)
);
// Группировка по городу, а затем по возрасту
Map<String, Map<Integer, List<Person>>> multiLevelGroup = people.stream()
.collect(Collectors.groupingBy(
Person::city,
Collectors.groupingBy(Person::age)
));
// Результат:
// {
// "NY": {25=[Alice, Charlie]},
// "LA": {30=[Bob], 35=[David]}
// }
5. Производительность и особенности
Порядок элементов в группах зависит от исходного потока (если он ordered, как в List, порядок сохраняется).
Хэш-коллизии обрабатываются через HashMap.
Нюансы при параллельных стримах:
Если используется ConcurrentHashMap, группировка становится потокобезопасной.
В некоторых случаях groupingByConcurrent работает быстрее.
#Java #Training #Medium #Collectors #groupingBy
Collectors в Java
Collectors.summingInt
Collectors.summingInt — это специализированный коллектор, предназначенный для суммирования целочисленных значений, извлекаемых из элементов потока. Он особенно полезен, когда нужно вычислить общую сумму числовых характеристик объектов.
1. Базовая форма и назначение
Сигнатура:
Где:
mapper — функция, преобразующая элемент потока в int (значение, которое нужно суммировать)
Возвращает коллектор, который применяет эту функцию ко всем элементам и суммирует результаты
Пример:
2. Внутренняя реализация
Коллектор работает по следующему алгоритму:
Использует промежуточный аккумулятор типа int[] (для мутабельного накопления суммы)
Для каждого элемента применяет mapper и добавляет результат в аккумулятор
В конце возвращает итоговую сумму
3. Особенности и ограничения
3.1. Только для примитивных int
Для long используйте summingLong
Для double — summingDouble
3.2. Нет обработки null
Если mapper возвращает null, будет NullPointerException
3.3. Параллельные стримы
Корректно работает в параллельных стримах благодаря атомарному накоплению
4. Альтернативы
4.1. Через mapToInt() + sum()
Более прямолинейный способ:
Плюсы:
Чуть лучше производительность
Более явное преобразование типов
Минусы:
Нельзя использовать в комбинации с другими коллекторами (например, при группировке)
4.2. reduce()
5. Комбинирование с другими коллекторами
Особенно полезно в groupingBy для агрегации:
Пример вывода:
6. Производительность
Для примитивов (summingInt) работает быстрее, чем reduce с Integer
В HotSpot JIT-компилятор может оптимизировать до прямого сложения
#Java #Training #Medium #Collectors #summingInt
Collectors.summingInt
Collectors.summingInt — это специализированный коллектор, предназначенный для суммирования целочисленных значений, извлекаемых из элементов потока. Он особенно полезен, когда нужно вычислить общую сумму числовых характеристик объектов.
1. Базовая форма и назначение
Сигнатура:
public static <T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper)
Где:
mapper — функция, преобразующая элемент потока в int (значение, которое нужно суммировать)
Возвращает коллектор, который применяет эту функцию ко всем элементам и суммирует результаты
Пример:
List<Product> products = List.of(
new Product("Laptop", 1000),
new Product("Phone", 500),
new Product("Tablet", 300)
);
int totalPrice = products.stream()
.collect(Collectors.summingInt(Product::getPrice));
// Результат: 1800 (1000 + 500 + 300)
2. Внутренняя реализация
Коллектор работает по следующему алгоритму:
Использует промежуточный аккумулятор типа int[] (для мутабельного накопления суммы)
Для каждого элемента применяет mapper и добавляет результат в аккумулятор
В конце возвращает итоговую сумму
3. Особенности и ограничения
3.1. Только для примитивных int
Для long используйте summingLong
Для double — summingDouble
3.2. Нет обработки null
Если mapper возвращает null, будет NullPointerException
3.3. Параллельные стримы
Корректно работает в параллельных стримах благодаря атомарному накоплению
4. Альтернативы
4.1. Через mapToInt() + sum()
Более прямолинейный способ:
int total = products.stream()
.mapToInt(Product::getPrice)
.sum();
Плюсы:
Чуть лучше производительность
Более явное преобразование типов
Минусы:
Нельзя использовать в комбинации с другими коллекторами (например, при группировке)
4.2. reduce()
int total = products.stream()
.reduce(0, (sum, p) -> sum + p.getPrice(), Integer::sum);
5. Комбинирование с другими коллекторами
Особенно полезно в groupingBy для агрегации:
Map<String, Integer> totalPriceByCategory = products.stream()
.collect(Collectors.groupingBy(
Product::getCategory,
Collectors.summingInt(Product::getPrice)
));
Пример вывода:
{
"Electronics": 1800,
"Office": 750
}
6. Производительность
Для примитивов (summingInt) работает быстрее, чем reduce с Integer
В HotSpot JIT-компилятор может оптимизировать до прямого сложения
#Java #Training #Medium #Collectors #summingInt
Collectors в Java
Collectors.joining
Collectors.joining – это специализированный коллектор для конкатенации строковых представлений элементов потока в единую строку. Это мощный инструмент для работы с текстовыми данными в Java Stream API.
1. Базовая форма: joining()
Конкатенирует элементы без разделителей.
Сигнатура:
Пример:
Особенности:
Работает только с CharSequence (String, StringBuilder и др.)
Если поток пуст, возвращает пустую строку ""
2. С разделителем: joining(delimiter)
Добавляет указанный разделитель между элементами.
Сигнатура:
Пример:
Применение:
CSV-форматирование
Построение SQL-запросов
Логирование коллекций
3. Полная форма: joining(delimiter, prefix, suffix)
Добавляет префикс и суффикс к результату.
Сигнатура:
Пример:
Типичные сценарии:
JSON-массивы: joining(", ", "[", "]")
SQL IN-условия: joining("', '", "'", "'") → 'Java', 'Stream', 'API'
Форматированные списки
4. Внутренняя реализация
Коллектор использует StringJoiner внутри:
Особенности работы:
Для пустого потока возвращает prefix + suffix (например, "[]")
Оптимизирован для String (избегает лишних преобразований)
В параллельных стримах работает корректно (слияние через StringJoiner.merge)
5. Работа с не-строковыми объектами
Если элементы не являются строками, нужно преобразовать их явно:
Альтернатива через Collectors.mapping:
6. Ограничения и нюансы
Null-элементы:
Вызывают NullPointerException
Решение – фильтрация:
Большие объемы данных:
Для гигантских потоков лучше использовать StringBuilder напрямую
Локализация:
Нет встроенной поддержки (для чисел/дат используйте NumberFormat перед joining)
7. Комбинирование с другими коллекторами
Группировка с объединением:
Результат:
Многоуровневое объединение:
8. Альтернативы
StringBuilder (для сложных сценариев):
String.join() (только для List<String>):
TextBlocks (Java 15+ для многострочных данных):
#Java #Training #Medium #Collectors #joining
Collectors.joining
Collectors.joining – это специализированный коллектор для конкатенации строковых представлений элементов потока в единую строку. Это мощный инструмент для работы с текстовыми данными в Java Stream API.
1. Базовая форма: joining()
Конкатенирует элементы без разделителей.
Сигнатура:
public static Collector<CharSequence, ?, String> joining()
Пример:
List<String> words = List.of("Java", "Stream", "API");
String result = words.stream().collect(Collectors.joining());
// Результат: "JavaStreamAPI"
Особенности:
Работает только с CharSequence (String, StringBuilder и др.)
Если поток пуст, возвращает пустую строку ""
2. С разделителем: joining(delimiter)
Добавляет указанный разделитель между элементами.
Сигнатура:
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter)
Пример:
String result = words.stream().collect(Collectors.joining(", "));
// Результат: "Java, Stream, API"
Применение:
CSV-форматирование
Построение SQL-запросов
Логирование коллекций
3. Полная форма: joining(delimiter, prefix, suffix)
Добавляет префикс и суффикс к результату.
Сигнатура:
public static Collector<CharSequence, ?, String> joining(
CharSequence delimiter,
CharSequence prefix,
CharSequence suffix
)
Пример:
String result = words.stream()
.collect(Collectors.joining(", ", "[", "]"));
// Результат: "[Java, Stream, API]"
Типичные сценарии:
JSON-массивы: joining(", ", "[", "]")
SQL IN-условия: joining("', '", "'", "'") → 'Java', 'Stream', 'API'
Форматированные списки
4. Внутренняя реализация
Коллектор использует StringJoiner внутри:
StringJoiner joiner = new StringJoiner(delimiter, prefix, suffix);
stream.forEach(element -> joiner.add(element.toString()));
return joiner.toString();
Особенности работы:
Для пустого потока возвращает prefix + suffix (например, "[]")
Оптимизирован для String (избегает лишних преобразований)
В параллельных стримах работает корректно (слияние через StringJoiner.merge)
5. Работа с не-строковыми объектами
Если элементы не являются строками, нужно преобразовать их явно:
List<Integer> numbers = List.of(1, 2, 3);
String result = numbers.stream()
.map(Object::toString)
.collect(Collectors.joining("-"));
// Результат: "1-2-3"
Альтернатива через Collectors.mapping:
String result = numbers.stream()
.collect(Collectors.mapping(
Object::toString,
Collectors.joining("/")
));
6. Ограничения и нюансы
Null-элементы:
Вызывают NullPointerException
Решение – фильтрация:
.filter(Objects::nonNull)
Большие объемы данных:
Для гигантских потоков лучше использовать StringBuilder напрямую
Локализация:
Нет встроенной поддержки (для чисел/дат используйте NumberFormat перед joining)
7. Комбинирование с другими коллекторами
Группировка с объединением:
Map<String, String> joinedByCategory = products.stream()
.collect(Collectors.groupingBy(
Product::getCategory,
Collectors.mapping(
Product::getName,
Collectors.joining(", ")
)
));
Результат:
{
"Electronics": "Laptop, Phone",
"Office": "Pen, Paper"
}
Многоуровневое объединение:
String complex = persons.stream()
.collect(Collectors.mapping(
p -> p.name() + " (" + p.age() + ")",
Collectors.joining("; ", "Persons: ", ".")
));
// Результат: "Persons: Alice (25); Bob (30)."
8. Альтернативы
StringBuilder (для сложных сценариев):
StringBuilder sb = new StringBuilder();
list.forEach(sb::append);
String.join() (только для List<String>):
String.join(", ", words);
TextBlocks (Java 15+ для многострочных данных):
String sql = """
SELECT %s
FROM table
WHERE id IN (%s)
""".formatted(
columns.stream().collect(joining(", ")),
ids.stream().map(Object::toString).collect(joining(", "))
);
#Java #Training #Medium #Collectors #joining
Collectors в Java
Collectors.partitioningBy
Collectors.partitioningBy – это специальный коллектор, который разделяет элементы потока на две группы с помощью предиката.
Результатом всегда является Map<Boolean, List<T>>, где:
true – элементы, удовлетворяющие условию
false – элементы, не удовлетворяющие условию
1. Базовая форма: partitioningBy(predicate)
Разделяет элементы на две группы без дополнительной обработки.
Сигнатура:
Пример:
Особенности:
Всегда возвращает обе группы (даже если одна пустая)
Сохраняет порядок элементов, если поток упорядочен
2. Расширенная форма: partitioningBy(predicate, downstream)
Позволяет дополнительно обработать каждую группу с помощью другого коллектора.
Сигнатура:
Примеры применения:
2.1. Подсчет количества элементов в каждой группе
2.2. Объединение элементов в строку
2.3. Группировка в Set вместо List
3. Внутренняя реализация
Коллектор работает по следующему алгоритму:
Создает Map<Boolean, List<T>> с двумя ключами (true/false)
Для каждого элемента применяет предикат и добавляет в соответствующую группу
Если указан downstream-коллектор, применяет его к каждой группе
Псевдокод реализации:
4. Практические примеры
4.1. Разделение пользователей по возрасту
4.2. Статистика по разделенным группам
5. Ограничения и нюансы
Неизменяемость результатов:
Возвращаемые коллекции можно модифицировать (обычно ArrayList)
Для неизменяемых групп использовать collectingAndThen:
Параллельные стримы:
Работает корректно в параллельных стримах
Группы объединяются через Map.merge
Null-значения:
Предикат должен обрабатывать null (иначе NPE)
Решение:
#Java #Training #Medium #Collectors #partitioningBy
Collectors.partitioningBy
Collectors.partitioningBy – это специальный коллектор, который разделяет элементы потока на две группы с помощью предиката.
Результатом всегда является Map<Boolean, List<T>>, где:
true – элементы, удовлетворяющие условию
false – элементы, не удовлетворяющие условию
1. Базовая форма: partitioningBy(predicate)
Разделяет элементы на две группы без дополнительной обработки.
Сигнатура:
public static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(
Predicate<? super T> predicate
)
Пример:
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// Результат:
// {
// false=[1, 3, 5],
// true=[2, 4]
// }
Особенности:
Всегда возвращает обе группы (даже если одна пустая)
Сохраняет порядок элементов, если поток упорядочен
2. Расширенная форма: partitioningBy(predicate, downstream)
Позволяет дополнительно обработать каждую группу с помощью другого коллектора.
Сигнатура:
public static <T, D, A> Collector<T, ?, Map<Boolean, D>> partitioningBy(
Predicate<? super T> predicate,
Collector<? super T, A, D> downstream
)
Примеры применения:
2.1. Подсчет количества элементов в каждой группе
Map<Boolean, Long> countByPartition = numbers.stream()
.collect(Collectors.partitioningBy(
n -> n % 2 == 0,
Collectors.counting()
));
// Результат: {false=3, true=2}
2.2. Объединение элементов в строку
Map<Boolean, String> joinedPartitions = numbers.stream()
.collect(Collectors.partitioningBy(
n -> n % 2 == 0,
Collectors.mapping(
Object::toString,
Collectors.joining("-")
)
));
// Результат: {false="1-3-5", true="2-4"}
2.3. Группировка в Set вместо List
Map<Boolean, Set<Integer>> toSet = numbers.stream()
.collect(Collectors.partitioningBy(
n -> n % 2 == 0,
Collectors.toSet()
));
3. Внутренняя реализация
Коллектор работает по следующему алгоритму:
Создает Map<Boolean, List<T>> с двумя ключами (true/false)
Для каждого элемента применяет предикат и добавляет в соответствующую группу
Если указан downstream-коллектор, применяет его к каждой группе
Псевдокод реализации:
Map<Boolean, A> result = new HashMap<>();
result.put(true, new ArrayList<>());
result.put(false, new ArrayList<>());
stream.forEach(item -> {
boolean key = predicate.test(item);
result.get(key).add(item);
});
// Если есть downstream:
result.replaceAll((k, v) -> downstream.finisher().apply(v));
4. Практические примеры
4.1. Разделение пользователей по возрасту
record Person(String name, int age) {}
List<Person> people = List.of(
new Person("Alice", 25),
new Person("Bob", 17),
new Person("Charlie", 30)
);
Map<Boolean, List<Person>> adults = people.stream()
.collect(Collectors.partitioningBy(p -> p.age() >= 18));
// Результат:
// {
// false=[Person[name=Bob, age=17]],
// true=[Person[name=Alice, age=25], Person[name=Charlie, age=30]]
// }
4.2. Статистика по разделенным группам
Map<Boolean, IntSummaryStatistics> stats = numbers.stream()
.collect(Collectors.partitioningBy(
n -> n % 2 == 0,
Collectors.summarizingInt(Integer::intValue)
));
// Получить среднее для четных чисел:
double avgEven = stats.get(true).getAverage();
5. Ограничения и нюансы
Неизменяемость результатов:
Возвращаемые коллекции можно модифицировать (обычно ArrayList)
Для неизменяемых групп использовать collectingAndThen:
.collect(Collectors.collectingAndThen(
Collectors.partitioningBy(predicate),
Collections::unmodifiableMap
));
Параллельные стримы:
Работает корректно в параллельных стримах
Группы объединяются через Map.merge
Null-значения:
Предикат должен обрабатывать null (иначе NPE)
Решение:
.partitioningBy(item -> item != null && predicate.test(item))
#Java #Training #Medium #Collectors #partitioningBy
Collectors в Java
Collectors.collectingAndThen
Collectors.collectingAndThen – это особый коллектор, который добавляет финальное преобразование к результату другого коллектора. Это мощный инструмент для пост-обработки данных после сбора.
1. Базовая концепция
Сигнатура:
Где:
downstream – основной коллектор (например, toList, groupingBy)
finisher – функция, применяемая к результату коллектора
RR – новый тип возвращаемого значения
2. Простые примеры
2.1. Создание неизменяемого списка
2.2. Получение первого элемента
3. Комбинация с другими коллекторами
3.1. Группировка с неизменяемыми значениями
3.2. Подсчет с дополнительной проверкой
4. Внутренняя реализация
Принцип работы:
Сначала выполняется основной коллектор (downstream)
Затем к его результату применяется finisher
Возвращается преобразованное значение
Псевдокод:
5. Практические применения
5.1. Безопасное извлечение Optional
5.2. Нормализация данных после группировки
6. Особенности и ограничения
Порядок выполнения:
Сначала полностью выполняется downstream-коллектор
Затем применяется finisher
Параллельные стримы:
Работает корректно, если finisher – thread-safe
Null-обработка:
Finisher должен сам обрабатывать null-значения
7. Производительность
Обычно добавляет незначительные накладные расходы:
5-10% к времени выполнения базового коллектора
В HotSpot часто инлайнится JIT-компилятором
#Java #Training #Medium #Collectors #partitioningBy
Collectors.collectingAndThen
Collectors.collectingAndThen – это особый коллектор, который добавляет финальное преобразование к результату другого коллектора. Это мощный инструмент для пост-обработки данных после сбора.
1. Базовая концепция
Сигнатура:
public static <T, A, R, RR> Collector<T, A, RR> collectingAndThen(
Collector<T, A, R> downstream,
Function<R, RR> finisher
)
Где:
downstream – основной коллектор (например, toList, groupingBy)
finisher – функция, применяемая к результату коллектора
RR – новый тип возвращаемого значения
2. Простые примеры
2.1. Создание неизменяемого списка
List<String> immutableList = stream
.collect(Collectors.collectingAndThen(
Collectors.toList(),
Collections::unmodifiableList
));
2.2. Получение первого элемента
String firstItem = stream
.collect(Collectors.collectingAndThen(
Collectors.toList(),
list -> list.isEmpty() ? null : list.get(0)
);
3. Комбинация с другими коллекторами
3.1. Группировка с неизменяемыми значениями
Map<String, List<Integer>> unmodifiableGroups = numbers.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
n -> n % 2 == 0 ? "even" : "odd"
),
Collections::unmodifiableMap
));
3.2. Подсчет с дополнительной проверкой
Long countOrZero = stream
.collect(Collectors.collectingAndThen(
Collectors.counting(),
c -> c > 100 ? c : 0L
));
4. Внутренняя реализация
Принцип работы:
Сначала выполняется основной коллектор (downstream)
Затем к его результату применяется finisher
Возвращается преобразованное значение
Псевдокод:
R intermediateResult = downstream.collect(stream);
RR finalResult = finisher.apply(intermediateResult);
return finalResult;
5. Практические применения
5.1. Безопасное извлечение Optional
Optional<String> lastElement = stream
.collect(Collectors.collectingAndThen(
Collectors.toList(),
list -> list.isEmpty()
? Optional.empty()
: Optional.of(list.get(list.size()-1))
);
5.2. Нормализация данных после группировки
Map<String, Set<String>> caseInsensitiveGroups = words.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
String::toLowerCase,
Collectors.toSet()
),
Collections::unmodifiableMap
));
6. Особенности и ограничения
Порядок выполнения:
Сначала полностью выполняется downstream-коллектор
Затем применяется finisher
Параллельные стримы:
Работает корректно, если finisher – thread-safe
Null-обработка:
Finisher должен сам обрабатывать null-значения
7. Производительность
Обычно добавляет незначительные накладные расходы:
5-10% к времени выполнения базового коллектора
В HotSpot часто инлайнится JIT-компилятором
#Java #Training #Medium #Collectors #partitioningBy
Collectors в Java
Collectors.reducing
Collectors.reducing – это универсальный коллектор для выполнения операций свертки (reduction) над элементами потока. Он предоставляет более гибкую альтернативу встроенным операциям типа sum(), max() и min(), позволяя задавать собственную логику агрегации.
1. Три формы reducing
1.1. Базовая форма (с identity, mapper и операцией)
Параметры:
identity – начальное значение (аналог "нейтрального элемента")
mapper – функция преобразования элемента перед обработкой
op – операция для объединения двух значений
Пример (сумма длин строк):
1.2. Упрощенная форма (с identity и операцией)
Пример (конкатенация строк):
1.3. Минималистичная форма (только операция)
Пример (поиск максимального числа):
2. Внутренняя реализация
Принцип работы:
Для каждого элемента применяет mapper (если указан)
Накопление результата через op:
3. Практические примеры
3.1. Сложная агрегация объектов
3.2. Комбинация с groupingBy
3.3. Кастомная агрегация
4. Ограничения и нюансы
Параллельные стримы:
Требует ассоциативности операции ((a op b) op c == a op (b op c))
Пример ассоциативных операций: сложение, умножение, min/max
Неассоциативные: вычитание, деление
Null-значения:
identity не может быть null
Операция должна обрабатывать null, если mapper может его вернуть
Производительность:
Чуть медленнее специализированных коллекторов (summingInt и др.)
На 10-15% медленнее прямого reduce для примитивов
5. Альтернативы
Когда лучше использовать другие методы:
Для примитивов:
Для стандартных операций:
Для неизменяемых коллекций:
#Java #Training #Medium #Collectors #reducing
Collectors.reducing
Collectors.reducing – это универсальный коллектор для выполнения операций свертки (reduction) над элементами потока. Он предоставляет более гибкую альтернативу встроенным операциям типа sum(), max() и min(), позволяя задавать собственную логику агрегации.
1. Три формы reducing
1.1. Базовая форма (с identity, mapper и операцией)
public static <T, U> Collector<T, ?, U> reducing(
U identity,
Function<? super T, ? extends U> mapper,
BinaryOperator<U> op
)
Параметры:
identity – начальное значение (аналог "нейтрального элемента")
mapper – функция преобразования элемента перед обработкой
op – операция для объединения двух значений
Пример (сумма длин строк):
List<String> words = List.of("Java", "Stream", "API");
int totalLength = words.stream()
.collect(Collectors.reducing(
0, // identity
String::length, // mapper
Integer::sum // op
));
// Результат: 11 (4 + 6 + 1)
1.2. Упрощенная форма (с identity и операцией)
public static <T> Collector<T, ?, T> reducing(
T identity,
BinaryOperator<T> op
)
Пример (конкатенация строк):
String concatenated = words.stream()
.collect(Collectors.reducing(
"", // identity
String::concat
));
// Результат: "JavaStreamAPI"
1.3. Минималистичная форма (только операция)
public static <T> Collector<T, ?, Optional<T>> reducing(
BinaryOperator<T> op
)
Пример (поиск максимального числа):
Optional<Integer> max = Stream.of(3, 1, 4)
.collect(Collectors.reducing(
Integer::max
));
// Результат: Optional[4]
2. Внутренняя реализация
Принцип работы:
Для каждого элемента применяет mapper (если указан)
Накопление результата через op:
// Псевдокод реализации
U result = identity;
for (T element : stream) {
result = op.apply(result, mapper.apply(element));
}
return result;
Для формы без identity использует Optional для обработки пустых потоков
3. Практические примеры
3.1. Сложная агрегация объектов
record Product(String name, double price) {}
List<Product> products = List.of(
new Product("Laptop", 999.99),
new Product("Phone", 599.99)
);
// Сумма цен продуктов с названием длиннее 4 символов
double total = products.stream()
.collect(Collectors.reducing(
0.0,
p -> p.name().length() > 4 ? p.price() : 0,
Double::sum
));
3.2. Комбинация с groupingBy
// Максимальная цена в каждой категории
Map<String, Optional<Product>> byCategory = products.stream()
.collect(Collectors.groupingBy(
Product::category,
Collectors.reducing((p1, p2) ->
p1.price() > p2.price() ? p1 : p2
));
3.3. Кастомная агрегация
// Пользовательская свертка для статистики
class Stats {
int count;
double sum;
// + конструкторы/методы
}
Stats stats = stream.collect(Collectors.reducing(
new Stats(0, 0.0),
num -> new Stats(1, num),
(s1, s2) -> new Stats(
s1.count + s2.count,
s1.sum + s2.sum
)
));
4. Ограничения и нюансы
Параллельные стримы:
Требует ассоциативности операции ((a op b) op c == a op (b op c))
Пример ассоциативных операций: сложение, умножение, min/max
Неассоциативные: вычитание, деление
Null-значения:
identity не может быть null
Операция должна обрабатывать null, если mapper может его вернуть
Производительность:
Чуть медленнее специализированных коллекторов (summingInt и др.)
На 10-15% медленнее прямого reduce для примитивов
5. Альтернативы
Когда лучше использовать другие методы:
Для примитивов:
// Вместо:
.collect(reducing(0, Integer::sum))
// Лучше:
.mapToInt().sum()
Для стандартных операций:
// Вместо:
.collect(reducing(Integer::max))
// Лучше:
.max(Comparator.naturalOrder())
Для неизменяемых коллекций:
// Вместо:
.collect(reducing(Collections.emptyList(), customMerge))
// Лучше:
.collect(toUnmodifiableList())
#Java #Training #Medium #Collectors #reducing
Collectors в Java
Кастомные коллекторы и неизменяемые коллекции
1. Создание кастомных коллекторов
Коллектор реализует интерфейс Collector<T, A, R>, где:
T – тип элементов потока
A – тип аккумулятора (промежуточное хранилище)
R – тип результата
Компоненты коллектора:
Supplier<A> – создает контейнер для накопления
BiConsumer<A, T> – добавляет элемент в контейнер
BinaryOperator<A> – объединяет частичные результаты (для параллельных стримов)
Function<A, R> – преобразует аккумулятор в результат
Characteristics (опционально) – набор характеристик (CONCURRENT, UNORDERED, IDENTITY_FINISH)
Пример: Кастомный коллектор для объединения строк с разделителем
2. Неизменяемые коллекции
Java 10+ предоставляет встроенные коллекторы для неизменяемых коллекций:
2.1. Стандартные неизменяемые коллекторы
2.2. Создание через collectingAndThen
3. Комбинирование подходов
3.1. Неизменяемый кастомный коллектор
3.2. Группировка с неизменяемыми значениями
4. Особенности реализации
4.1. Параллельная обработка
Кастомные коллекторы должны иметь thread-safe аккумулятор или характеристику CONCURRENT
Combiner должен корректно объединять частичные результаты
4.2. Оптимизации
Для примитивов используйте специализированные коллекторы (summingInt, averagingDouble)
Избегайте boxing/unboxing в кастомных коллекторах
4.3. Обработка null
Встроенные неизменяемые коллекторы бросают NullPointerException при null-элементах
Решение:
Когда создавать кастомные коллекторы:
✔️ Для сложной логики агрегации
✔️ Когда встроенные коллекторы не подходят
✔️ Для оптимизации производительности в специфичных сценариях
Когда использовать неизменяемые коллекции:
✔️ Для безопасного возврата результатов из методов
✔️ В многопоточных сценариях
✔️ Для защиты данных от модификации
#Java #Training #Medium #Collectors
Кастомные коллекторы и неизменяемые коллекции
1. Создание кастомных коллекторов
Коллектор реализует интерфейс Collector<T, A, R>, где:
T – тип элементов потока
A – тип аккумулятора (промежуточное хранилище)
R – тип результата
Компоненты коллектора:
Supplier<A> – создает контейнер для накопления
BiConsumer<A, T> – добавляет элемент в контейнер
BinaryOperator<A> – объединяет частичные результаты (для параллельных стримов)
Function<A, R> – преобразует аккумулятор в результат
Characteristics (опционально) – набор характеристик (CONCURRENT, UNORDERED, IDENTITY_FINISH)
Пример: Кастомный коллектор для объединения строк с разделителем
Collector<String, StringBuilder, String> joinStrings = Collector.of(
StringBuilder::new, // supplier
(sb, str) -> { // accumulator
if (!sb.isEmpty()) sb.append(", ");
sb.append(str);
},
(sb1, sb2) -> { // combiner (для параллельных стримов)
if (sb1.length() > 0 && sb2.length() > 0) {
sb1.append(", ");
}
return sb1.append(sb2);
},
StringBuilder::toString // finisher
);
String result = Stream.of("Java", "Kotlin", "Scala")
.collect(joinStrings);
// Результат: "Java, Kotlin, Scala"
2. Неизменяемые коллекции
Java 10+ предоставляет встроенные коллекторы для неизменяемых коллекций:
2.1. Стандартные неизменяемые коллекторы
List<String> unmodifiableList = stream.collect(Collectors.toUnmodifiableList());
Set<String> unmodifiableSet = stream.collect(Collectors.toUnmodifiableSet());
Map<String, Integer> unmodifiableMap = stream.collect(
Collectors.toUnmodifiableMap(
keyMapper,
valueMapper,
mergeFunction // для обработки дубликатов
)
);
2.2. Создание через collectingAndThen
List<String> immutable = stream
.collect(Collectors.collectingAndThen(
Collectors.toList(),
Collections::unmodifiableList
));
3. Комбинирование подходов
3.1. Неизменяемый кастомный коллектор
Collector<String, List<String>, List<String>> toImmutableList = Collector.of(
ArrayList::new, // supplier
List::add, // accumulator
(left, right) -> { // combiner
left.addAll(right);
return left;
},
Collections::unmodifiableList // finisher
);
3.2. Группировка с неизменяемыми значениями
Map<String, List<Integer>> immutableGroups = numbers.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
n -> n % 2 == 0 ? "even" : "odd"
),
Collections::unmodifiableMap
));
4. Особенности реализации
4.1. Параллельная обработка
Кастомные коллекторы должны иметь thread-safe аккумулятор или характеристику CONCURRENT
Combiner должен корректно объединять частичные результаты
4.2. Оптимизации
Для примитивов используйте специализированные коллекторы (summingInt, averagingDouble)
Избегайте boxing/unboxing в кастомных коллекторах
4.3. Обработка null
Встроенные неизменяемые коллекторы бросают NullPointerException при null-элементах
Решение:
.filter(Objects::nonNull)
.collect(toUnmodifiableList())
Когда создавать кастомные коллекторы:
✔️ Для сложной логики агрегации
✔️ Когда встроенные коллекторы не подходят
✔️ Для оптимизации производительности в специфичных сценариях
Когда использовать неизменяемые коллекции:
✔️ Для безопасного возврата результатов из методов
✔️ В многопоточных сценариях
✔️ Для защиты данных от модификации
#Java #Training #Medium #Collectors
Jackson
В мире Java-программирования одна из самых частых задач — это работа с JSON. Будь то обмен данными между сервером и клиентом, интеграция с внешними API или хранение конфигураций — JSON стал стандартом де-факто. Именно для этих целей и был создан Jackson.
Что такое Jackson
Jackson — это одна из самых популярных Java-библиотек для работы с JSON. Она позволяет сериализовать объекты Java в JSON и обратно — десериализовать JSON в Java-объекты. Причем делает это быстро, эффективно и с минимальными накладными расходами.
Быстрая история
2007 год — Джейсон Т. Смит (Jason T. Smith) начал разработку Jackson.
2009 год — Первый стабильный релиз (Jackson 1.x).
2012 год — Выпуск Jackson 2.0 с полностью переработанным API.
Сейчас — Jackson активно поддерживается, последняя версия (на 2024 год) — Jackson 2.16+.
Где используется Jackson
Сегодня Jackson используется практически везде, где в Java-приложениях требуется работа с JSON:
В REST API на базе Spring Boot и других фреймворков
В микросервисной архитектуре для обмена данными между сервисами
При интеграции с внешними веб-сервисами
Для сохранения и чтения конфигураций в формате JSON
В тестировании при генерации или проверке JSON-структур
Многие крупные компании и проекты строят на Jackson свою работу с данными. Более того, Spring Framework по умолчанию использует Jackson для сериализации и десериализации JSON, что дополнительно укрепило его позиции.
Почему Jackson так популярен
Есть несколько причин, почему Jackson стал стандартом:
Производительность
Jackson славится своей высокой скоростью работы, что критично при обработке больших объемов данных.
Простота использования
Для базовых операций достаточно создать один экземпляр ObjectMapper и вызвать пару методов. В большинстве случаев не нужно писать дополнительный код.
Гибкость
Jackson поддерживает тонкую настройку через аннотации и внешнюю конфигурацию. Можно легко адаптировать сериализацию под любые требования.
Расширяемость
Jackson предлагает богатую экосистему модулей: поддержка новых типов данных (например, Java 8 Date/Time API), работа с CBOR, XML, YAML и другими форматами.
Интеграция с фреймворками
Такие популярные решения как Spring Boot, Dropwizard и многие другие "из коробки" работают с Jackson.
#Java #Training #Medium #Jackson
В мире Java-программирования одна из самых частых задач — это работа с JSON. Будь то обмен данными между сервером и клиентом, интеграция с внешними API или хранение конфигураций — JSON стал стандартом де-факто. Именно для этих целей и был создан Jackson.
Что такое Jackson
Jackson — это одна из самых популярных Java-библиотек для работы с JSON. Она позволяет сериализовать объекты Java в JSON и обратно — десериализовать JSON в Java-объекты. Причем делает это быстро, эффективно и с минимальными накладными расходами.
Быстрая история
2007 год — Джейсон Т. Смит (Jason T. Smith) начал разработку Jackson.
2009 год — Первый стабильный релиз (Jackson 1.x).
2012 год — Выпуск Jackson 2.0 с полностью переработанным API.
Сейчас — Jackson активно поддерживается, последняя версия (на 2024 год) — Jackson 2.16+.
Где используется Jackson
Сегодня Jackson используется практически везде, где в Java-приложениях требуется работа с JSON:
В REST API на базе Spring Boot и других фреймворков
В микросервисной архитектуре для обмена данными между сервисами
При интеграции с внешними веб-сервисами
Для сохранения и чтения конфигураций в формате JSON
В тестировании при генерации или проверке JSON-структур
Многие крупные компании и проекты строят на Jackson свою работу с данными. Более того, Spring Framework по умолчанию использует Jackson для сериализации и десериализации JSON, что дополнительно укрепило его позиции.
Почему Jackson так популярен
Есть несколько причин, почему Jackson стал стандартом:
Производительность
Jackson славится своей высокой скоростью работы, что критично при обработке больших объемов данных.
Простота использования
Для базовых операций достаточно создать один экземпляр ObjectMapper и вызвать пару методов. В большинстве случаев не нужно писать дополнительный код.
Гибкость
Jackson поддерживает тонкую настройку через аннотации и внешнюю конфигурацию. Можно легко адаптировать сериализацию под любые требования.
Расширяемость
Jackson предлагает богатую экосистему модулей: поддержка новых типов данных (например, Java 8 Date/Time API), работа с CBOR, XML, YAML и другими форматами.
Интеграция с фреймворками
Такие популярные решения как Spring Boot, Dropwizard и многие другие "из коробки" работают с Jackson.
#Java #Training #Medium #Jackson
Jackson. Быстрая сериализация и десериализация объектов
Работа с JSON в Jackson строится вокруг главного класса — ObjectMapper. Это основной инструмент для конвертации Java-объектов в JSON и обратно.
ObjectMapper: главный рабочий инструмент
ObjectMapper — это универсальный класс, предоставляющий удобные методы для:
Сериализации (преобразования объекта в JSON)
Десериализации (преобразования JSON обратно в объект)
Создание экземпляра максимально простое:
Сериализация объекта в JSON
Сериализация — это процесс превращения Java-объекта в строку JSON.
Рассмотрим пример:
Результат работы программы:
Десериализация JSON в объект
Десериализация — это обратный процесс: превращение JSON-строки в объект Java.
Пример:
На что обратить внимание
🔹 Обязательны геттеры/сеттеры или public-поля (иначе Jackson не увидит поля).
🔹 Пустой конструктор нужен для десериализации.
🔹 Имена полей JSON и Java должны совпадать (или использовать аннотации, об этом в след. посте).
🔹 Обработка исключений — методы writeValueAsString() и readValue() могут выбросить JsonProcessingException.
#Java #Training #Medium #Jackson
Работа с JSON в Jackson строится вокруг главного класса — ObjectMapper. Это основной инструмент для конвертации Java-объектов в JSON и обратно.
ObjectMapper: главный рабочий инструмент
ObjectMapper — это универсальный класс, предоставляющий удобные методы для:
Сериализации (преобразования объекта в JSON)
Десериализации (преобразования JSON обратно в объект)
Создание экземпляра максимально простое:
ObjectMapper objectMapper = new ObjectMapper();
Сериализация объекта в JSON
Сериализация — это процесс превращения Java-объекта в строку JSON.
Рассмотрим пример:
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
User user = new User("Alice", 25);
String jsonString = objectMapper.writeValueAsString(user);
System.out.println(jsonString);
}
}
class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
// Геттеры и сеттеры обязательны для Jackson
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Результат работы программы:
{"name":"Alice","age":25}
writeValueAsString превращает объект в строку JSON. Можно также сохранить результат сразу в файл через writeValue(new File("user.json"), user).
Десериализация JSON в объект
Десериализация — это обратный процесс: превращение JSON-строки в объект Java.
Пример:
String jsonInput = "{\"name\":\"Bob\",\"age\":30}";
User user = objectMapper.readValue(jsonInput, User.class);
System.out.println(user.getName()); // Выведет: Bob
System.out.println(user.getAge()); // Выведет: 30
readValue принимает JSON и класс, в который нужно преобразовать данные. Важно, чтобы у целевого класса был публичный конструктор без параметров и стандартные геттеры/сеттеры.
На что обратить внимание
🔹 Обязательны геттеры/сеттеры или public-поля (иначе Jackson не увидит поля).
🔹 Пустой конструктор нужен для десериализации.
🔹 Имена полей JSON и Java должны совпадать (или использовать аннотации, об этом в след. посте).
🔹 Обработка исключений — методы writeValueAsString() и readValue() могут выбросить JsonProcessingException.
#Java #Training #Medium #Jackson
Jackson
Ключевые аннотации для сериализации.
Jackson предоставляет богатый набор аннотаций для управления тем, как Java-объекты сериализуются в JSON. На практике чаще всего используются три основные аннотации: @JsonProperty, @JsonIgnore и @JsonInclude.
@JsonProperty
Аннотация @JsonProperty позволяет задать имя поля в итоговом JSON независимо от имени переменной в Java-классе. Это особенно полезно, если требуется соблюдать определенные соглашения об именовании в JSON (например, использовать snake_case).
Пример:
При сериализации объект будет выглядеть так:
@JsonIgnore
Аннотация @JsonIgnore используется для исключения полей из сериализации и десериализации. Если поле не должно попадать в JSON-вывод или не должно учитываться при чтении JSON, достаточно добавить эту аннотацию.
Пример:
Результат сериализации:
@JsonInclude
Аннотация @JsonInclude позволяет управлять включением полей в JSON в зависимости от их значения. Например, можно настроить сериализацию так, чтобы поля с null не попадали в итоговый JSON.
Пример:
Если email будет null, результат сериализации будет таким:
#Java #Training #Medium #Jackson #JsonProperty #JsonIgnore #JsonInclude
Ключевые аннотации для сериализации.
Jackson предоставляет богатый набор аннотаций для управления тем, как Java-объекты сериализуются в JSON. На практике чаще всего используются три основные аннотации: @JsonProperty, @JsonIgnore и @JsonInclude.
@JsonProperty
Аннотация @JsonProperty позволяет задать имя поля в итоговом JSON независимо от имени переменной в Java-классе. Это особенно полезно, если требуется соблюдать определенные соглашения об именовании в JSON (например, использовать snake_case).
Пример:
import com.fasterxml.jackson.annotation.JsonProperty;
public class User {
@JsonProperty("user_name")
private String name;
private int age;
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
// Геттеры и сеттеры
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
При сериализации объект будет выглядеть так:
{"user_name":"Alice","age":25}
Без @JsonProperty имя поля в JSON было бы "name", но аннотация позволила задать собственное имя.
@JsonIgnore
Аннотация @JsonIgnore используется для исключения полей из сериализации и десериализации. Если поле не должно попадать в JSON-вывод или не должно учитываться при чтении JSON, достаточно добавить эту аннотацию.
Пример:
import com.fasterxml.jackson.annotation.JsonIgnore;
public class User {
private String name;
@JsonIgnore
private String password;
public User() {}
public User(String name, String password) {
this.name = name;
this.password = password;
}
// Геттеры и сеттеры
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Результат сериализации:
{"name":"Alice"}
Поле password полностью исключено из JSON.
@JsonInclude
Аннотация @JsonInclude позволяет управлять включением полей в JSON в зависимости от их значения. Например, можно настроить сериализацию так, чтобы поля с null не попадали в итоговый JSON.
Пример:
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
private String name;
private String email;
public User() {}
public User(String name, String email) {
this.name = name;
this.email = email;
}
// Геттеры и сеттеры
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Если email будет null, результат сериализации будет таким:
{"name":"Alice"}
Без @JsonInclude поле email также попало бы в JSON с null значением.
#Java #Training #Medium #Jackson #JsonProperty #JsonIgnore #JsonInclude
Jackson
Аннотации Jackson для десериализации и изменения структуры JSON
Иногда структура входящего JSON не совпадает напрямую с полями Java-объекта. Для корректной десериализации в таких случаях Jackson предлагает ряд аннотаций, среди которых особенно важны @JsonCreator, @JsonSetter и @JsonAlias.
@JsonCreator
Аннотация @JsonCreator позволяет указать, какой именно конструктор или фабричный метод должен использоваться для создания объекта из JSON.
Особенно полезна в случаях, когда объект должен быть создан через специфический конструктор, а не через пустой и геттеры/сеттеры.
Пример:
При десериализации Jackson использует указанный конструктор, а не требует пустой конструктор и сеттеры:
@JsonSetter
Аннотация @JsonSetter указывается на методе и сообщает Jackson, что данный метод должен использоваться для установки значения при десериализации.
Это полезно, когда метод сеттера отличается по названию от поля или нужно выполнить какую-то дополнительную обработку значения.
Пример:
JSON:
@JsonAlias
Аннотация @JsonAlias позволяет указывать альтернативные имена полей, которые могут приходить в JSON. Это особенно полезно для работы с нестабильными или изменяющимися API.
Пример:
Теперь следующий JSON будет корректно обработан:
или
Пример нестандартного маппинга
Иногда API может менять структуру данных или использовать разные названия для одного и того же поля. Комбинируя @JsonCreator, @JsonProperty и @JsonAlias, можно добиться правильной обработки даже самых нестандартных случаев.
Например:
#Java #Training #Medium #Jackson #JsonCreator #JsonSetter #JsonAlias
Аннотации Jackson для десериализации и изменения структуры JSON
Иногда структура входящего JSON не совпадает напрямую с полями Java-объекта. Для корректной десериализации в таких случаях Jackson предлагает ряд аннотаций, среди которых особенно важны @JsonCreator, @JsonSetter и @JsonAlias.
@JsonCreator
Аннотация @JsonCreator позволяет указать, какой именно конструктор или фабричный метод должен использоваться для создания объекта из JSON.
Особенно полезна в случаях, когда объект должен быть создан через специфический конструктор, а не через пустой и геттеры/сеттеры.
Пример:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class User {
private final String name;
private final int age;
@JsonCreator
public User(@JsonProperty("name") String name,
@JsonProperty("age") int age) {
this.name = name;
this.age = age;
}
// Только геттеры
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
При десериализации Jackson использует указанный конструктор, а не требует пустой конструктор и сеттеры:
String json = "{\"name\":\"Alice\",\"age\":30}";
User user = objectMapper.readValue(json, User.class);
@JsonSetter
Аннотация @JsonSetter указывается на методе и сообщает Jackson, что данный метод должен использоваться для установки значения при десериализации.
Это полезно, когда метод сеттера отличается по названию от поля или нужно выполнить какую-то дополнительную обработку значения.
Пример:
import com.fasterxml.jackson.annotation.JsonSetter;
public class Product {
private String name;
public Product() {}
public String getName() {
return name;
}
@JsonSetter("product_name")
public void setName(String name) {
this.name = name.toUpperCase();
}
}
JSON:
{
"product_name": "laptop"
}
При десериализации будет вызван метод setName, а имя продукта будет автоматически приведено к верхнему регистру: "LAPTOP".
@JsonAlias
Аннотация @JsonAlias позволяет указывать альтернативные имена полей, которые могут приходить в JSON. Это особенно полезно для работы с нестабильными или изменяющимися API.
Пример:
import com.fasterxml.jackson.annotation.JsonAlias;
public class Customer {
@JsonAlias({"user_name", "login"})
private String username;
public Customer() {}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
Теперь следующий JSON будет корректно обработан:
{ "user_name": "admin" }
или
{ "login": "admin" }
И в обоих случаях значение попадет в поле username.
Пример нестандартного маппинга
Иногда API может менять структуру данных или использовать разные названия для одного и того же поля. Комбинируя @JsonCreator, @JsonProperty и @JsonAlias, можно добиться правильной обработки даже самых нестандартных случаев.
Например:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonAlias;
public class Employee {
private final String id;
private final String fullName;
@JsonCreator
public Employee(@JsonProperty("id") String id,
@JsonProperty("full_name") @JsonAlias({"name", "fullname"}) String fullName) {
this.id = id;
this.fullName = fullName;
}
public String getId() {
return id;
}
public String getFullName() {
return fullName;
}
}
Теперь Jackson корректно обработает JSON, где имя сотрудника приходит под разными ключами: full_name, name или fullname.
#Java #Training #Medium #Jackson #JsonCreator #JsonSetter #JsonAlias
Jackson
Работа со списками, картами и вложенными объектами
JSON — это не только простые объекты, но и сложные структуры: списки, словари, вложенные объекты. В этом посте разберем, как Jackson справляется с такими случаями, и как правильно сериализовать и десериализовать коллекции и составные структуры.
Списки объектов
Представим, что у нас есть список пользователей:
Сериализация списка в JSON:
Результат:
Десериализация списка обратно в Java:
Карты (мапы)
JSON-объект с произвольными ключами часто удобно мапить в Map<String, Object>.
Например:
Можно прочитать в Map:
Сериализация карты обратно:
Вложенные объекты
Вложенные структуры — это JSON, где одно поле содержит объект:
Соответствующий Java-класс:
Jackson автоматически корректно сериализует и десериализует такие вложенные объекты:
Результат:
И обратно:
Комбинирование структур
Jackson легко справляется с комбинацией вложенных объектов, списков и мап:
Нужно лишь правильно описать классы:
#Java #Training #Medium #Jackson
Работа со списками, картами и вложенными объектами
JSON — это не только простые объекты, но и сложные структуры: списки, словари, вложенные объекты. В этом посте разберем, как Jackson справляется с такими случаями, и как правильно сериализовать и десериализовать коллекции и составные структуры.
Списки объектов
Представим, что у нас есть список пользователей:
public class User {
private String name;
private int age;
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
// Геттеры и сеттеры
}
Сериализация списка в JSON:
List<User> users = List.of(
new User("Alice", 30),
new User("Bob", 25)
);
String json = objectMapper.writeValueAsString(users);
System.out.println(json);
Результат:
[
{"name":"Alice","age":30},
{"name":"Bob","age":25}
]
Десериализация списка обратно в Java:
String jsonInput = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]";
List<User> users = objectMapper.readValue(
jsonInput,
new TypeReference<List<User>>() {}
);
Здесь важно использовать TypeReference, чтобы сохранить информацию о типе во время десериализации.
Карты (мапы)
JSON-объект с произвольными ключами часто удобно мапить в Map<String, Object>.
Например:
{
"name": "Alice",
"age": 30,
"active": true
}
Можно прочитать в Map:
String json = "{\"name\":\"Alice\",\"age\":30,\"active\":true}";
Map<String, Object> map = objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {});
Сериализация карты обратно:
Map<String, Object> data = new HashMap<>();
data.put("name", "Alice");
data.put("age", 30);
data.put("active", true);
String jsonOutput = objectMapper.writeValueAsString(data);
Вложенные объекты
Вложенные структуры — это JSON, где одно поле содержит объект:
{
"id": 1,
"user": {
"name": "Alice",
"age": 30
}
}
Соответствующий Java-класс:
public class Wrapper {
private int id;
private User user;
public Wrapper() {}
public Wrapper(int id, User user) {
this.id = id;
this.user = user;
}
// Геттеры и сеттеры
}
Jackson автоматически корректно сериализует и десериализует такие вложенные объекты:
Wrapper wrapper = new Wrapper(1, new User("Alice", 30));
String json = objectMapper.writeValueAsString(wrapper);
Результат:
{
"id": 1,
"user": {
"name": "Alice",
"age": 30
}
}
И обратно:
String input = "{\"id\":1,\"user\":{\"name\":\"Alice\",\"age\":30}}";
Wrapper wrapper = objectMapper.readValue(input, Wrapper.class);
Комбинирование структур
Jackson легко справляется с комбинацией вложенных объектов, списков и мап:
{
"group": "admins",
"members": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
],
"meta": {
"created": "2023-10-01",
"active": true
}
}
Нужно лишь правильно описать классы:
public class Group {
private String group;
private List<User> members;
private Map<String, Object> meta;
// Конструктор, геттеры, сеттеры
}
#Java #Training #Medium #Jackson
Jackson
Кастомные десериализаторы
Иногда структура входящего JSON или требования к обработке данных слишком специфичны, и стандартной десериализации Jackson оказывается недостаточно. В таких случаях можно реализовать собственный десериализатор, унаследовавшись от JsonDeserializer.
Когда нужен кастомный десериализатор
— Нужно преобразовать нестандартный формат данных
— Нужно добавить проверку, валидацию, нормализацию
— Нужно маппить одно поле в несколько
— Нужно логировать, фильтровать, трансформировать содержимое JSON
Шаг 1: Создание собственного десериализатора
Для этого создаем класс, расширяющий JsonDeserializer<T>:
Шаг 2: Привязка десериализатора к полю
Теперь мы можем использовать наш кастомный десериализатор на нужном поле через аннотацию @JsonDeserialize.
Шаг 3: Пример использования
Что ещё можно делать в кастомных десериализаторах
— Преобразовывать дату из нестандартного формата
— Разбирать строки с разделителями (например, CSV в List)
— Валидировать числовые диапазоны
— Преобразовывать строки в enum с логикой по умолчанию
— Обрабатывать null как спецзначения
— Инъектировать зависимости (через context.findInjectableValue())
#Java #Training #Medium #Jackson
Кастомные десериализаторы
Иногда структура входящего JSON или требования к обработке данных слишком специфичны, и стандартной десериализации Jackson оказывается недостаточно. В таких случаях можно реализовать собственный десериализатор, унаследовавшись от JsonDeserializer.
Когда нужен кастомный десериализатор
— Нужно преобразовать нестандартный формат данных
— Нужно добавить проверку, валидацию, нормализацию
— Нужно маппить одно поле в несколько
— Нужно логировать, фильтровать, трансформировать содержимое JSON
Шаг 1: Создание собственного десериализатора
Для этого создаем класс, расширяющий JsonDeserializer<T>:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
public class NameDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String rawValue = p.getText();
if (rawValue == null || rawValue.trim().isEmpty()) {
throw new IOException("Имя не может быть пустым");
}
// Пример нормализации: первая буква заглавная
return rawValue.substring(0, 1).toUpperCase() + rawValue.substring(1).toLowerCase();
}
}
Шаг 2: Привязка десериализатора к полю
Теперь мы можем использовать наш кастомный десериализатор на нужном поле через аннотацию @JsonDeserialize.
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
public class User {
@JsonDeserialize(using = NameDeserializer.class)
private String name;
private int age;
public User() {}
// Геттеры и сеттеры
}
Шаг 3: Пример использования
String json = "{\"name\":\" aLiCe \", \"age\":30}";
User user = objectMapper.readValue(json, User.class);
System.out.println(user.getName()); // Выведет: Alice
Если в поле name будет пустая строка или только пробелы — Jackson выбросит исключение, как мы это настроили в десериализаторе.
Что ещё можно делать в кастомных десериализаторах
— Преобразовывать дату из нестандартного формата
— Разбирать строки с разделителями (например, CSV в List)
— Валидировать числовые диапазоны
— Преобразовывать строки в enum с логикой по умолчанию
— Обрабатывать null как спецзначения
— Инъектировать зависимости (через context.findInjectableValue())
#Java #Training #Medium #Jackson
Jackson
Глобальная настройка ObjectMapper: как управлять сериализацией и десериализацией
Jackson предоставляет удобный способ централизованно настраивать поведение через ObjectMapper. Эта конфигурация позволяет управлять форматированием JSON, обработкой неизвестных полей, null-значений и множеством других аспектов.
Инициализация ObjectMapper
1. Игнорирование неизвестных полей
По умолчанию Jackson выбросит исключение, если в JSON попадётся поле, которого нет в Java-классе.
Это можно отключить так:
Теперь лишние поля будут просто игнорироваться:
2. Красивая печать JSON (Pretty Print)
По умолчанию JSON сериализуется в одну строку. Чтобы включить форматирование с отступами:
Пример вывода:
3. Исключение null-полей
Если нужно убрать из JSON все поля с null, можно настроить так:
Пример:
Результат без email, если он null:
4. Ошибка при отсутствии обязательных полей
По умолчанию Jackson пропускает отсутствующие поля. Но можно требовать их наличия:
5. Написание чисел как строки
Результат:
6. Установка глобального формата дат
Когда это полезно
— В проектах с нестабильными API
— При интеграции с фронтендом, где важно форматирование
— При логировании JSON в читаемом виде
— Для обеспечения устойчивости к изменяющимся данным
#Java #Training #Medium #Jackson #ObjectMapper
Глобальная настройка ObjectMapper: как управлять сериализацией и десериализацией
Jackson предоставляет удобный способ централизованно настраивать поведение через ObjectMapper. Эта конфигурация позволяет управлять форматированием JSON, обработкой неизвестных полей, null-значений и множеством других аспектов.
Инициализация ObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
Можно использовать его напрямую или создать бин (например, в Spring).
1. Игнорирование неизвестных полей
По умолчанию Jackson выбросит исключение, если в JSON попадётся поле, которого нет в Java-классе.
Это можно отключить так:
import com.fasterxml.jackson.databind.DeserializationFeature;
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Теперь лишние поля будут просто игнорироваться:
{
"name": "Alice",
"age": 30,
"extra_field": "ignored"
}
2. Красивая печать JSON (Pretty Print)
По умолчанию JSON сериализуется в одну строку. Чтобы включить форматирование с отступами:
import com.fasterxml.jackson.databind.SerializationFeature;
mapper.enable(SerializationFeature.INDENT_OUTPUT);
Пример вывода:
{
"name" : "Alice",
"age" : 30
}
Это особенно полезно для логирования и отладки.
3. Исключение null-полей
Если нужно убрать из JSON все поля с null, можно настроить так:
import com.fasterxml.jackson.annotation.JsonInclude;
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
Пример:
public class User {
public String name;
public String email; // может быть null
}
Результат без email, если он null:
{
"name": "Alice"
}
4. Ошибка при отсутствии обязательных полей
По умолчанию Jackson пропускает отсутствующие поля. Но можно требовать их наличия:
mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true);
mapper.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, true);
5. Написание чисел как строки
mapper.configure(SerializationFeature.WRITE_NUMBERS_AS_STRINGS, true);
Результат:
{
"age": "30"
}
6. Установка глобального формата дат
import java.text.SimpleDateFormat;
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
Когда это полезно
— В проектах с нестабильными API
— При интеграции с фронтендом, где важно форматирование
— При логировании JSON в читаемом виде
— Для обеспечения устойчивости к изменяющимся данным
#Java #Training #Medium #Jackson #ObjectMapper
Jackson
Использование модулей в Jackson: JavaTimeModule и работа с Java 8 датами
Начиная с Java 8, в язык была добавлена новая мощная модель работы со временем: LocalDate, LocalDateTime, Instant и другие типы из пакета java.time. Однако стандартная конфигурация Jackson не умеет корректно сериализовать и десериализовать эти типы "из коробки".
Проблема по умолчанию
Если попытаться сериализовать объект с полем LocalDateTime, Jackson выдаст ошибку или выведет нечитабельное представление (например, через timestamp).
Пример:
Решение: JavaTimeModule
Чтобы Jackson начал понимать java.time.*, нужно подключить модуль JavaTimeModule.
Подключение (если используешь Maven):
Регистрация модуля:
Важно: если хочешь сериализовать даты в человекочитаемом формате, отключи запись timestamp:
Пример работы
Результат:
Аналогично работает и с другими типами:
LocalDate
LocalTime
ZonedDateTime
Instant
Десериализация обратно
Другие полезные модули Jackson
ParameterNamesModule — помогает при десериализации через конструкторы без @JsonProperty
Jdk8Module — поддержка Optional, OptionalInt и других типов из java.util
AfterburnerModule — ускоряет работу Jackson на больших объёмах данных
#Java #Training #Medium #Jackson
Использование модулей в Jackson: JavaTimeModule и работа с Java 8 датами
Начиная с Java 8, в язык была добавлена новая мощная модель работы со временем: LocalDate, LocalDateTime, Instant и другие типы из пакета java.time. Однако стандартная конфигурация Jackson не умеет корректно сериализовать и десериализовать эти типы "из коробки".
Проблема по умолчанию
Если попытаться сериализовать объект с полем LocalDateTime, Jackson выдаст ошибку или выведет нечитабельное представление (например, через timestamp).
Пример:
public class Event {
public String name;
public LocalDateTime dateTime;
public Event() {}
public Event(String name, LocalDateTime dateTime) {
this.name = name;
this.dateTime = dateTime;
}
}
Event event = new Event("Conference", LocalDateTime.now());
String json = objectMapper.writeValueAsString(event);
Без дополнительных модулей Jackson не поймёт, как сериализовать LocalDateTime.
Решение: JavaTimeModule
Чтобы Jackson начал понимать java.time.*, нужно подключить модуль JavaTimeModule.
Подключение (если используешь Maven):
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Регистрация модуля:
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
Важно: если хочешь сериализовать даты в человекочитаемом формате, отключи запись timestamp:
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
Пример работы
Event event = new Event("Conference", LocalDateTime.of(2025, 4, 29, 10, 30));
String json = mapper.writeValueAsString(event);
System.out.println(json);
Результат:
{
"name": "Conference",
"dateTime": "2025-04-29T10:30:00"
}
Аналогично работает и с другими типами:
LocalDate
LocalTime
ZonedDateTime
Instant
Десериализация обратно
String input = "{\"name\":\"Conference\",\"dateTime\":\"2025-04-29T10:30:00\"}";
Event event = mapper.readValue(input, Event.class);
Другие полезные модули Jackson
ParameterNamesModule — помогает при десериализации через конструкторы без @JsonProperty
Jdk8Module — поддержка Optional, OptionalInt и других типов из java.util
AfterburnerModule — ускоряет работу Jackson на больших объёмах данных
#Java #Training #Medium #Jackson