2009-10-27 3 views
19

Я пытаюсь переопределить метод для параметризованного класса 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?

+2

Кстати, эта логика не означает, что «a», «a» и «a», «b» равны? Не было бы проще просто написать «если a0 = a0 и a1 = a1, или a0 = a1 и a1 = a0 ...» –

+0

Если вы допустили, чтобы ваши a0 и a1 были пустыми, убедитесь, что вы добавили проверки для этого. –

ответ

15

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

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

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

+0

Точно ... хранение токена класса в этом случае бессмысленно. –

+5

Как насчет Id и Id

, даже если фактическое значение внутри объекта (идентификатор базы данных) одинаково, я не думаю, что они должны считаться равными, поскольку они представляют собой идентификаторы для разных столбцов БД. – herman

+1

+1 для ясного объяснения, но я бы также включил здесь, что писать в методе equals, когда тип параметра не важен, чтобы удалить предупреждения компилятора. Для других читайте это. На этой странице есть еще один ответ, в котором упоминается шаблон , и он используется здесь. – C0M37

4

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

Также см эту тему: What is the equivalent of the C++ Pair<L,R> in Java?

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

EDIT: здесь полезный класс 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

С generics are erased at compile time, вы в принципе не можете. Во время выполнения любой параметр типа ушел, и что касается 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. 
} 
3

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

2

Предложения сохранить ссылку типа E «s с Class объектом кажется неэффективное (это много бессмысленных ссылок на Class занимает память) и бессмысленно для вашей проблемы.

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

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

+0

Я не вижу, как сохранение ссылки на тип E занимает память; каждый объект класса должен быть сохранен классом-загрузчиком/JVM в любом случае, поэтому единственная дополнительная память, которую вы используете, - это дополнительные четыре байта для указателя (8 для 64-разрядных систем). –

+0

Правильно, это ссылки, которые занимают пространство. 4/8 байт на объект могут быть или не быть тривиальными в зависимости от количества экземпляров. Поскольку это не помогает решить проблему, кажется, я бы назвал эти ссылки расточительными в любом размере. –

0

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

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

Идентификация объекта и общий параметр/контр-дисперсия являются двумя ортогональными проблемами.

0

Я согласен с приведенными выше замечаниями, почему класс 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 (если он не является нулевым, из курс.)

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

4

Я просто столкнулся с этой проблемой сам, и в моем -particular- случае, мне не нужно знать тип 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<?>. Подстановочный знак параметра типа?? экономит день.

+1

Никогда не думал использовать вот так. Это фантастический ответ, так как большую часть времени, во время проверки равноценности в классе с параметрами, на самом деле все равно, какими типами являются дженерики. +1 – C0M37

+0

1. Вопрос состоял в том, как проверить, что параметры типа равны, и этот ответ избегает предупреждения «небезопасного приведения», когда параметры типа не отмечены -> downvoted 2. действительно есть случаи, когда вы хотите экземпляры для разных типов считаются неравными – herman

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