Как реализовать метод "equals" для генериков с использованием "instanceof"?

У меня есть класс, который принимает общий тип, и я хочу переопределить метод equals не-неудобным способом (т.е. то, что выглядит чисто и имеет минимальный объем кода, но для очень общего использования).

Сейчас у меня есть что-то вроде этого:

public class SingularNode<T> {
    private T value;

    @SuppressWarnings("unchecked")
    @Override
    public boolean equals(Object other){
        if(other instanceof SingularNode<?>){
            if(((SingularNode<T>)other).value.equals(value)){
                return true;
            }
        }
        return false;
    }
}

Что, я предполагаю, довольно ошибочен - я делаю приведение к SingularNode<T> объекта other, что потенциально может вызвать ошибку.

Другое дело - когда я делаю if(other instanceof SingularNode<?>), я на самом деле не проверяю правильную вещь. Я действительно хочу проверить тип T, а не тип ?. Всякий раз, когда я пытаюсь сделать ? в T, я получаю некоторую ошибку, например:

Невозможно выполнить проверку экземпляра с параметризованным типом SingularNode<T>. Вместо этого используйте форму SingularNode<?>, так как дополнительная информация о типовом типе будет стерта во время выполнения

Как я могу обойти это? Есть ли способ сделать T.class.isInstance(other);?

Я полагаю, есть одно действительно уродливое решение для взлома:

@SuppressWarnings("unchecked")
public boolean isEqualTo(Class<?> c, Object obj){
    if(c.isInstance(obj) && c.isInstance(this)){
        if(((SingularNode<T>)obj).value.equals(value)){
            return true;
        }
    }
    return false;
}

Но это просто выглядит неудобно с дополнительным параметром метода, а также не встроенная функция типа equals.

Любой, кто разбирается в дженериках, объясните это? Я не настолько разбираюсь в Java, как вы можете ясно видеть, поэтому, пожалуйста, объясните немного подробнее!

Ответы

Ответ 1

Эта версия не дает предупреждений

public boolean equals(Object other){
    if (other instanceof SingularNode<?>){
        if ( ((SingularNode<?>)other).value.equals(value) ){
            return true;
        }
    }
    return false;
}

Что касается кастинга на SingularNode<T>, это ничего не поможет, вы не можете предположить, что T может быть чем угодно, кроме Object.

Подробнее о том, как сгенерированы обобщенные файлы на Java в

https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

Ответ 2

Решение Evgeniy и аргументы Михала являются правильными - вам не нужно беспокоиться о типе T здесь. Причина в том, что метод equals не зависит от правильной работы дженериков. Вместо этого он объявляется Object, и он принимает Object. Таким образом, он отвечает за проверку типа выполнения того, что было передано.

Если this оказывается SingularNode<String>, и вы сравниваете его с SingularNode<Integer>, тогда ((SingularNode<?>)other).value.equals(value) отлично, потому что вызов Integer.equals с аргументом String будет правильно возвращать false.

Ответ 3

Я поставил здесь ответ, чтобы поставить код.

В вашем примере у вас есть (в псевдокоде) Integer(5).equals(Char('k')), который равен false, в соответствии со следующей версией equals на java.lang.Integer:

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

Идя таким образом, вам не нужно беспокоиться о кастинге.

Ответ 4

У меня та же проблема, однако она более общая. У меня есть класс, где у меня есть 3 типа. Мне не нужно сохранять какую-либо переменную этих типов, потому что этот класс используется для преобразования. Однако здесь хранятся переменные "запрос", и я использую кеш на основе этого класса, поэтому мне нужно реализовать метод equals(), основанный на этих генериках.

Знаете ли вы, есть ли какой-нибудь подход, как сделать это без отражения? Может быть, внутренняя переменная этого типа.. Однако тогда она равна нулю.

public class TheClass<I, O, M> {

    private ClassA param1;
    private ClassB param2;
    private ClassC<M> param3;
    private BiFunction<ClassC<M>, I, Optional<O>> mapping;

    public ClassD<O, M> doSomething(ClassD<I, M> param) {
        ...
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null) {
            return false;
        }
        if (getClass() != o.getClass()) {
            return false;
        }
        TheClass<?, ?, ?> that = (TheClass<?, ?, ?>) o;

        return Objects.equals(getParam1(), that.getParam1()) &&
                Objects.equals(getParam2(), that.getParam2()) &&
                Objects.equals(getParam3(), that.getParam3());
    }
}

Для лучшего воображения... У меня есть набор объектов DAO, получающих данные из базы данных. С другой стороны, у нас есть еще один набор поставщиков API, которые предоставляют похожие данные в другом формате (REST, внутренние системы..) Нам нужна функция отображения от одного типа к другому. Мы используем кеширование для лучшей производительности, и единственным человеком в середине является этот класс.

Ответ 5

Вам не нужно использовать кастинг. Лучше всего соответствует реализации. Я вижу это как

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof VehicleModel)) return false;

        VehicleModel that = (VehicleModel) o;

        if (vehicleName != null ? !vehicleName.equals(that.vehicleName) : that.vehicleName != null)
            return false;

        return true;
    }