Переопределяющий метод «равно»: как определить тип параметра? - PullRequest
19 голосов
/ 27 октября 2009

Я пытаюсь переопределить equals метод для параметризованного класса.

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (!(obj instanceof Tuple))
        return false;

    Tuple<E> other = (Tuple<E>) obj; //unchecked cast
    if (!a0.equals(other.a0) && !a0.equals(other.a1)) {
        return false;
    }
    if (!a1.equals(other.a1) && !a1.equals(other.a0)) {
        return false;
    }

    return true;
}

Как я могу убедиться, что <E> объекта other совпадает с this?

Ответы [ 9 ]

15 голосов
/ 27 октября 2009

Вы можете сделать это, сохранив ссылку на тип Class<E>. Однако, по моему мнению, тесты на равенство должны относиться к значениям, которые представляют объекты, а не к конкретным типам, значения которых выражаются .

Классическим примером этого является, например, API коллекций. new ArrayList<String>().equals(new LinkedList<Object>()) возвращает true. Хотя они имеют совершенно разные типы, они представляют одно и то же значение, а именно «пустую коллекцию».

Лично, если два Tuple s, которые представляют одинаковые данные (например, ("a", "b")), не равны, потому что один имеет тип Tuple<String>, а другой - Tuple<Object>?

4 голосов
/ 23 декабря 2011

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

Например:

public class Example<E> {
    E value;

    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Example<?> other = (Example<?>) obj;
        if (value == null) {
            if (other.value != null)
                return false;
        } else if (!value.equals(other.value))
            return false;
        return true;
    }
}

В приведенном выше коде нет непроверенного приведения из-за использования Example<?>. Подстановочный знак параметра типа '?' спасает день.

4 голосов
/ 27 октября 2009

Из-за стирания вы не можете. Самое лучшее, что вы можете сделать, - это сохранить в классе кортежей тип, который вы планируете хранить в кортеже в элементе поля «java.lang.Class». Затем вы можете сравнить эти поля, чтобы убедиться, что класс кортежа содержит те же типы.

Также смотрите эту тему: Что эквивалентно паре C ++ в Java?

Было бы полезно, если бы вы опубликовали больше о своем классе. Я думаю, что непроверенный актерский состав и число полей, которые вы приравниваете, означает, что это должен быть Tuple нет?

РЕДАКТИРОВАТЬ: вот полезный класс Pair, который я регулярно использую (вы можете адаптировать свой класс Tuple при необходимости). Обратите внимание, что аналогично предложениям других, этот класс просто позволяет содержащимся членам решать вопрос равенства. Ваш вариант использования - это то, что должно определять, действительно ли равенство основано на типе содержащихся в нем членов.

/**
 * Adapted from http://forums.sun.com/thread.jspa?threadID=5132045
 * 
 * 
 * @author Tim Harsch
 *
 * @param <L>
 * @param <R>
 */
public class Pair<L, R> {

    private final L left;
    private final R right;

    public R getRight() {
        return right;
    } // end getter

    public L getLeft() {
        return left;
    } // end getter

    public Pair(final L left, final R right) {
        this.left = left;
        this.right = right;
    } // end constructor

    public static <A, B> Pair<A, B> create(A left, B right) {
        return new Pair<A, B>(left, right);
    } // end factory method

    @Override
    public final boolean equals(Object o) {
        if (!(o instanceof Pair<?,?>))
            return false;

        final Pair<?, ?> other = (Pair<?, ?>) o;
        return equal(getLeft(), other.getLeft()) && equal(getRight(), other.getRight());
    } // end method

    public static final boolean equal(Object o1, Object o2) {
        if (o1 == null) {
            return o2 == null;
        }
        return o1.equals(o2);
    } // end method

    @Override
    public int hashCode() {
        int hLeft = getLeft() == null ? 0 : getLeft().hashCode();
        int hRight = getRight() == null ? 0 : getRight().hashCode();

        return hLeft + (37 * hRight);
    } // end method

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append('<');
        if( left == null ) {
            sb.append("null");
        } else {
            sb.append(left.toString());
        } // end if
        sb.append(',');
        if( right == null ) {
            sb.append("null");
        } else {
            sb.append(right.toString());
        } // end if
        sb.append('>');
        return sb.toString();
    } // end method
} // end class
3 голосов
/ 27 октября 2009

К сожалению, вы не можете сделать это во время компиляции; информация ушла. Таковы последствия стирания типа . Альтернативой является сохранение параметра как экземпляра Class, а затем поиск его позже.

3 голосов
/ 27 октября 2009

Поскольку генерики стираются во время компиляции , вы в принципе не можете. Во время выполнения любой параметр типа пропадает, и что касается JVM, они абсолютно одинаковы во всех отношениях.

Способ обойти это - сохранить поле Class, представляющее тип, и создать объект с этим типом в конструкторе.

Пример конструктора:

public class Tuple < E > {

    public Tuple(Class<E> c) {
        //store the class
    }

}

Или вы можете использовать фабрику:

public static <E> Tuple <E> getTuple(Class<E> type) {
    // Create and return the tuple, 
    // just store that type variable in it 
    // for future use in equals.
}
2 голосов
/ 27 октября 2009

Рекомендации по сохранению ссылки на тип E с объектом Class кажутся неэффективными (это много бессмысленных ссылок на Class, занимающих память) и бессмысленными для вашего проблема.

Обычно не правда, что Foo<Bar> и Foo<Baz> должны быть неравными. Так что вам не нужно E. И в написанном вами коде он даже не нужен для компиляции. Просто приведите Tuple<?>, так как это действительно все, что вы знаете о Tuple на тот момент. Все равно компилируется и все.

Если вы передадите Tuple s с данными двух совершенно разных типов, эти элементы не будут equals, и ваш метод вернет false, как требуется. (Можно надеяться - зависит от тех типов, которые equals реализуют разумно.)

0 голосов
/ 27 октября 2009

Я согласен с комментариями выше, почему класс E должен быть равен? и как вы хотите относиться к подклассам E?

В любом случае, учитывая, что вот пример кода, который может вам помочь:

public class Example<T> {

  T t;

  public Example(T t) {
    this.t = t;
  }

  public static void main(String[] args) {
    final String s = "string";
    final Integer i = 1;
    final Number n = 1;

    final Example<String> exampleString = new Example<String>(s);
    final Example<Integer> exampleInteger = new Example<Integer>(i);
    final Example<Number> exampleNumber = new Example<Number>(n);

    System.out.println("exampleString subclass "  + exampleString.t.getClass());
    System.out.println("exmapleIntger subclass " + exampleInteger.t.getClass());
    System.out.println("exmapleNumber subclass " + exampleNumber.t.getClass());
    System.out.println("Integer equals Number = " + 
        exampleInteger.t.equals(exampleNumber.t));
  }
}

Вы можете вызвать t.getClass (), чтобы получить информацию о классе о типе T (конечно, при условии, что он не нулевой.)

Надеюсь, это поможет.

0 голосов
/ 27 октября 2009

Не по теме - понимаете ли вы, что в соответствии с вашей реализацией Tuple (a0, a1) равен Tuple (a1, a1)? Я подозреваю, что это не то, что вы хотите ...

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

Равенство объектов и общий ко-/ дисперсионный параметр - это две ортогональные проблемы.

0 голосов
/ 27 октября 2009

Используйте отражение. http://tutorials.jenkov.com/java-reflection/generics.html

...