← Назад к вопросам
Что будет с повторяющимся элементом при передаче в TreeMap?
2.3 Middle🔥 191 комментариев
#Коллекции#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что будет с повторяющимся элементом при передаче в TreeMap?
TreeMap — это отсортированная реализация Map, основанная на красно-чёрных деревьях. При попытке добавить дубликат ключа происходит перезапись значения (как и в обычных Maps), но механизм их удаления требует понимания контракта compareTo().
Поведение дубликатов ключей
Так как TreeMap использует красно-чёрное дерево с сортировкой, оно определяет уникальность ключей не через equals(), а через метод compareTo() (или Comparator).
import java.util.*;
public class TreeMapDuplicates {
public static void main(String[] args) {
// Пример 1: Integer как ключи
Map<Integer, String> map = new TreeMap<>();
map.put(1, "Один");
map.put(2, "Два");
map.put(1, "Один (обновлён)"); // Тот же ключ
System.out.println(map.size()); // 2, не 3!
System.out.println(map.get(1)); // "Один (обновлён)"
// Вывод:
// {1=Один (обновлённый), 2=Два}
}
}
Ключевой момент: TreeMap видит два элемента как идентичные, потому что 1.compareTo(1) == 0.
Когда compareTo() возвращает 0
public class ComparableExample {
static class Person implements Comparable<Person> {
int id;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public int compareTo(Person other) {
return Integer.compare(this.id, other.id); // Только по id!
}
}
public static void main(String[] args) {
Map<Person, String> map = new TreeMap<>();
Person p1 = new Person(1, "John");
Person p2 = new Person(2, "Jane");
Person p3 = new Person(1, "Johnny"); // Тот же id
map.put(p1, "First");
map.put(p2, "Second");
map.put(p3, "Third"); // Перезапишет первого!
System.out.println(map.size()); // 2
System.out.println(map.get(p1)); // "Third"
// p1.compareTo(p3) == 0, поэтому они считаются одним ключом
}
}
Отличие от HashMap
public class HashMapVsTreeMap {
static class CustomKey {
int value;
String name;
public CustomKey(int value, String name) {
this.value = value;
this.name = name;
}
@Override
public int hashCode() {
return value; // Только по value
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof CustomKey)) return false;
return this.value == ((CustomKey) obj).value;
}
@Override
public String toString() {
return "[" + value + "," + name + "]";
}
}
public static void main(String[] args) {
Map<CustomKey, String> hashMap = new HashMap<>();
Map<CustomKey, String> treeMap = new TreeMap<>((k1, k2) ->
Integer.compare(k1.value, k2.value));
CustomKey k1 = new CustomKey(1, "First");
CustomKey k2 = new CustomKey(1, "Second");
hashMap.put(k1, "V1");
hashMap.put(k2, "V2");
System.out.println("HashMap size: " + hashMap.size()); // 1 (equals вернул true)
treeMap.put(k1, "V1");
treeMap.put(k2, "V2");
System.out.println("TreeMap size: " + treeMap.size()); // 1 (compareTo вернул 0)
}
}
Важная ошибка: несогласованность compareTo() и equals()
Это одна из самых подводных ошибок в Java!
public class InconsistentComparator {
static class Student implements Comparable<Student> {
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
// compareTo сравнивает только по id
@Override
public int compareTo(Student other) {
return Integer.compare(this.id, other.id);
}
// Но equals сравнивает по id И name
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Student)) return false;
Student other = (Student) obj;
return this.id == other.id && this.name.equals(other.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
public static void main(String[] args) {
TreeMap<Student, String> map = new TreeMap<>();
Student s1 = new Student(1, "John");
Student s2 = new Student(1, "Johnny"); // Тот же id, но другое имя
map.put(s1, "Value1");
map.put(s2, "Value2"); // compareTo вернёт 0, перезапишет!
System.out.println(map.size()); // 1
// Но если проверить equals:
System.out.println(s1.equals(s2)); // false!
// ВНИМАНИЕ: s1 не equals s2, но TreeMap их считает одинаковыми!
// Это нарушение контракта Comparable и может вызвать ошибки
}
}
Правильная реализация
public class CorrectTreeMapUsage {
static class Product implements Comparable<Product> {
int id;
String name;
public Product(int id, String name) {
this.id = id;
this.name = name;
}
// compareTo и equals проверяют одни и те же поля!
@Override
public int compareTo(Product other) {
int idCompare = Integer.compare(this.id, other.id);
if (idCompare != 0) return idCompare;
return this.name.compareTo(other.name);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Product)) return false;
Product other = (Product) obj;
return this.id == other.id && this.name.equals(other.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
public static void main(String[] args) {
TreeMap<Product, String> map = new TreeMap<>();
Product p1 = new Product(1, "John");
Product p2 = new Product(1, "Johnny");
map.put(p1, "Value1");
map.put(p2, "Value2");
System.out.println(map.size()); // 2 — разные элементы!
// p1.compareTo(p2) != 0, так что они разные
}
}
Резюме
- TreeMap использует compareTo(), не equals() — для определения уникальности ключей
- compareTo() == 0 означает дубликат — значение будет перезаписано
- Согласованность критична —
(a.compareTo(b) == 0)должно быть эквивалентноa.equals(b) - Нарушение контракта вызывает баги — TreeMap и Collections.binarySearch() могут работать неправильно
- Если compareTo() возвращает != 0 — то элементы считаются разными и оба сохраняются