Как указано другими, ваша последняя реализация .equals()
нарушает его договор. Вы просто не можете реализовать его таким образом. И если вы перестанете думать об этом, это имеет смысл, поскольку ваша реализация .equals()
не предназначена для возврата true
, когда два объекта на самом деле равны, но когда они аналогичны. Но достаточно аналогичный is не тот как равный, ни на Java, ни где-либо еще.
Проверить .equals()
javadocs и вы увидите, что любой объект, который реализует его должен придерживаться своего контракта:
Равных метод реализует отношение эквивалентности ссылок непустых объекта:
это рефлексивно: для любого ненулевого опорного значения х, x.equals (х) должна возвращать верно.
Это симметрично: для любых ненулевых опорных значений x и y x.equals (y) должно возвращать истинное тогда и только тогда, когда y.equals (x) возвращает true.
Это транзитивно: для любых ненулевых опорных значений x, y и z, если x.equals (y) возвращает true, а y.equals (z) возвращает true, тогда x.equals (z) должно return true.
Это согласуется: для любых ненулевых опорных значений x и y несколько вызовов x.equals (y) последовательно возвращают true или последовательно возвращают false, если информация, используемая при равных сравнениях с объектами, не изменяется.
Для любого ненулевого опорного значения х, x.equals (NULL) должен возвращать ложь.
Ваша реализация .equals()
не выполняет этот контракт:
- В зависимости от вашей реализации
double similarity(Car car1, Car car2)
, она не может быть симметричным
- Это явно не транзитивно (хорошо объяснено в предыдущих ответах)
- Возможно, это не соответствует:
Рассмотрим пример несколько иной, чем тот, который вы дали в комментарии:
«кобальта» будет равен «синий», а «красный» будет отличаться от «синего»
Если вы использовал некоторый внешний источник для вычисления сходства, например словаря, и если один день «кобальт» не был найден как запись, вы можете вернуть сходство около 0.0, поэтому автомобили не будут равны. Однако на следующий день вы поймете, что «кобальт» - особый вид «синего», поэтому вы добавляете его в словарь, и на этот раз, когда вы сравниваете одни и те же автомобили, сходство очень велико (или около 1.0), поэтому они равны. Это было бы несоответствием .Я не знаю, как работает функция сходства, но если это зависит от чего-либо другого, чем данные, содержащиеся в двух объектах, которые вы сравниваете, вы можете нарушать также ограничение консистенции .equals()
.
Что касается использования TreeMap<Car, Whatever>
, я не вижу, как это может быть полезно. Из TreeMap
javadocs:
... интерфейс Карты определяется в терминах операции РАВНО, но отсортированная карта выполняет все основные сравнения, используя его СотрагеТо (или сравнить) метод, поэтому два ключа, которые считаются равным этим метод, с точки зрения сортированной карты, равен.
Другими словами, в TreeMap<Car, Whatever> map
, map.containsKey(car1)
вернуться бы true
тогда и только тогда car1.compareTo(car2)
вернулся точно 0
для некоторого car2
, который принадлежит map
. Однако, если сравнение не вернулось 0
, map.containsKey(car1)
может вернуть false
, несмотря на то, что car1
и car2
были очень похожи с точки зрения вашей функции подобия. Это связано с тем, что .compareTo()
предназначен для использования , заказывая, а не для сходства.
Таким образом, ключевой момент заключается в том, что вы не можете использовать только Map
в соответствии с вашим прецедентом, потому что это просто неправильная структура. На самом деле, вы не можете использовать какую-либо структуру Java, которая полагается на .hashCode()
и .equals()
, потому что вы никогда не сможете найти объект, соответствующий вашему ключу.
Теперь, если вы хотите, чтобы найти автомобиль, который является наиболее близким к данной машине с помощью вашей similarity()
функции, я предлагаю вам использовать Guava's HashBasedTable structure построить таблицу коэффициентов подобия (или любой другой вам понравится ваше воображаемое имя) между каждой машиной вашего набора.
Этот подход необходимо будет Car
реализовать .hashCode()
и .equals()
как обычно (т.е. не проверять только по цвету, и, конечно же, не прибегая к вашей similarity()
функции). Например, вы можете проверить новый номер номерCar
.
Идея заключается в том, чтобы иметь таблицу которая хранит сходство между каждого автомобиля, с его диагональ чистой, так как мы уже знаем, что автомобиль похож на себя (на самом деле, это равное к себе). Например, для следующих автомобилей:
Car a = new Car("red", "audi", "plate1");
Car b = new Car("red", "bmw", "plate2");
Car c = new Car("light red", "audi", "plate3");
таблица будет выглядеть следующим образом:
a b c
a ---- 0.60 0.95
b 0.60 ---- 0.45
c 0.95 0.45 ----
Для значений подобия, я предполагаю, что автомобили той же марки и того же цвета семьи больше похожие на автомобили того же цвета, но разные марки, и что автомобили разных марок и не одинакового цвета еще менее похожи.
Возможно, вы заметили, что таблица симметрична. Мы могли бы хранить только половину ячеек, если бы была необходима оптимизация пространства.Однако, согласно документам, HashBasedTable
оптимизирован для доступа к ключу строки, поэтому давайте сохраним его просто и дадим дальнейшую оптимизацию в качестве упражнения.
алгоритм, чтобы найти автомобиль, который является наиболее близким к данной машине можно было бы в общих чертах следующим образом:
- Получить строку данного автомобиля
- вернуть автомобиль, который наиболее близок к данной машине в возвращаемой строке, т.е. автомобиль строки с наибольшим коэффициентом подобия
Вот код, показывающий общие идеи:
public class SimilarityTest {
Table<Car, Car, Double> table;
void initialize(Car... cars) {
int size = cars.length - 1; // implicit null check
this.table = HashBasedTable.create(size, size);
for (Car rowCar : cars) {
for (Car columnCar : cars) {
if (!rowCar.equals(columnCar)) { // add only different cars
double similarity = this.similarity(rowCar, columnCar);
this.table.put(rowCar, columnCar, similarity);
}
}
}
}
double similarity(Car car1, Car car2) {
// Place your similarity calculation here
}
Car mostSimilar(Car car) {
Map<Car, Double> row = this.table.row(car);
Map.Entry mostSimilar = Maps.immutableEntry(car, Double.MIN_VALUE);
for (Map.Entry<Car, Double> entry : row.entrySet()) {
double mostSimilarCoefficient = mostSimilar.getValue();
double currentCoefficient = entry.getValue();
if (currentCoefficient > mostSimilarCoefficient) {
mostSimilar = entry;
}
}
return mostSimilar.getKey();
}
public static void main(String... args) {
SimilarityTest test = new SimilarityTest();
Car a = new Car("red", "audi", "plate1");
Car b = new Car("red", "bmw", "plate2");
Car c = new Car("light red", "audi", "plate3");
test.initialize(a, b, c);
Car mostSimilarToA = test.mostSimilar(a);
System.out.println(mostSimilarToA); // should be c
Car mostSimilarToB = test.mostSimilar(b);
System.out.println(mostSimilarToB); // should be a
Car mostSimilarToC = test.mostSimilar(c);
System.out.println(mostSimilarToC); // should be a
}
}
Что касается сложности ... Инициализация таблицы занимает O (n2), при поиске наиболее сходного автомобиля занимает O (N). Я уверен, что это может быть улучшено, то есть зачем ставить автомобили в таблице, которые, как известно, не похожи друг на друга? (мы могли бы поставить только автомобили с коэффициентом подобия выше заданного порога), или вместо того, чтобы найти автомобиль с самым высоким коэффициентом подобия, мы могли бы остановить поиск, когда найдем автомобиль, коэффициент подобия которого выше другого заданного порога, и т.д.
Что определяет 'сходство()> 0,5'? Как только мы это узнаем, мы можем построить новый 'hashCode()' – JLewkovich
@J. Это была фактически упрощенная версия, потому что реальный проект, над которым я работаю, более сложный. Для этой проблемы это может иметь смысл, но, с технической точки зрения, пусть функция подобия определяет сходство строк между цветами. Например, если 2 автомобиля имеют цвета «синий» и «голубой», чем возврат значения более 0,5, но если цвета были «синими» и «красными», он возвращал 0. – giliev
Как ваш метод «равно» будет нарушать общий контракт для 'equals', в глубине души этот вопрос является дубликатом http://stackoverflow.com/questions/27581/what-issues-should-be-considered-when-overriding-equals-and-hashcode- in-java – Raedwald