← Назад к вопросам

Что такое distinct()?

1.0 Junior🔥 181 комментариев
#Базы данных и SQL

Комментарии (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, которая:

  1. Удаляет дубликаты из потока, оставляя только уникальные элементы
  2. Сохраняет порядок первого появления элементов
  3. Использует HashSet внутри для отслеживания увиденных элементов
  4. Требует правильной реализации equals() и hashCode() для объектов
  5. Имеет O(n) сложность по памяти (хранит все элементы в памяти)
  6. Идеальна для цепочек операций со Stream API

Для простого удаления дубликатов лучше использовать Set, для комплексной обработки данных — distinct() в потоке.