인스턴스가보다 구체적인 유형의 다른 인스턴스와 동일 할 수 있습니까?


25

이 기사를 읽었습니다 : Java에서 평등 메소드를 작성하는 방법 .

기본적으로 상속을 지원하는 equals () 메소드에 대한 솔루션을 제공합니다.

Point2D twoD   = new Point2D(10, 20);
Point3D threeD = new Point3D(10, 20, 50);
twoD.equals(threeD); // true
threeD.equals(twoD); // true

그러나 좋은 생각입니까? 이 두 인스턴스는 같지만 두 개의 다른 해시 코드를 가질 수 있습니다. 조금 틀리지 않습니까?

대신 피연산자를 캐스팅하면 더 나은 결과를 얻을 수 있다고 생각합니다.


1
링크에 주어진 색상 점이있는 예가 더 의미가 있습니다. 2D 포인트 (x, y)가 Z 성분이 0 인 3D 포인트 (x, y, 0)로 간주 될 수 있다고 생각하고 귀하의 경우 평등이 false를 반환하고 싶습니다. 실제로이 기사에서 ColoredPoint는 명시 적으로 Point와 다르며 항상 false를 반환합니다.
coredump

10
일반적인 관습을 깨뜨리는 튜토리얼보다 더 나쁠 것은 없습니다 ... 프로그래머들로부터 이런 종류의 습관을 없애려면 몇 년이 걸립니다.
corsiKa

3
@coredump 2D 점을 0 z좌표 로 처리하는 것은 일부 응용 프로그램에 유용한 규칙이 될 수 있습니다 (레거시 데이터를 처리하는 초기 CAD 시스템이 고려됩니다). 그러나 그것은 임의의 규칙입니다. 3 차원 이상의 공간에있는 평면은 임의의 방향을 가질 수 있습니다. 흥미로운 문제를 흥미롭게 만듭니다.
벤 rudgers

답변:


71

이것은 전이성 을 파괴하기 때문에 평등이되어서는 안됩니다 . 다음 두 가지 표현을 고려하십시오.

new Point3D(10, 20, 50).equals(new Point2D(10, 20)) // true
new Point2D(10, 20).equals(new Point3D(10, 20, 60)) // true

평등은 전이 적이므로 다음 표현도 참임을 의미해야합니다.

new Point3D(10, 20, 50).equals(new Point3D(10, 20, 60))

그러나 물론 아닙니다.

따라서 캐스팅에 대한 아이디어는 정확합니다. Java에서 캐스팅은 단순히 참조 유형을 캐스팅하는 것을 의미합니다. 여기서 실제로 원하는 것은 Point2D객체에서 새 객체를 만드는 변환 방법입니다 Point3D. 이것은 또한 표현을 더 의미있게 만듭니다.

twoD.equals(threeD.projectXY())

1
이 기사에서는 전이성을 깨고 다양한 해결 방법을 제공하는 구현에 대해 설명합니다. 2D 점을 허용하는 영역에서 우리는 이미 3 차원이 중요하지 않다고 결정했습니다. 따라서 (10, 20, 50)동등 (10, 20, 60)합니다. 우리는 신경 1020.
벤 rudgers

1
해야 Point2DprojectXYZ()제공하는 방법 Point3D자체의 표현을? 다시 말해, 구현 이 서로를 알아야합니까?
hjk

4
@hjk Point2D2D 점을 투영하려면 먼저 3D 공간에서 평면을 정의해야하므로 제거가 더 간단 해 보입니다. 2D 점이 평면임을 알고 있으면 이미 3D 점입니다. 그렇지 않으면 투사 할 수 없습니다. 나는 Abbott의 Flatland를 생각 나게한다 .
벤 rudgers

@benrudgers 그러나 Plane3D3D 공간에서 평면을 정의하는 객체를 정의 할 수 있습니다.이 평면 에는 "제 3 축에 대해 lifta Point2D와 숫자를 받아 들일 수있는 방법 (2D-> 3D가 들어 올려지고 돌출되지 않음)을 가질 수 있습니다. "-평면 법선을 따라 평면에서 거리. 사용하기 쉽도록 공통 평면을 정적 상수로 정의 할 수 있으므로 다음과 같은 작업을 수행 할 수 있습니다.Plane3D.XY.lift(new Point2D(10, 20), 50).equals(new Point3D(10, 20, 50))
Idan Arye

@IdanArye 나는 2D 포인트가 프로젝션 방법을 가져야한다고 제안했다. 리프트 방법을 사용하는 평면에 대해서는 2D 점과 2D 점이있는 것으로 가정되는 점, 즉 점을 소유하지 않는 경우 투영이어야합니다. 포인트를 소유하고 있다면 3D 포인트를 소유하고 문제가있는 데이터 유형과 kludged 방법의 냄새를 없애는 것이 어떻습니까? YMMV.
ben rudgers

10

Alan J. Perlis 의 지혜 에 관한 기사를 읽지 않겠습니다.

그림 9. 10 개의 데이터 구조에 대한 10 개의 함수보다 100 개의 함수가 하나의 데이터 구조에 대해 작동하는 것이 좋습니다.

"평등"을 얻는다는 것은 밤에 스칼라의 Martin Ordersky 발명가 를 유지시키는 일종의 문제라는 사실 equals은 상속 트리에서 재정의 가 좋은 아이디어 인지 여부를 잠시 멈추어야 한다는 것입니다.

우리가 불행한 경우에 발생하는 ColoredPoint일은 상속을 사용하여 데이터 유형을 향상시키지 않고 데이터 유형을 확산시키기 때문에 지오메트리가 실패한다는 것입니다. 이것은 상속 트리의 루트 노드로 돌아가서 작업을 수행해야 함에도 불구하고 equals. 왜 a z와 a color를 추가하지 Point않습니까?

좋은 이유는 것이라고 PointColoredPoint해당 도메인이 섞여 결코 적어도 경우 ... 다른 도메인에서 작동합니다. 그러나이 경우에는를 재정의 할 필요가 없습니다 equals. 비교 ColoredPointPoint평등은 그들이 어울릴 수있는 세 번째 영역에서만 의미가 있습니다. 이 경우, 혼합되지 않은 도메인 중 하나 또는 다른 하나 또는 둘 다에서 동등성 시맨틱을 적용하려고 시도하는 것보다 "평등"을 해당 세 번째 도메인에 맞게 조정하는 것이 좋습니다. 다시 말해, "평등"은 6 개월 전에 오전 2시에 좋은 생각이라고 생각한 경우에도 ColoredPoint.equals(pt)인스턴스에서 실패 하기 를 원하지 않기 때문에 양쪽에서 진흙이 유입되는 장소에 대해 로컬로 정의해야합니다. .PointColoredPoint


6

구 프로그래밍 신들이 클래스를 가진 객체 지향 프로그래밍을 발명 할 때, 객체에 대해 "is a"와 "has a"라는 두 가지 관계를 갖는 것이 구성과 상속에 관한 시점을 결정했습니다.
이것은 서브 클래스가 부모 클래스와 다른 문제를 부분적으로 해결했지만 코드를 손상시키지 않고 사용할 수있게했습니다. 서브 클래스 인스턴스는 "수퍼 클래스 객체"이고이를 직접 대체 할 수 있기 때문에 서브 클래스에 더 많은 멤버 함수 또는 데이터 멤버가 있더라도 "있다"는 부모의 모든 함수를 수행하고 모든 회원. 따라서 Point3D는 "is a"Point이고 Point2D는 둘 다 Point에서 상속하는 경우 "is"라고 말할 수 있습니다. 또한 Point3D는 Point2D의 하위 클래스가 될 수 있습니다.

클래스 간 평등은 도메인마다 문제가 있으며, 위의 예는 프로그래머가 프로그램이 올바르게 작동하는 데 필요한 것에 대해 모호합니다. 일반적으로 수학 영역 규칙을 따르고 비교 범위를이 경우 두 차원으로 제한하면 모든 데이터 멤버를 비교하지 않는 경우 데이터 값이 동일합니다.

따라서 평등이 좁아지는 표를 얻습니다.

Both objects have same values, limited to subset of shared members

Child classes can be equal to parent classes if parent and childs
data members are the same.

Both objects entire data members are the same.

Objects must have all same values and be similar classes. 

Objects must have all same values and be the same class type. 

Equality is determined by specific logical conditions in the domain.

Only Objects that both point to same instance are equal. 

일반적으로 문제 영역에서 필요한 모든 기능을 계속 수행 할 수있는 가장 엄격한 규칙을 선택합니다. 숫자에 대한 내장 평등 테스트는 수학 목적만큼 제한적으로 설계되었지만 반올림, 자르기, 자르기, gt, lt 등을 포함하여 목표가 아닌 경우 프로그래머는 여러 가지 방법을 사용할 수 있습니다. . 타임 스탬프가있는 객체는 생성 시간으로 비교되는 경우가 많으므로 각 인스턴스는 고유해야하므로 비교가 매우 구체화됩니다.

이 경우 설계 요소는 객체를 비교하는 효율적인 방법을 결정하는 것입니다. 때로는 모든 객체 데이터 멤버를 재귀 적으로 비교하는 것이 당신이해야 할 일이며 많은 데이터 멤버가있는 많은 객체가있는 경우 매우 비쌀 수 있습니다. 대안은 관련 데이터 값만 비교하거나, 객체가 다른 유사한 객체와의 빠른 비교를 위해 관련 데이터 멤버의 해시 값을 생성하도록하고, 더 빠르고 덜 CPU 집약적 인 비교를 수행하기 위해 콜렉션을 정렬하고 정리하는 것입니다. 컬링 할 데이터가 동일하고 단일 오브젝트에 대한 중복 포인터가 대신 배치됩니다.


2

규칙은 재정의 할 때마다 hashcode()재정의 equals()되며 그 반대도 마찬가지입니다. 이것이 좋은 아이디어인지 아닌지는 의도 된 사용법에 달려 있습니다. 개인적으로 isLike()동일한 효과를 얻기 위해 다른 방법 ( 또는 유사한 방법 )을 사용합니다.


1
equals를 재정의하지 않고 hashCode를 재정의해도됩니다. 예를 들어, 동일한 동등 조건에 대해 다른 해싱 알고리즘을 테스트하기 위해 그렇게 할 수 있습니다.
Patricia Shanahan

1

비공개 클래스에 대해 동등성 테스트 방법을 사용하는 것이 유용 합니다. 동일한 유형의 객체가 동일한 정보를 나타내는 경우 서로 다른 유형의 객체가 서로 "동일한"것으로 간주 할 수 있지만 Java는 클래스가 각각을 가장 할 수있는 수단을 허용하지 않기 때문입니다. 다른 표현으로 다른 객체를 가질 수있는 경우에는 단일 공용 페이퍼 유형을 사용하는 것이 좋습니다.

예를 들어, 불변의 2 차원 행렬을 캡슐화하는 클래스를 생각해보십시오 double. 외부 방법 중 하나가 크기가 1000 인 항등 행렬을 요청하면 두 번째 방법은 대각선 행렬을 요청하고 1000을 포함하는 배열을 전달하고, 세 번째 방법은 2D 행렬을 요청하고 기본 대각선의 요소가 모두 1.0 인 1000x1000 배열을 전달합니다. 다른 모든 클래스가 0 인 경우, 세 클래스 모두에 주어진 객체는 내부적으로 서로 다른 백업 저장소를 사용할 수 있습니다 (첫 번째는 크기에 대한 단일 필드를 갖고, 두 번째는 1000 요소 배열을, 세 번째는 1000 요소 배열을 가짐). [3 개 모두 1000x1000 불변 매트릭스를 대각선에 1 개로 캡슐화하고 다른 곳에서는 0을 캡슐화하므로] 서로 동등한 것으로보고해야합니다.

별개의 백업 저장소 유형이 존재하지 않는다는 사실 외에도 래퍼는 비교를 용이하게하는 데 유용 할 것입니다. 항목의 동등성을 검사하는 것은 일반적으로 다단계 프로세스이기 때문입니다. 첫 번째 항목이 두 번째 항목과 같은지 알고 있는지 묻습니다. 모르는 경우 두 번째가 첫 번째와 같은지 알고 있는지 물어보십시오. 어떤 객체도 알지 못하면 각 배열에 개별 요소의 내용에 대해 문의하십시오 (장기 개별 항목 비교 경로를 수행하기로 결정하기 전에 다른 검사를 추가 할 수 있음).

이 시나리오에서 각 개체에 대한 동등성 검정 방법은 3 가지 상태 값 ( "예, 동등하지 않습니다", "같지 않습니다"또는 "모름")을 반환해야합니다. 따라서 일반적인 "같음"방법은 적합하지 않습니다. 다른 객체에 대해 물었을 때 어떤 객체도 단순히 "모름"이라고 대답 할 수 있지만, 예를 들어 메인 매트릭스 외부의 요소에 대해 아이덴티티 매트릭스 또는 대각선 매트릭스를 요구하지 않는 대각선 매트릭스에 로직을 추가하면 이러한 비교를 크게 촉진 할 수 있습니다. 유형.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.