toString (), equals () и hashCode () в интерфейсе - PullRequest
51 голосов
/ 12 ноября 2009

Итак, у меня есть интерфейс с кучей методов, которые нужно реализовать, имена методов не имеют значения.

Объекты, которые реализуют этот интерфейс, часто помещаются в коллекции, а также имеют специальный формат toString (), который я хочу, чтобы они использовали.

Итак, я подумал, что было бы удобно поместить hashCode (), equals () и toString () в интерфейс, чтобы убедиться, что я не забыл переопределить метод по умолчанию для них. Но когда я добавил эти методы в интерфейс, IDE / Compiler не будет жаловаться, если у меня не реализованы эти три метода, даже если я явно поместил их в интерфейс.

Почему это не будет навязано мне? Он жалуется, если я не реализую другие методы, но не применяет эти три. Что дает? Любые подсказки?

Ответы [ 12 ]

39 голосов
/ 12 ноября 2009

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

public abstract class MyBaseClass implements ... /* existing interface(s) */ {

    public abstract boolean equals(Object other);

    public abstract int hashCode();

    public abstract String toString();
}

Затем измените текущие классы на extend этот класс.

Этот подход работает, но он не идеальное решение.

  • Это может быть проблематично для существующей иерархии классов.

  • Это плохая идея заставить классы, которые реализуют ваш существующий интерфейс, расширять определенный абстрактный класс. Например, вы можете изменить параметры в сигнатурах методов, чтобы использовать абстрактный класс, а не существующие интерфейсы. Но конечный результат - менее гибкий код. (И люди в любом случае могут найти способы подорвать это; например, добавив свой собственный абстрактный подкласс, который «реализует» методы с помощью вызова super.<method>(...)!)

  • Наложение определенной иерархии классов / шаблона реализации недальновидно. Вы не можете предсказать, будет ли какое-то будущее изменение требований означать, что ваши ограничения вызовут трудности. (Вот почему люди рекомендуют программирование с использованием интерфейсов, а не конкретных классов.)


Возвращаясь к вашему актуальному вопросу о том, почему ваш интерфейс не заставляет класс переопределять эти методы:

Почему это не будет навязано мне? Он жалуется, если я не реализую другие методы, но не применяет эти три. Что дает? Любые подсказки?

Интерфейс налагает ограничение на то, что конкретный класс, реализующий его, имеет реализацию для каждого из методов. Однако не требуется, чтобы сам класс реализовывал эти методы. Реализации метода могут быть унаследованы от суперкласса. И в этом случае именно это и происходит. Методы, унаследованные от java.lang.Object, ограничивают ограничение.

JLS 8.1.5 гласит следующее:

"Если объявленный класс не является абстрактным, все абстрактные методы-члены каждого прямого суперинтерфейса должны быть реализованы (§8.4.8.1) либо объявлением в этом классе , либо существующим объявлением метода, унаследованным из прямого суперкласса или прямого суперинтерфейса , потому что классу, который не является абстрактным, не разрешается иметь абстрактные методы (§8.1.1.1). "

38 голосов
/ 12 ноября 2009

Все объекты в Java наследуются от java.lang.Object и Объект обеспечивает реализации этих методов по умолчанию.

Если ваш интерфейс содержит другие методы, Java будет жаловаться, если вы не полностью реализуете интерфейс, предоставляя реализацию этих методов. Но в случае equals(), hashCode() и toString() (а также нескольких других, которые вы не упомянули), реализация уже существует.

Одним из способов достижения желаемого результата является предоставление в интерфейсе другого метода, скажем, toPrettyString() или чего-то подобного. Затем вы можете вызвать этот метод вместо метода toString() по умолчанию.

17 голосов
/ 12 ноября 2009

Все 3 из этих методов определены как java.lang.Object, который (неявно) расширяется всеми другими классами; поэтому существуют реализации по умолчанию для этих методов, и компилятору не на что жаловаться.

7 голосов
/ 12 ноября 2009

Любой класс, который реализует ваш интерфейс, также расширяет Object. Объект определяет hashCode, equals и toString и имеет стандартные реализации всех трех.

То, чего вы пытаетесь достичь, хорошо, но неосуществимо.

5 голосов
/ 12 ноября 2009

Если вы хотите принудительно переопределить equals () и hashCode (), продолжайте от абстрактного суперкласса, который определяет эти методы как абстрактные.

5 голосов
/ 12 ноября 2009

Существует реализация для этих методов, начиная с Object.

4 голосов
/ 12 ноября 2009

Ваш объект уже содержит реализации этих трех методов, потому что каждый объект наследует эти методы от Object, если они не переопределены.

3 голосов
/ 12 ноября 2009

Другие люди достаточно ответили на ваш вопрос. Что касается решения вашей конкретной проблемы, вы можете подумать о создании собственных методов (возможно, getStringRepresentation, getCustomHashcode и equalsObject) и попросить ваши объекты расширить базовый класс, чьи методы equals, toString и hashCode вызывают эти методы.

Впрочем, это может не дать цели использовать интерфейс. Это одна из причин того, что некоторые люди предположили, что equals, toString и hashCode никогда не должны были бы быть включены в класс Object.

3 голосов
/ 12 ноября 2009

Java заботится только о том, чтобы методы были определены где-то. Интерфейс не заставляет вас переопределять методы в новых классах, которые наследуются от интерфейса в первый раз, если они уже определены. Поскольку java.lang.Object уже реализует эти методы, ваши новые объекты соответствуют интерфейсу, даже если они не переопределяют эти три метода самостоятельно.

1 голос
/ 03 февраля 2013

Адам дает вам причину, почему вы не можете сойти с рук, пытаясь форсировать equals, hashCode и toString. Я хотел бы перейти к следующей реализации, которая касается решения, предоставленного Адамом и Стефаном:

public interface Distinct {
    boolean checkEquals(Object other);
    int hash();
}

public interface Stringable {
    String asString();
}

public abstract class DistinctStringableObject {

    @Override
    public final boolean equals(Object other) {
        return checkEquals();
    }

    @Override
    public final int hashCode() {
        return hash();
    }

    @Override
    public final String toString() {
        return asString();
    }
}

Теперь любой класс, который требует, чтобы он определенно отличался и представлялся как String, может расширять DistinctStringableObject, что заставит реализовать checkEquals, hashCode и toString.

Образец бетона класса:

public abstract class MyDistinctStringableObject extends DistinctStringableObject {

    @Override
    public final boolean checkEquals(Object other) {
        ...
    }

    @Override
    public final int hash() {
        ...
    }

    @Override
    public final String asString() {
        ...
    }
}
...