Я читал эту статью: Как написать метод равенства в Java .
По сути, он предоставляет решение для метода equals (), который поддерживает наследование:
Point2D twoD = new Point2D(10, 20);
Point3D threeD = new Point3D(10, 20, 50);
twoD.equals(threeD); // true
threeD.equals(twoD); // true
Но хорошая ли это идея? эти два экземпляра кажутся равными, но могут иметь два разных хеш-кода. Разве это не так?
Я полагаю, что это было бы лучше достигнуто, используя вместо этого операнды.
java
c#
scala
comparison
Wes
источник
источник
z
координату может быть полезным соглашением для некоторых приложений (на ум приходят ранние системы CAD, работающие с устаревшими данными). Но это произвольное соглашение. Плоскости в пространствах с 3 и более измерениями могут иметь произвольную ориентацию ... это то, что делает интересные проблемы интересными.Ответы:
Это не должно быть равенством, потому что оно нарушает транзитивность . Рассмотрим эти два выражения:
Поскольку равенство транзитивно, это должно означать, что следующее выражение также верно:
Но, конечно, это не так.
Итак, ваша идея приведения правильна - ожидайте, что в Java приведение просто означает приведение типа ссылки. Что вам действительно нужно, так это метод преобразования, который создаст новый
Point2D
объект изPoint3D
объекта. Это также сделало бы выражение более значимым:источник
(10, 20, 50)
равных(10, 20, 60)
в порядке. Мы заботимся только о10
и20
.Point2D
бытьprojectXYZ()
метод, чтобы обеспечитьPoint3D
представление себя? Другими словами, должны ли реализации знать друг друга?Point2D
кажется проще, поскольку проецирование 2D-точек требует сначала определения их плоскости в 3D-пространстве. Если 2D-точка знает, что это плоскость, то это уже 3D-точка. Если это не так, он не может проецироваться. Мне напомнили о равнине Эбботта .Plane3D
объект, который будет определять плоскость в трехмерном пространстве, у этой плоскости может бытьlift
метод (2D-> 3D поднимается, а не проецируется), который примет aPoint2D
и число для «третьей оси». «- расстояние от плоскости по плоскости нормали. Для простоты использования вы можете определить общие плоскости как статические константы, чтобы вы могли делать такие вещи, какPlane3D.XY.lift(new Point2D(10, 20), 50).equals(new Point3D(10, 20, 50))
Я ухожу от прочтения статьи, размышляя о мудрости Алана Дж. Перлиса:
Тот факт, что правильное «равенство» является той проблемой, которая не дает Мартину Ордски изобретателю Scala работать ночью, должен дать повод задуматься о том,
equals
является ли переопределение в дереве наследования хорошей идеей.Что происходит, когда нам не везет, так это
ColoredPoint
то, что наша геометрия дает сбой, потому что мы использовали наследование для распространения типов данных вместо того, чтобы создать один хороший. Это несмотря на необходимость вернуться и изменить корневой узел дерева наследования, чтобы заставитьequals
работать. Почему бы просто не добавитьz
иcolor
кPoint
?На это есть веская причина,
Point
и ониColoredPoint
работают в разных доменах ... по крайней мере, если эти домены никогда не смешиваются. Но если это так, нам не нужно переопределятьequals
. СравнениеColoredPoint
иPoint
равенство имеет смысл только в третьей области, где им разрешено смешиваться. И в этом случае, вероятно, лучше иметь «равенство», адаптированное к этому третьему домену, а не пытаться применять семантику равенства из одного или другого или обоих из неотмеченных доменов. Другими словами, «равенство» должно быть определено локально по отношению к месту, где с обеих сторон течет грязь, потому что мы можем не захотетьColoredPoint.equals(pt)
потерпеть неудачу в случаях,Point
даже если авторColoredPoint
считает, что это была хорошая идея шесть месяцев назад в 2 часа ночи. ,источник
Когда старые боги программирования изобретали объектно-ориентированное программирование с классами, они решили, когда дело доходит до композиции и наследования, иметь два отношения для объекта: «является» и «имеет».
Это частично решило проблему отличия подклассов от родительских классов, но сделало их пригодными для использования без нарушения кода. Поскольку экземпляр подкласса «является» объектом суперкласса и может быть заменен непосредственно на него, даже если подкласс имеет больше функций-членов или элементов данных, «has a» гарантирует, что он будет выполнять все функции родительского элемента и будет иметь все свои члены. Таким образом, вы можете сказать, что Point3D - это «Point, а Point2D» - это «Point», если они оба наследуются от Point. Кроме того, Point3D может быть подклассом Point2D.
Однако равенство между классами зависит от конкретной предметной области, и приведенный выше пример неоднозначен в отношении того, что нужно программисту для правильной работы программы. Как правило, правила математической области соблюдаются, и значения данных будут генерировать равенство, если вы ограничите область сравнения только в этом случае двумя измерениями, но не если вы сравните все элементы данных.
Итак, вы получите таблицу сужающих равенств:
Как правило, вы выбираете самые строгие правила, которые по-прежнему будут выполнять все необходимые функции в вашей проблемной области. Встроенные тесты на равенство для чисел предназначены для того, чтобы быть настолько ограничительными, насколько это возможно для математических целей, но у программиста есть много способов обойти это, если это не является целью, включая округление вверх / вниз, усечение, gt, lt и т. Д. , Объекты с временными метками часто сравниваются по времени их генерации, поэтому каждый экземпляр должен быть уникальным, чтобы сравнения становились очень конкретными.
Фактором проектирования в этом случае является определение эффективных способов сравнения объектов. Иногда вам нужно сделать рекурсивное сравнение всех элементов данных объектов, и это может оказаться очень дорогостоящим, если у вас много и много объектов с множеством элементов данных. Альтернативы состоят в том, чтобы сравнивать только релевантные значения данных или заставить объект генерировать хеш-значение из соответствующих элементов данных для быстрого сравнения с другими подобными объектами, сортировать и сокращать коллекции, чтобы проводить сравнения быстрее и менее интенсивно, и, возможно, разрешать объекты, которые идентичны в данных, которые будут отбракованы, и на его место будет помещен дублирующий указатель на один объект.
источник
Правило гласит: всякий раз, когда вы переопределяете
hashcode()
, вы переопределяетеequals()
, и наоборот. Является ли это хорошей идеей или нет, зависит от предполагаемого использования. Лично я бы использовал другой метод (isLike()
или похожий) для достижения того же эффекта.источник
Для классов, не являющихся общедоступными, часто бывает полезно иметь метод тестирования эквивалентности, который позволяет объектам разных типов считать друг друга «равными», если они представляют одну и ту же информацию, но потому, что в Java нет средств, с помощью которых классы могут выдавать себя за каждого с другой стороны, часто полезно иметь один открытый тип оболочки в тех случаях, когда возможно иметь эквивалентные объекты с разными представлениями.
Например, рассмотрим класс, инкапсулирующий неизменную двумерную матрицу
double
значений. Если один внешний метод запрашивает единичную матрицу размером 1000, второй запрашивает диагональную матрицу и передает массив, содержащий 1000 единиц, а третий запрашивает двумерную матрицу и передает массив 1000x1000, где все элементы на первичной диагонали равны 1,0. и все остальные равны нулю, объекты, предоставленные всем трем классам, могут использовать разные внутренние хранилища (первое имеет одно поле для размера, второе имеет массив из тысячи элементов, а третье имеет тысячу массивов из 1000 элементов), но должны сообщать друг о друге как эквивалентные [поскольку все три инкапсулируют неизменную матрицу 1000x1000 с единицами на диагонали и нулями повсюду в другом месте].Помимо того факта, что он скрывает существование различных типов хранилища резервных копий, оболочка также будет полезна для облегчения сравнений, поскольку проверка элементов на эквивалентность обычно представляет собой многоэтапный процесс. Спросите первый элемент, если он знает, равен ли он второму; если он не знает, спросите второго, знает ли он, равен ли он первому. Если ни один объект не знает, то спросите каждый массив о содержимом его отдельных элементов [можно добавить другие проверки, прежде чем принять решение о длинном-медленном маршруте сравнения отдельных элементов].
Обратите внимание, что метод проверки эквивалентности для каждого объекта в этом сценарии должен возвращать значение из трех состояний («Да, я не эквивалентен», «Нет, я не эквивалентен» или «Я не знаю»), поэтому нормальный метод «равно» не подходит. В то время как любой объект может просто ответить «я не знаю», когда его спрашивают о любой другой, добавление логики, например, к диагональной матрице, которая не будет беспокоить запрос какой-либо единичной матрицы или диагональной матрицы о каких-либо элементах вне главной диагонали, значительно ускорит сравнение среди таких типы.
источник