Java 7, TreeSet и NullPointerException.
Недавно я попытался скомпилировать с помощью java 7 проект, разработанный с использованием java 6. Во время выполнения тестов произошло много веселья, тесты, которые в java 6 работали без сбоев, с java 7 они странным образом терпели неудачу! Итак, я должен был понять, почему, и это то, что я обнаружил … Сначала контекст: в этом проекте у меня есть простой Hibernate Entity, более или менее похожий на следующий.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package com.marco.test;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.Table;import javax.persistence.UniqueConstraint;import org.hibernate.validator.NotNull;@Entity@Table(...)public class ABean { ... private String name; @Column(name = "name", nullable = false) @NotNull public String getName() { return name; } public void setName(String name) { this.name = name; }} |
обратите внимание, что поле «имя» имеет значение nullable = false и помечено @NotNull . Это указывает Hibernate на сбой проверки в случае, если пользователь пытается создать или обновить этот столбец до Null. У меня также есть компаратор для этой организации. Этот компаратор использует поле имени для сравнения сущности (это просто упрощенная версия того, что у меня есть в проекте, конечно, я не заказываю бин на основе длины строки)
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
package com.marco.test;import java.util.Comparator;public class ABeanComparator implements Comparator<ABean> { @Override public int compare(ABean o1, ABean o2) { if (o1.getName().length() > o2.getName().length()) { return 1; } else if (o1.getName().length() < o2.getName().length()) { return -1; } else { return 0; } }} |
обратите внимание, что в названии поля нет нулевой проверки, в моем проекте Hibernate уже позаботился об этом. Теперь у меня есть тест, который создает одну пустую сущность и сохраняет ее в TreeSet, а затем делает другие вещи, которые нам здесь безразличны. Начало теста похоже на код ниже:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
package com.marco.test;import java.util.SortedSet;import java.util.TreeSet;public class SortedTestTest { public static void main(String[] args) { ABean aBean = new ABean(); SortedSet<ABean> sortedSet = new TreeSet<ABean>(new ABeanComparator()); sortedSet.add(aBean); }} |
Если я запускаю это с Java 6, все в порядке. Но с Java 7 у меня есть исключение NullPointerException.
|
1
2
3
4
5
6
7
|
Exception in thread "main" java.lang.NullPointerException at com.marco.test.ABeanComparator.compare(ABeanComparator.java:9) at com.marco.test.ABeanComparator.compare(ABeanComparator.java:1) at java.util.TreeMap.compare(TreeMap.java:1188) at java.util.TreeMap.put(TreeMap.java:531) at java.util.TreeSet.add(TreeSet.java:255) at com.marco.test.SortedTestTest.main(SortedTestTest.java:14) |
Почему? Вот почему:
|
01
02
03
04
05
06
07
08
09
10
|
public V put(K key, V value) { Entry<K,V> t = root; if (t == null) { compare(key, key); // type (and possibly null) check root = new Entry<>(key, value, null); size = 1; modCount++; return null; } |
В Java 7, когда первый объект добавляется (if (t == null)) в TreeSet , выполняется сравнение с самим собой (сравнение (ключ, ключ)). Затем метод сравнения вызовет компаратор (если он есть), и у нас будет свойство NullPointerException для свойства name.
|
1
2
3
4
5
6
7
8
9
|
// Little utilities /** * Compares two keys using the correct comparison method for this TreeMap. */ final int compare(Object k1, Object k2) { return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2) : comparator.compare((K)k1, (K)k2); } |
Это вызывает больше вопросов, чем ответов:
- Зачем проводить сравнение, если вы знаете, что Объект в TreeSet является первым и единственным?
- Я предполагаю, что они хотели выполнить простую проверку Null.
- Почему бы не создать правильный метод проверки нуля?
- Нет ответа
- Зачем тратить процессор и память на сравнение, которое не нужно?
- Нет ответа
- Зачем сравнивать объект с самим собой (сравнивать (ключ, ключ)) ??
- Нет ответа
Это метод размещения TreeSet в Java 6, и, как вы можете видеть, сравнение было закомментировано.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
public V put(K key, V value) { Entry<K, V> t = root; if (t == null) { // TBD: // 5045147: (coll) Adding null to an empty TreeSet should // throw NullPointerException // // compare(key, key); // type check root = new Entry<K, V>(key, value, null); size = 1; modCount++; return null; } |
Вы видите комментарий? Добавление нуля в пустой TreeSet должно вызвать исключение NullPointerException. Так что просто проверьте, является ли ключ нулевым, не запускайте бесполезное сравнение! Вывод? Всегда старайтесь анализировать код, который вы используете, потому что даже в JDK есть плохой код!