Вероятно, самый чистый способ сделать это - определить следующие методы:
Flows reverse()
, которое возвращает обратное направление Flows
данного Flows
Flows canon()
, который возвращает канонизированную форму Flows
- Вы можете определить, например,
Flows
является каноном, если srcAddr.compareTo(dstAddr) <= 0
- В противном случае его
reverse()
является каноном по определению
Тогда для ненаправленного сравнения вы можете просто сравнить канонические формы двух потоков. Наличие этих методов делает остальную логику очень чистой и удобочитаемой (см. Код ниже).
Вкл. Comparator
, Comparable
и согласованность с equals
Используя приведенную выше концепцию reverse()
, если вы хотите f.equals(f.reverse())
всегда, то, возможно, вообще не должно быть понятия направленности. Если это так, то каноникализация - лучший подход.
Если f
обычно не equals(f.reverse())
, и все же вы можете захотеть, чтобы f
и f.reverse()
сравнивались с 0, тогда Comparable
не следует использовать, потому что при этом наложить естественный порядок, который не соответствует равным.
Из документации:
Естественный порядок для класса C
называется в соответствии с equals
тогда и только тогда, когда e1.compareTo(e2) == 0
имеет то же логическое значение, что и e1.equals(e2)
для каждых e1
и e2
класса C
.
Настоятельно рекомендуется (хотя и не обязательно), чтобы естественный порядок соответствовал equals
.
То есть вместо наложения естественного порядка в Comparable
, который не согласуется с equals
, вместо этого следует указать ненаправленный Comparator
.
В качестве аналогии, сравните эту ситуацию с String
, который предоставляет Comparator<String> CASE_INSENSITIVE_ORDER
, который позволяет двум строкам, которые не equals
, сравниваться с 0 в случае нечувствительность.
Здесь вы должны написать Comparator<Flows>
, которое позволяет двум Flows
, которые не equals
, сравниваться с 0 по нечувствительности к направлению.
Смотри также
Смежные вопросы
Пример реализации
Вот пример реализации класса Edge
, который имеет from
и to
, с направленным естественным упорядочением, соответствующим equals
, что также обеспечивает ненаправленный Comparator
.
Затем тестируется с 3 видами Set
:
- A
HashSet
, для проверки equals
и hashCode
- A
TreeSet
, для проверки естественного порядка
- A
TreeSet
с пользовательским Comparator
, для проверки ненаправленности
Реализация краткая и понятная, и должна быть поучительной.
import java.util.*;
class Edge implements Comparable<Edge> {
final String from, to;
public Edge(String from, String to) {
this.from = from;
this.to = to;
}
@Override public String toString() {
return String.format("%s->%s", from, to);
}
public Edge reverse() {
return new Edge(to, from);
}
public Edge canon() {
return (from.compareTo(to) <= 0) ? this : this.reverse();
}
@Override public int hashCode() {
return Arrays.hashCode(new Object[] {
from, to
});
}
@Override public boolean equals(Object o) {
return (o instanceof Edge) && (this.compareTo((Edge) o) == 0);
}
@Override public int compareTo(Edge other) {
int v;
v = from.compareTo(other.from);
if (v != 0) return v;
v = to.compareTo(other.to);
if (v != 0) return v;
return 0;
}
public static Comparator<Edge> NON_DIRECTIONAL =
new Comparator<Edge>() {
@Override public int compare(Edge e1, Edge e2) {
return e1.canon().compareTo(e2.canon());
}
};
}
public class Main {
public static void main(String[] args) {
testWith(new HashSet<Edge>());
testWith(new TreeSet<Edge>());
testWith(new TreeSet<Edge>(Edge.NON_DIRECTIONAL));
}
public static void testWith(Set<Edge> set) {
set.clear();
set.add(new Edge("A", "B"));
set.add(new Edge("C", "D"));
System.out.println(set.contains(new Edge("A", "B")));
System.out.println(set.contains(new Edge("B", "A")));
System.out.println(set.contains(new Edge("X", "Y")));
System.out.println(set);
set.add(new Edge("B", "A"));
set.add(new Edge("Z", "A"));
System.out.println(set);
System.out.println();
}
}
Вывод (, как видно на ideone.com ) ниже, с комментариями:
// HashSet
// add(A->B), add(C->D)
true // has A->B?
false // has B->A?
false // has X->Y?
[C->D, A->B]
// add(B->A), add(Z->A)
[B->A, C->D, Z->A, A->B]
// TreeSet, natural ordering (directional)
// add(A->B), add(C->D)
true // has A->B?
false // has B->A?
false // has X->Y
[A->B, C->D]
// add(B->A), add(Z->A)
[A->B, B->A, C->D, Z->A]
// TreeSet, custom comparator (non-directional)
// add(A->B), add(C->D)
true // has A->B?
true // has B->A?
false // has X->Y?
[A->B, C->D]
// add(B->A), add(Z->A)
[A->B, Z->A, C->D]
Обратите внимание, что в ненаправленном TreeSet
, Z->A
канонизируется до A->Z
, поэтому он появляется перед C->D
в этом порядке. Точно так же B->A
канонизируется к A->B
, который уже находится в наборе, что объясняет, почему там только 3 Edge
.
Ключевые моменты
Edge
является неизменным
Arrays.hashCode(Object[])
используется для удобства; не нужно кодировать все эти формулы
- Если естественный порядок соответствует
equals
, вы можете использовать compareTo == 0
в equals
- Используйте многошаговую логику
return
в compareTo
для краткости и ясности
- Наличие
reverse()
и canon()
значительно упрощает ненаправленное сравнение
- Просто сравните их канонизированные формы в их естественном порядке
См. Также
- Effective Java 2nd Edition
- Пункт 8: Соблюдайте общий контракт при переопределении
equals
- Пункт 9: Всегда переопределять
hashCode
при переопределении equals
- Пункт 10: Всегда переопределять
toString
- Пункт 12: Рассмотреть возможность реализации
Comparable
- Пункт 15: свернутьизменчивость
- Элемент 36: последовательно использовать
@Override
аннотация - Элемент 47: Знать и использовать библиотеки