Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое distinct()?
distinct() — это операция в Java Streams API, которая удаляет дубликаты из потока данных, оставляя только уникальные элементы. Это промежуточная (intermediate) операция, которая работает с потоками (Stream).
Базовое использование
import java.util.Arrays;
import java.util.List;
public class DistinctExample {
public static void main(String[] args) {
// Список с дубликатами
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 4, 4);
// distinct() удаляет дубликаты
List<Integer> unique = numbers.stream()
.distinct() // Оставить только уникальные
.toList(); // Собрать в список
System.out.println(unique); // [1, 2, 3, 4]
}
}
Как это работает
Внутренне distinct() использует HashSet для отслеживания уже увиденных элементов:
// Внутренняя реализация distinct() (упрощённо)
public Stream<T> distinct() {
Set<T> seen = new HashSet<>();
return filter(element -> {
// Проверяем, видели ли мы этот элемент
return seen.add(element);
// set.add() возвращает true только для новых элементов
// и false для дубликатов
});
}
// Пример работы
Set<Integer> seen = new HashSet<>();
1 → seen.add(1) → true ✅ Пропускаем
2 → seen.add(2) → true ✅ Пропускаем
2 → seen.add(2) → false ❌ Блокируем (дубликат)
3 → seen.add(3) → true ✅ Пропускаем
Примеры с разными типами данных
1. Строки
List<String> words = Arrays.asList("hello", "world", "hello", "java", "world");
List<String> uniqueWords = words.stream()
.distinct()
.toList();
System.out.println(uniqueWords); // [hello, world, java]
2. Объекты (с equals и hashCode)
public class User {
private long id;
private String name;
public User(long id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return id == user.id && name.equals(user.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
@Override
public String toString() {
return "User(" + id + ", " + name + ")";
}
}
// Использование
List<User> users = Arrays.asList(
new User(1, "Alice"),
new User(2, "Bob"),
new User(1, "Alice"), // дубликат
new User(3, "Charlie")
);
List<User> uniqueUsers = users.stream()
.distinct()
.toList();
System.out.println(uniqueUsers);
// [User(1, Alice), User(2, Bob), User(3, Charlie)]
Важно: distinct() использует equals() и hashCode() для определения дубликатов!
3. По определённому полю
// ❌ НЕПРАВИЛЬНО: distinct() сравнивает весь объект
List<User> users = Arrays.asList(
new User(1, "Alice"),
new User(2, "Alice"), // Другой id, но одно имя
new User(3, "Bob")
);
List<User> result = users.stream()
.distinct() // Всё считается уникальным
.toList();
// Результат: все 3 пользователя (разные id)
// ✅ ПРАВИЛЬНО: используем дополнительную логику
Set<String> seenNames = new HashSet<>();
List<User> uniqueByName = users.stream()
.filter(user -> seenNames.add(user.getName()))
.toList();
// Результат: User(1, Alice) и User(3, Bob)
distinct() в цепочке операций
List<Integer> numbers = Arrays.asList(
5, 2, 8, 2, 9, 1, 5, 3, 8, 4
);
List<Integer> result = numbers.stream()
.filter(n -> n > 2) // Фильтруем > 2: [5, 8, 9, 5, 3, 8, 4]
.distinct() // Удаляем дубликаты: [5, 8, 9, 3, 4]
.sorted() // Сортируем: [3, 4, 5, 8, 9]
.map(n -> n * 2) // Умножаем на 2: [6, 8, 10, 16, 18]
.toList();
System.out.println(result); // [6, 8, 10, 16, 18]
Важные свойства distinct()
1. Порядок сохраняется
List<Integer> numbers = Arrays.asList(3, 1, 3, 2, 1);
List<Integer> result = numbers.stream()
.distinct()
.toList();
System.out.println(result); // [3, 1, 2]
// Первое появление 3 и 1 остаётся в оригинальном порядке
2. Это stateful операция
// distinct() должна помнить все увиденные элементы
// Это требует O(n) памяти!
List<Integer> bigList = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
bigList.add(i % 1000); // Только 1000 уникальных значений
}
List<Integer> unique = bigList.stream()
.distinct() // Сохраняет HashSet из 1_000_000 элементов
.toList();
3. Требует правильной реализации equals/hashCode
public class BuggyUser {
private long id;
private String name;
// ❌ ОШИБКА: не переопределили hashCode
@Override
public boolean equals(Object o) {
if (!(o instanceof BuggyUser)) return false;
BuggyUser other = (BuggyUser) o;
return id == other.id;
}
// hashCode() НЕ переопределён → использует стандартный (по адресу)
}
// Использование
List<BuggyUser> users = Arrays.asList(
new BuggyUser(1, "Alice"),
new BuggyUser(1, "Alice") // Одно id, но разные hashCode
);
List<BuggyUser> unique = users.stream()
.distinct() // НЕ удалит дубликат!
.toList();
System.out.println(unique.size()); // 2 (ошибка!)
Правило: Если переопределяешь equals(), обязательно переопредели hashCode()!
Сравнение с другими подходами
Способ 1: Set (самый простой)
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3);
// ✅ Простой способ
Set<Integer> unique = new HashSet<>(numbers);
System.out.println(unique); // [1, 2, 3]
// ❌ Минус: порядок не гарантирован
// ✅ Плюс: очень быстро, минимум памяти
Способ 2: LinkedHashSet (сохраняет порядок)
// ✅ Сохраняет порядок
Set<Integer> unique = new LinkedHashSet<>(numbers);
List<Integer> result = new ArrayList<>(unique);
// ✅ Плюс: порядок сохранён
// ✅ Плюс: быстро
// ❌ Минус: неочевидно для других разработчиков
Способ 3: distinct() в stream
// ✅ Более выразительно
List<Integer> result = numbers.stream()
.distinct()
.toList();
// ✅ Плюс: ясное намерение
// ✅ Плюс: можно комбинировать с другими операциями
// ❌ Минус: чуть медленнее, больше памяти
Производительность
public class DistinctBenchmark {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
numbers.add(i % 100_000); // 100,000 уникальных
}
// Способ 1: Set
long start = System.nanoTime();
Set<Integer> set = new HashSet<>(numbers);
long setTime = System.nanoTime() - start;
// Способ 2: distinct()
start = System.nanoTime();
List<Integer> distinct = numbers.stream()
.distinct()
.toList();
long distinctTime = System.nanoTime() - start;
System.out.println("Set approach: " + (setTime / 1_000_000) + " ms");
System.out.println("distinct() approach: " + (distinctTime / 1_000_000) + " ms");
// Результат:
// Set approach: ~10 ms
// distinct() approach: ~20 ms
// Set примерно в 2x раз быстрее
}
}
Когда использовать distinct()
// ✅ ИСПОЛЬЗУЙ distinct():
// 1. Когда нужна цепочка operations
List<String> uniqueEmails = users.stream()
.map(User::getEmail) // Извлекаем email
.distinct() // Удаляем дубликаты
.filter(e -> e.contains("@")) // Фильтруем
.toList();
// 2. Когда важен порядок элементов
List<Integer> ordered = numbers.stream()
.distinct() // Сохраняет порядок первого появления
.toList();
// 3. Когда пишешь функциональный стиль
processData(numbers.stream().distinct());
// ❌ НЕ ИСПОЛЬЗУЙ distinct():
// 1. Когда нужна только уникальность
Set<Integer> unique = new HashSet<>(numbers); // Быстрее
// 2. Когда нужна специальная логика удаления дубликатов
// (например, по определённому полю)
Set<String> seen = new HashSet<>();
List<User> unique = users.stream()
.filter(u -> seen.add(u.getName()))
.toList();
Итоговый ответ
distinct() — это операция Java Streams API, которая:
- Удаляет дубликаты из потока, оставляя только уникальные элементы
- Сохраняет порядок первого появления элементов
- Использует HashSet внутри для отслеживания увиденных элементов
- Требует правильной реализации equals() и hashCode() для объектов
- Имеет O(n) сложность по памяти (хранит все элементы в памяти)
- Идеальна для цепочек операций со Stream API
Для простого удаления дубликатов лучше использовать Set, для комплексной обработки данных — distinct() в потоке.