Это плохая идея, если функция equals (null) вместо этого выдает исключение NullPointerException? - PullRequest
75 голосов
/ 22 мая 2010

Договор 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) должен выдать NullPointerException, даже если e.equals(null) вернет false.

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

Смежные вопросы


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

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

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

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

JLS4.3.1 Объекты

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

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

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

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

Ответы [ 11 ]

102 голосов
/ 22 мая 2010

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

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

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

19 голосов
/ 22 мая 2010

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

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

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

8 голосов
/ 23 мая 2010

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

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

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

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

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

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

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

Integer i = null;
int j = 0;
if(j > i) { // NPE
   ... 
}
4 голосов
/ 14 октября 2013

Если вы принимаете во внимание объектно-ориентированные концепции и рассматриваете целые роли отправителя и получателя, я бы сказал, что такое поведение удобно. Посмотрите в первом случае, вы спрашиваете объект, равен ли он никому. Он ДОЛЖЕН сказать "НЕТ, я не".

Во втором случае вы ни к кому не обращаетесь, поэтому вы никого не спрашиваете. ЭТО должно вызвать исключение, первый случай не должен.

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

В качестве последнего пункта. При возникновении ошибки в вашем коде должно возникать исключение нулевого указателя. Тем не менее, спрашивать объект, если он никто, не следует считать недостатком программирования. Я думаю, что вполне нормально спросить объект, если он не нулевой. Что если вы не контролируете источник, предоставляющий вам объект? и этот источник отправляет вам ноль. Вы бы проверили, является ли объект нулевым, и только потом посмотрите, равны ли они? Разве не было бы более интуитивно просто сравнивать два, и независимо от того, что является вторым объектом, сравнение будет выполняться без исключений?

Честно говоря, я бы разозлился, если метод equals внутри своего тела нарочно возвращает исключение нулевого указателя. Равные предназначены для использования против любого типа объекта, поэтому он не должен быть настолько разборчив в том, что он получает. Если метод equals вернул npe, последнее, о чем я думаю, это то, что он сделал это специально. Особенно учитывая, что это непроверенное исключение. Если бы вы подняли npe, то парню пришлось бы помнить, что нужно всегда проверять наличие нуля, прежде чем вызывать ваш метод, или, что еще хуже, окружать вызов равными в блоке try / catch (Боже, я ненавижу блоки try / catch). ..

4 голосов
/ 22 мая 2010

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

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.
}

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

2 голосов
/ 22 мая 2010

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

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

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

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

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

2 голосов
/ 22 мая 2010

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

Также не стоит бросать NullPointerException вы нарушаете контракт для метода equals и усложняете ситуацию.

2 голосов
/ 22 мая 2010

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

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

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

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

1 голос
/ 22 мая 2010

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

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

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

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 голос
/ 22 мая 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...