Действительно ли это - плохая идея, если равняется броскам (пустого указателя) NullPointerException вместо этого?

Контракт equals относительно null, следующие:

Для любого значения ненулевой ссылки x, x.equals(null) если return false.

Это является довольно странным, потому что если o1 != null и o2 == null, затем мы имеем:

o1.equals(o2) // returns false
o2.equals(o1) // throws NullPointerException

Факт это o2.equals(o1) throws NullPointerException хорошая вещь, потому что она предупреждает нас ошибки программиста. И все же, та ошибка не была бы зафиксирована, если бы по различным причинам мы просто передвинули ее к o1.equals(o2), который просто "тихо перестал бы работать" вместо этого.

Таким образом, вопросы:

  • Почему это - хорошая идея это o1.equals(o2) если return false вместо броска NullPointerException?
  • Это была бы плохая идея если везде, где возможный мы переписываем контракт так, чтобы anyObject.equals(null) всегда бросок NullPointerException вместо этого?

На сравнении с Comparable

Напротив, это что Comparable в контракте говорится:

Отметьте это null не экземпляр никакого класса, и e.compareTo(null) должен бросить a NullPointerException даже при том, что e.equals(null) возвраты false.

Если NullPointerException подходит для compareTo, почему это не для equals?

Связанные вопросы


Чисто семантический аргумент

Это фактические слова в Object.equals(Object obj) документация:

Указывает, "равен ли некоторый другой объект" этому.

И что такое объект?

JLS 4.3.1 объекта

Объект является экземпляром класса или массивом.

Ссылочные значения (часто просто ссылки) являются указателями на эти объекты и специальным предложением null ссылка, которая не относится ни к какому объекту.

Мой аргумент от этого угла действительно прост.

  • equals тесты, "равен ли некоторый другой объект" this
  • null ссылка не дает никакой другой объект для теста
  • Поэтому equals(null) должен бросить NullPointerException

75
задан Community 23 May 2017 в 12:10
поделиться

10 ответов

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

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

Я не могу не согласиться. Я бы не стал переписывать equals, чтобы постоянно генерировать исключение. Я бы заменил любой класс, который делал бы это, если бы я был его клиентом.

19
ответ дан 24 November 2019 в 11:37
поделиться

Лично я бы предпочел, чтобы он работал так, как есть.

NullPointerException указывает, что проблема заключается в объекте, в отношении которого выполняется операция равенства.

Если NullPointerException использовалось, как вы предлагаете, и вы попробовали (вроде бессмысленную) операцию ...

o1.equals (o1) , где o1 = null ... Возникает ли NullPointerException из-за того, что ваша функция сравнения не работает, или из-за того, что o1 имеет значение null, но вы не осознавали? Я знаю, что это крайний пример, но я чувствую, что с учетом текущего поведения вы легко поймете, в чем проблема.

2
ответ дан 24 November 2019 в 11:37
поделиться

Подумайте, как .equals связано с ==, а .compareTo связано с операторами сравнения>, <,> =, <=.

Если вы собираетесь утверждать, что использование .equals для сравнения объекта с null должно вызывать NPE, тогда вы должны сказать, что этот код также должен выдавать один:

Object o1 = new Object();
Object o2 = null;
boolean b = (o1 == o2); // should throw NPE here!

Разница между o1.equals (o2) и o2.equals (o1) заключается в том, что в первом случае вы сравниваете что-то с null, аналогично o1 == o2, тогда как во втором случае метод equals никогда не выполняется так что никакого сравнения не происходит.

Что касается контракта .compareTo, сравнение ненулевого объекта с нулевым объектом похоже на попытку сделать это:

int j = 0;
if(j > null) { 
   ... 
}

Очевидно, это не будет компилироваться. Вы можете использовать автоматическую распаковку для компиляции, но при сравнении вы получите NPE, что согласуется с контрактом .compareTo:

Integer i = null;
int j = 0;
if(j > i) { // NPE
   ... 
}
8
ответ дан 24 November 2019 в 11:37
поделиться

Не то чтобы это обязательно ответ на ваш вопрос, это просто пример того, когда я считаю полезным, что поведение такое, как сейчас.

private static final String CONSTANT_STRING = "Some value";
String text = getText();  // Whatever getText() might be, possibly returning null.

В нынешнем виде я могу.

if (CONSTANT_STRING.equals(text)) {
    // do something.
}

И у меня нет шансов получить исключение NullPointerException. Если бы это было изменено, как вы предложили, я бы вернулся к тому, чтобы сделать:

if (text != null && text.equals(CONSTANT_STRING)) {
    // do something.
}

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

4
ответ дан 24 November 2019 в 11:37
поделиться

Обратите внимание, что контракт предназначен «для любой ненулевой ссылки x». Таким образом, реализация будет выглядеть так:

if (x != null) {
    if (x.equals(null)) {
        return false;
    }
}

x не обязательно должно быть null считаться равным null , потому что следующее определение равно возможно :

public boolean equals(Object obj) {
    // ...
    // If someMember is 0 this object is considered as equal to null.
    if (this.someMember == 0 and obj == null) {
         return true;
    }
    return false;
}
1
ответ дан 24 November 2019 в 11:37
поделиться

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

Использование методов экземпляра для равенства, сравнения и т. Д. Обязательно делает упорядочение асимметричным - небольшая проблема для огромного преимущества полиморфизма. Когда мне не нужен полиморфизм, я иногда создаю симметричный статический метод с двумя аргументами, MyObject.equals (MyObjecta, MyObject b) . Затем этот метод проверяет, являются ли один или оба аргумента нулевыми ссылками. Если я специально хочу исключить пустые ссылки, я создаю дополнительный метод, например. equalsStrict () или аналогичный, который выполняет нулевую проверку перед делегированием другому методу.

1
ответ дан 24 November 2019 в 11:37
поделиться

Это сложный вопрос. Для обратной совместимости вы не можете этого сделать.

Представьте себе следующий сценарий

void m (Object o) {
 if (one.equals (o)) {}
 else if (two.equals (o)) {}
 else {}
}

Теперь, когда equals возвращает false, будет выполнено предложение else, но не при генерации исключения.

Также null на самом деле не равно «2», поэтому имеет смысл возвращать false. Тогда, вероятно, лучше настоять на том, чтобы null.equals ("b") возвращал также false:))

Но это требование действительно создает странное и несимметричное отношение равенства.

0
ответ дан 24 November 2019 в 11:37
поделиться

В первом случае o1.equals (o2) возвращает false, потому что o1 не равно o2 , что совершенно нормально. Во втором случае создается исключение NullPointerException , поскольку o2 равно null . Невозможно вызвать какой-либо метод для null . Это может быть ограничение языков программирования в целом, но мы должны с этим жить.

Также не рекомендуется бросать NullPointerException , если вы нарушаете контракт для метода равно и усложняете вещи более сложными, чем они должны быть.

2
ответ дан 24 November 2019 в 11:37
поделиться

Есть много общих ситуаций, когда null ни в коем случае не является исключительным, например он может просто представлять (неисключительный) случай, когда ключ не имеет значения или иным образом означает «ничего». Следовательно, выполнение x.equals (y) с неизвестным y также довольно распространено, и необходимость всегда сначала проверять null будет напрасной тратой усилий.

Что касается того, почему null.equals (y) отличается, то является программной ошибкой при вызове любого метода экземпляра по нулевой ссылке в Java , и поэтому заслуживает исключения. Порядок x и y в x.equals (y) должен быть выбран таким, чтобы x не было ] null . Я бы сказал, что почти во всех случаях это переупорядочивание может быть выполнено на основе того, что известно об объектах заранее (например,, из их источника или путем проверки на null для других вызовов методов).

Между тем, если оба объекта имеют неизвестную «нулевость», то другой код почти наверняка требует проверки хотя бы одного из них, иначе с объектом можно сделать немногое без риска возникновения NullPointerException .

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

Он мог быть задан по-другому (т. Е. Предварительное условие равно потребовало бы, чтобы аргумент не был нулевым ), поэтому это не должно скажите, что ваша аргументация неверна, но текущая спецификация делает язык программирования более простым и практичным.

2
ответ дан 24 November 2019 в 11:37
поделиться

На вопрос, является ли эта асимметрия непоследовательной, я думаю, что нет, и отсылаю вас к этому древнему дзэн-коану:

  • Спросите любого человека, так ли он хорош, как и все остальные. скажу да.
  • Спросите любого мужчину, не хуже ли он никого, и каждый скажет «нет».
  • Никого не спрашивайте, так ли он хорош, как любой мужчина, и вы никогда не получите ответа.

В тот момент компилятор достиг просветления.

102
ответ дан 24 November 2019 в 11:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: