Контракт 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)
должен бросить aNullPointerException
даже при том, чтоe.equals(null)
возвратыfalse
.
Если NullPointerException
подходит для compareTo
, почему это не для equals
?
Это фактические слова в Object.equals(Object obj)
документация:
Указывает, "равен ли некоторый другой объект" этому.
И что такое объект?
Объект является экземпляром класса или массивом.
Ссылочные значения (часто просто ссылки) являются указателями на эти объекты и специальным предложением
null
ссылка, которая не относится ни к какому объекту.
Мой аргумент от этого угла действительно прост.
equals
тесты, "равен ли некоторый другой объект" this
null
ссылка не дает никакой другой объект для тестаequals(null)
должен бросить NullPointerException
Исключением действительно должна быть исключительная ситуация. Нулевой указатель не может быть ошибкой программиста.
Вы процитировали существующий контракт. Если вы решите пойти против соглашения, по прошествии всего этого времени, когда каждый Java-разработчик ожидает, что равенство вернет false, вы сделаете что-то неожиданное и нежелательное, что сделает ваш класс изгоем.
Я не могу не согласиться. Я бы не стал переписывать equals, чтобы постоянно генерировать исключение. Я бы заменил любой класс, который делал бы это, если бы я был его клиентом.
Лично я бы предпочел, чтобы он работал так, как есть.
NullPointerException
указывает, что проблема заключается в объекте, в отношении которого выполняется операция равенства.
Если NullPointerException
использовалось, как вы предлагаете, и вы попробовали (вроде бессмысленную) операцию ...
o1.equals (o1)
, где o1 = null ...
Возникает ли NullPointerException
из-за того, что ваша функция сравнения не работает, или из-за того, что o1 имеет значение null, но вы не осознавали?
Я знаю, что это крайний пример, но я чувствую, что с учетом текущего поведения вы легко поймете, в чем проблема.
Подумайте, как .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
...
}
Не то чтобы это обязательно ответ на ваш вопрос, это просто пример того, когда я считаю полезным, что поведение такое, как сейчас.
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.
}
Достаточно ли это веская причина для того, чтобы поведение было таким, как оно есть ?? Не знаю, но это полезный побочный эффект.
Обратите внимание, что контракт предназначен «для любой ненулевой ссылки 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;
}
Я думаю, дело в удобстве и, что более важно, согласованности - включение значений NULL в сравнение позволяет избежать проверки null
и каждый раз реализовывать семантику этого равно
называется. null
ссылки допустимы во многих типах коллекций, поэтому имеет смысл отображать их справа при сравнении.
Использование методов экземпляра для равенства, сравнения и т. Д. Обязательно делает упорядочение асимметричным - небольшая проблема для огромного преимущества полиморфизма. Когда мне не нужен полиморфизм, я иногда создаю симметричный статический метод с двумя аргументами, MyObject.equals (MyObjecta, MyObject b)
. Затем этот метод проверяет, являются ли один или оба аргумента нулевыми ссылками. Если я специально хочу исключить пустые ссылки, я создаю дополнительный метод, например. equalsStrict ()
или аналогичный, который выполняет нулевую проверку перед делегированием другому методу.
Это сложный вопрос. Для обратной совместимости вы не можете этого сделать.
Представьте себе следующий сценарий
void m (Object o) {
if (one.equals (o)) {}
else if (two.equals (o)) {}
else {}
}
Теперь, когда equals возвращает false, будет выполнено предложение else, но не при генерации исключения.
Также null на самом деле не равно «2», поэтому имеет смысл возвращать false. Тогда, вероятно, лучше настоять на том, чтобы null.equals ("b") возвращал также false:))
Но это требование действительно создает странное и несимметричное отношение равенства.
В первом случае o1.equals (o2)
возвращает false, потому что o1
не равно o2
, что совершенно нормально. Во втором случае создается исключение NullPointerException
, поскольку o2
равно null
. Невозможно вызвать какой-либо метод для null
. Это может быть ограничение языков программирования в целом, но мы должны с этим жить.
Также не рекомендуется бросать NullPointerException
, если вы нарушаете контракт для метода равно
и усложняете вещи более сложными, чем они должны быть.
Есть много общих ситуаций, когда null
ни в коем случае не является исключительным, например он может просто представлять (неисключительный) случай, когда ключ не имеет значения или иным образом означает «ничего». Следовательно, выполнение x.equals (y)
с неизвестным y
также довольно распространено, и необходимость всегда сначала проверять null
будет напрасной тратой усилий.
Что касается того, почему null.equals (y)
отличается, то является программной ошибкой при вызове любого метода экземпляра по нулевой ссылке в Java , и поэтому заслуживает исключения. Порядок x
и y
в x.equals (y)
должен быть выбран таким, чтобы x
не было ] null
. Я бы сказал, что почти во всех случаях это переупорядочивание может быть выполнено на основе того, что известно об объектах заранее (например,, из их источника или путем проверки на null
для других вызовов методов).
Между тем, если оба объекта имеют неизвестную «нулевость», то другой код почти наверняка требует проверки хотя бы одного из них, иначе с объектом можно сделать немногое без риска возникновения NullPointerException
.
И поскольку это определено именно так, нарушение контракта и создание исключения для аргумента null
для равно
является программной ошибкой. И если вы рассматриваете альтернативу, требующую генерирования исключения, то каждая реализация равняется
должна делать его частным случаем, и каждый вызов равняется
с любым потенциально объект null
должен быть проверен перед вызовом.
Он мог быть задан по-другому (т. Е. Предварительное условие равно
потребовало бы, чтобы аргумент не был нулевым
), поэтому это не должно скажите, что ваша аргументация неверна, но текущая спецификация делает язык программирования более простым и практичным.
На вопрос, является ли эта асимметрия непоследовательной, я думаю, что нет, и отсылаю вас к этому древнему дзэн-коану:
В тот момент компилятор достиг просветления.