JavaRush /Курсы /Модуль 1. Java Syntax /Знакомство с коллекцией TreeMap

Знакомство с коллекцией TreeMap

Модуль 1. Java Syntax
18 уровень , 1 лекция
Открыта

Однажды в университете мне нужно было написать задачу для сортировки в порядке возрастания фамилий, которые были ключами к личным делам моих одногруппников, и я потратил на это немало времени. Но если бы тогда я был знаком с классом TreeMap, это у меня получилось бы гораздо быстрее.

Что такое TreeMap? Это структура данных, похожая на словарь, которая позволяет сохранять элементы в виде ключ-значение, используя сортировку по ключам.

Где и как его можно использовать? Например, в том же случае с одногруппниками. Если мне нужно хранить значения в порядке возрастания, вместо того, чтобы писать свои алгоритмы сортировки, достаточно создать TreeMap и положить значения туда.

Для таких типов как Integer и String используется порядок сортировки по возрастанию. Но если ты хочешь положить в TreeMap собственный тип, нужно, чтобы твой класс имплементировал интерфейс Comparable, в классе был реализован метод compareTo() и указано, как нужно сортировать значения данного класса.


public class Person implements Comparable<Person> {
 
    private String firstName;
    private String lastName;
 
    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
  ….
 
    @Override
    public int compareTo(Person person) {
        return person.getFirstName().compareTo(firstName);
    }
 
    @Override
    public String toString() {
        return "Person{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                '}';
    }
}

Переопределим метод compareTo(), чтобы он сортировал значение в порядке алфавитного убывания имени:


TreeMap map = new TreeMap<Person, String>();
 
map.put(new Person("AA","BB"), "aa");
map.put(new Person("BB","BB"), "aa");
map.put(new Person("DD","BB"), "aa");
map.put(new Person("CC","BB"), "aa");

Значения будут храниться в следующем порядке:


Person{firstName='DD', lastName='BB'}
Person{firstName='CC', lastName='BB'}
Person{firstName='BB', lastName='BB'}
Person{firstName='AA', lastName='BB'}

Класс TreeMap реализует интерфейс NavigableMap, который в свою очередь расширяет SortedMap. Это позволяет классу TreeMap использовать для хранения деревья и хранить значения в отсортированном порядке.

Дерево, как говорит Википедия, — это самобалансирующая двоичная структура поиска, для хранения данных, которая использует узлы для хранения, сравнивая перед этим значения.

Если говорить простыми словами, красно-чёрное дерево — это структура данных, хранящая справа значения, которые больше корневого, а слева — те, что меньше. Такая реализация помогает быстрее искать значения в структуре.

Красно-черное дерево самобалансирующееся, поэтому при каждой новой вставке оно будет менять свою структуру, так как добавляются новые значения. Корневым считается значение, добавленное первым, но в процессе балансировки, корневым может стать и другое значение.

Итак, теперь ты знаешь, что такое TreeMap и по какому принципу оно работает.

Важно помнить то, что хранить в TreeMap можно только тех, кто имплементировал Comparable интерфейс и переопределил метод compareTo().

Но что делать, если мы используем сторонние классы, которые подгружаем из других библиотек, и не можем имплементировать в них Comparable? Для таких случаев есть решение: написать свой Comparator.

Comparator — это интерфейс, в котором есть метод compare(). С его помощью мы можем сравнивать объекты и хранить их в TreeMap.


Comparator<Person> comparator = new Comparator<Person>() {
 
    @Override
    public int compare(Person person1, Person person2) {
        return person1.getFirstName().compareTo(person2.getFirstName());
    }
};
 
 
TreeMap map = new TreeMap<Person, String>(comparator);

В этом примере мы создали кастомный Comparator и передали TreeMap классу.

Начиная с Java 8 это можно написать, используя лямбда-выражение:


TreeMap map = new TreeMap<Person, String>((Person person1, Person person2) -> person1.getFirstName().compareTo(person2.getFirstName()) );

То есть, чтобы хранить значения в TreeMap, нужно указать, как их сортировать. Сделать это можно двумя способами: имплементировать Comparable или реализовать свой Comparator.

Но что если нам нужно положить в TreeMap значение null как ключ? HashMap позволяет это сделать. А как же работает с этим TreeMap?


TreeMap map = new TreeMap<Person, String>();
map.put (null, "Person");

Запустив этот код, получим ошибку:

Exception in thread "main" java.lang.NullPointerException at java.base/java.util.TreeMap.put(TreeMap.java:561)

Проблема заключается в том, что внутри класса TreeMap происходит сравнение значений методом compareTo(). Ты, конечно же, можешь положить туда null значение, и оно скомпилируется, но в рантайме ты получишь ошибку, так как на значении null вызовется метод и вылетит NullPointerException.

Сравнение HashMap и TreeMap

В отличие от TreeMap, в HashMap разрешено хранить null как ключ. Для всех null ключей выделено определенное место в структуре. Хранить null ключи в HashMap позволяет то, что определение места здесь происходит по значению хэша, и для этого не нужно сравнивать значения с другими. Поэтому у всех null ключей есть свое место.

Итак, теперь ты знаешь, что использовать, когда тебе нужно хранить значения в отсортированном порядке, и как задавать правила и порядок сортировки.

Комментарии (48)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Денис Уровень 31
17 ноября 2024
"Итак, теперь ты знаешь, что такое TreeMap". Нет, я не знаю, что такое TreeMap. В лекции информация представлена в сжатом и поверхностном виде.
Сергей Уровень 26
18 июня 2024
В задаче "Зарплаты и позиции" ориентируемся на НИЖНЮЮ планку в ЗП при выводе.
Олег Уровень 106 Expert
28 мая 2024
Я ничего не понял :D
Денис Уровень 31
17 ноября 2024
но ты уже эксперт уровня 100. Скорее всего понял).
Олег Уровень 106 Expert
24 ноября 2024
действительно, это оказалось очень простым и понятным, по сравнению с тем, что ждёт дальше)
Anton Rogulenko Уровень 41 Expert
18 апреля 2024
Нет слов, одни возмущения, что за абстрактная информация, которая не дает нормального понимания. Читаем "Класс TreeMap реализует интерфейс NavigableMap, который в свою очередь расширяет SortedMap. Это позволяет классу TreeMap использовать для хранения деревья и хранить значения в отсортированном порядке. Дерево, как говорит Википедия, — это самобалансирующая двоичная структура поиска, для хранения данных, которая использует узлы для хранения, сравнивая перед этим значения. Если говорить простыми словами, красно-чёрное дерево — это структура данных, хранящая справа значения, которые больше корневого, а слева — те, что меньше. Такая реализация помогает быстрее искать значения в структуре. Красно-черное дерево самобалансирующееся, поэтому при каждой новой вставке оно будет менять свою структуру, так как добавляются новые значения. Корневым считается значение, добавленное первым, но в процессе балансировки, корневым может стать и другое значение." Как сходу понять учебный материал? По видимому материалы лекции не проверяют и соответствующие оценка этому разделу, тоже не является индикатором чтобы исправить, переработать лекцию.
Павло Уровень 7
31 января 2024
hint: try to use map.floorEntry() and pass salary as a parameter
dnekh Уровень 46 Expert
4 июля 2024
Крутая штука! Практически одна строка кода, помимо return
hidden #3378254 Уровень 25
29 декабря 2023

public int compareTo(Person person) {
        return person.getFirstName().compareTo(firstName);
если бы написали так

public int compareTo(Person person) {
        return this.getFirstName().compareTo(person.firstName);
было бы как-то понятнее что ли
Александр Уровень 34 Expert
31 октября 2023
Не понял как подступиться, посмотрел доп.материал тоже не понял, для таких понимающих как и я, посмотрите метод startsWith у стринга, а еще название переменной полученной в параметрах "startsWith", не знаю подсказка это или нет, но если взять из решения (key.toLowerCase().startsWith(startsWith.toLowerCase())) и заменить на (key.startsWith(startsWith). Результат не поменяется, но станет понятней в несколько раз. Если кто знает, напишите пожалуйста как работает это строка в правильном решении. Потому что в строчке (key.startsWith(startsWith), мы у ключа вызываем проверку(методом startsWith) на грубо говоря первые входящие символы(переменной startsWith) и все становится понятно. Для чего нужен toLowerCase, не понятно. Спасибо.
Артем Уровень 29 Expert
30 июня 2023
Пришлось искать инфу кнч и догонять , что вообще от меня хотят)
14 июня 2023
Предыдущая лекция (1 раздел): "Ключом пары может быть что угодно, но ключ не может быть null." Эта лекция: "В HashMap разрешено хранить null как ключ."
Павел Уровень 7 Expert
16 августа 2023
Проверил на личном опыте. "В HashMap разрешено хранить null как ключ."
Алексей Уровень 53 Expert
30 мая 2023
Не очень понятно, зачем создавать отдельный NavigableSet для ключей....