소프트웨어 구조 시리즈 학습 노트(3.5)---ADT와 OOP의 등가성
15761 단어 소프트웨어 구조 시리즈 학습 노트
ADT 및 OOP의 등가성
많은 장면에서 두 대상이'상등'인지 아닌지를 판정해야 한다. 예를 들어 어떤 Collection에 특정 요소가 포함되어 있는지 판단해야 한다. ==equals ()와 차이가 있습니까?어떻게 사용자 정의 ADT를 위해 equals()를 정확하게 실현합니까?
카탈로그
==
vs. equals()
무엇이 등가성과 왜 등가성을 필요로 하는가
ADT는 데이터에 대한 추상적인 것으로 데이터에 대한 일련의 조작으로 나타난다.추상 함수 AF: 내부 표시 -> 추상 표시.추상 함수 AF를 기반으로 ADT의 등가 조작을 정의합니다.
현실 속의 모든 대상의 실체는 독특하기 때문에 완전히 같을 수는 없지만'유사성'이 있고 수학에서는'절대 같음'이 존재한다.
등가성을 판단하는 세 가지 방법
== vs. equals()
==
는 인용 등가성이다.equals()
는 대상 등가성이다.ADT를 사용자 정의할 때 다시 쓰기Object
의equals()
가 필요합니다.기본 데이터 형식에 대해 ==을 사용하여 동일하게 판정한다.대상 유형에 대해 사용
equals()
, 사용==
은 두 대상의 신분 표지 ID가 동일한지 판단하는 것이다(메모리에 있는 같은 공간을 가리킨다).불변 유형의 등가성
먼저 Object에서 구현된 기본값
equals()
을 살펴보겠습니다.public class Object {
...
public boolean equals(Object that) {
return this == that;
}
}
Object에서 구현된 기본값
equals()
은 참조의 등가성을 판단하는 것입니다.이것은 보통 프로그래머가 기대하는 것이 아니기 때문에 다시 써야 한다.다음 밤을 보십시오.
public class Duration {
...
// Problematic definition of equals()
public boolean equals(Duration that) {
return this.getLength() == that.getLength();
}
}
이어서 다음 코드를 계속 시도해 보도록 하겠습니다.
Duration d1 = new Duration (1, 2);
Duration d2 = new Duration (1, 2);
Object o2 = d2;
d1.equals(d2) → true
d1.equals(o2) → false
비록
d2
와 o2
가 최종적으로 같은 대상을 메모리에 참조한다고 해도 그들에게 너는 여전히 다른 결과를 얻을 수 있을 것이다.이게 어떻게 된 일입니까?사실이 증명하듯이 이 방법Duration
은 이미 적재량을 초과equals()
하였는데, 방법 서명이 Object's와 같지 않기 때문이다.우리는 실제로 두 가지equals()
방법이 있는데 그것이 바로 은식equals(Object)
계승Object
과 새로운equals(Duration)
이다.public class Duration extends Object {
// explicit method that we declared:
public boolean equals (Duration that) {
return this.getLength() == that.getLength();
}
// implicit method inherited from Object:
public boolean equals (Object that) {
return this == that;
}
}
컴파일러가 컴파일할 때 형식의 매개 변수를 사용하여 재부팅 작업 사이에서 선택한 것을 회상해 보십시오.예를 들어/연산자를 사용할 때 컴파일러는 매개 변수가
int
인지 float
인지에 따라 정수 제법 또는 부동 제법을 선택합니다.여기서 같은 컴파일링이 발생할 때 선택하십시오.만약 우리가 Object
을 참고한다면 d1.equals(o2)
우리는 최종적으로 equals(Object)
를 호출하여 실현할 것이다.만약 우리가 Duration
를 참고한다면 d1.equals(d2)
에서 우리는 최종적으로 equals(Duration)
버전을 호출할 것이다.설령 이런 상황이 발생한다 하더라도o2
d2
둘 다 운행할 때 같은 대상을 가리킨다!평등은 이미 불일치하게 변했다.방법 서명에서 오류를 범하는 것은 쉬우며, 덮어쓰려고 할 때 방법을 다시 불러옵니다.이것은 흔히 볼 수 있는 오류입니다. 자바는 언어 특성을 가지고 있으며, 주석
@Override
은 초클래스를 다시 쓰는 방법만 의도한다면 사용해야 합니다.이 주석을 통해 자바 컴파일러는 슈퍼클래스에 같은 서명이 있는지 확인하고 서명에 오류가 발생하면 컴파일러 오류가 발생합니다.따라서 올바른 구축 방법
Duration
equals()
:@Override
public boolean equals (Object thatObject) {
if (!(thatObject instanceof Duration)) return false;
Duration thatDuration = (Duration) thatObject;
return this.getLength() == thatDuration.getLength();
}
이것은 이 문제를 해결했다.
Duration d1 = new Duration(1, 2);
Duration d2 = new Duration(1, 2);
Object o2 = d2;
d1.equals(d2) → true
d1.equals(o2) → true
대상 계약
이
equals
방법을 다시 쓸 때, 전체 계약을 준수해야 합니다.다음과 같이 표시됩니다.equals
는 반드시 하나의 등가 관계인 자반, 대칭과 전달의 관계를 정의해야 한다.equals
는 일치해야 한다. 방법의 중복 호출은 반드시 같은 결과를 만들어야 한다. 전제는 equals
가 비교 대상에 사용할 정보를 수정하지 않았다는 것이다.x
에 대해x.equals(null)
false로 돌아가야 한다.hashCode
는 반드시 이 equals
방법이 같은 대상으로 간주되는 두 개의 동일한 결과를 만들어야 한다.만약 두 대상이
equals(Object)
방법에 따라 같다면, 이 두 대상 중의 각 대상에 대한 호출hashCode
방법은 반드시 같은 정수 결과를 만들어야 한다.Object 기본값
hashCode()
은 기본값과 일치합니다equals()
.public class Object {
...
public boolean equals(Object that) { return this == that; }
public int hashCode() { return /* the memory address of this */; }
}
a와 b를 인용할 때 a==b라면 A의 주소==B의 주소입니다.그래서 Object 계약은 만족스러웠어요.
그러나 불변 대상은 서로 다른 실현이 필요하다
hashCode()
.Duration
기본값 hashCode()
을 덮어쓰지 않았기 때문에 Object를 위반하고 있습니다.Duration d1 = new Duration(1, 2);
Duration d2 = new Duration(1, 2);
d1.equals(d2) → true
d1.hashCode() → 2392
d2.hashCode() → 4823
d1
그리고 d2
는 equal()이지만 서로 다른 해시 코드를 가지고 있다.그래서 우리는 이 문제를 해결해야 한다.계약을 충족시키는 간단하고 난폭한 방법은
hashCode
시종일관 상수치를 되돌려주는 것이기 때문에 모든 대상의 해시 코드는 같다.이것은 Object 계약을 충족시켰지만, 모든 키는 같은 슬롯에 저장되고, 모든 검색은 긴 목록을 따라 선형 검색으로 퇴화되기 때문에 재난적인 성능 영향을 미칠 수 있습니다.계약의 더 합리적인 해시 코드를 구성하는 표준적인 방법은 상등성을 확정하는 대상을 계산하는 데 사용되는 모든 구성 요소의 해시 코드(일반적으로 호출
hashCode
된 모든 구성 요소를 사용하는 방법)를 계산한 다음에 이 해시 코드를 조합하여 산술 연산을 하는 것이다.Duration
때문에 이것은 매우 쉽다. 왜냐하면 이 종류의 추상적인 값은 이미 정수치이기 때문이다.@Override
public int hashCode() {
return (int) getLength();
}
소프트 유형의 등가성
등가성 관찰: 상태를 바꾸지 않는 상황에서observer,producer,creator만 호출하는 방법으로 두 mutable 대상이 일치하는지 확인합니다.행위 등가성: 대상을 호출하는 모든 방법, 두 대상을 호출하는 모든 방법, mutator를 포함하여 일치된 결과를 보여줍니다.
가변 유형에 대해 말하자면 왕왕 엄격한 관찰 등가성을 실현하는 경향이 있다.그러나 등가성을 관찰하면 버그가 발생할 수도 있고 RI를 파괴할 수도 있다.
가령 우리가
List
를 만들고 그것을 Set
에 놓았다면:List<String> list = new ArrayList<>();
list.add("a");
Set<List<String>> set = new HashSet<List<String>>();
set.add(list);
우리는 이 집합이 우리가 넣은 목록을 포함하는지 확인할 수 있으며, 이 집합은 다음과 같다.
set.contains(list) → true
하지만 이제 우리는 목록을 바꾼다.
list.add("goodbye");
그것은 더 이상 집합에 나타나지 않는다.
set.contains(list) → false!
사실 그것은 이것보다 더 나쁘다. 우리가 모인 구성원들을 두루 돌아다닐 때, 우리는 여전히 그곳에서 목록을 찾았지만, contains () 는 그것이 거기에 없다고 말했다.
for (List l : set) {
set.contains(l) → false!
}
이게 어떻게 된 일입니까?
List
는 가변 객체입니다.표준 자바에서 집합 클래스, 예를 들어 List
, 돌연변이 영향equals()
과 hashCode()
의 결과를 실현했다.list를 처음 넣으면HashSet
산열통에 저장된 결과 대응hashCode()
.리스트가 뒤이어 돌연변이를 일으켰을 때, 그 hashCode()
변화는 있었지만, HashSet
다른 통으로 이동해야 한다는 것을 깨닫지 못했다.그래서 다시는 못 찾겠어.equals()
와 hashCode()
가 돌연변이의 영향을 받을 수 있기 때문에 우리는 이 대상을 키워드로 하는 산 목록의 불변량을 깨뜨릴 수 있다.다음은 다음과 같은 규범
java.util.Set
에서 나온 눈에 띄는 인용문이다.Note: Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set.
우리는 이 예에서 교훈을 받아들여 가변 유형에 대해 행위의 등가성을 실현하면 된다. 즉, 같은 메모리 공간을 가리키는 Objects만이 서로 같다.따라서 가변 유형에 있어서 이 두 함수를 다시 쓰지 않고 Object 대상의 두 가지 방법을 직접 계승하면 된다.만약 두 가변 대상이 일치하는지 아닌지를 반드시 판단하려면 새로운 방법을 정의하는 것이 가장 좋다.
equals() 및 hashCode()의 최종 규칙
불변 유형의 경우
equals()
는 추상적인 값을 비교해야 한다.이것은 equals()
행위의 등가성을 제공해야 한다는 말과 같다.hashCode()
는 추상적인 값을 하나의 정수로 비추어야 한다.소프트 유형의 경우
equals()
는 비교적 인용해야 한다. 예를 들어 ==와 같다.다시 한 번 말하지만, 이것은 equals()
행위 등가를 제공해야 한다는 말과 같다.hashCode()
는 인용을 정수로 비추어야 한다.자동 포장 및 동등가(Autoboxing and Equality)
Java의 또 다른 계발적 함정.우리는 이미 원시 유형과 그들의 대상 유형의 등가물, 예를 들어
int
과Integer
에 대해 이야기했다.객체 유형equals()
은 올바른 방식으로 구현되므로 같은 값을 가진 두 객체Integer
를 만들면 서로 마주 보게 됩니다.Integer x = new Integer(3);
Integer y = new Integer(3);
x.equals(y) → true
그런데 여기에 미묘한 문제가 하나 있다.적재량을 초과하다.유사한 참조 유형 Integer의 경우 참조 등가가 적용됩니다.
x == y // returns false
그러나 원시 형식 int와 같은 경우 == 행위 등가를 실현한다.
(int)x == (int)y // returns true
그래서 Integer 호환 int를 실제로 사용할 수 없습니다.자바가 int와 Integer를 자동으로 변환한다는 사실은 미묘한 오류를 초래할 수 있습니다.너는 표현식의 컴파일링 시 유형이 무엇인지 알아야 한다.다음 사항을 고려하십시오.
Map<String, Integer> a = new HashMap(), b = new HashMap();
a.put("c", 130); // put ints into the map
b.put("c", 130);
a.get("c") == b.get("c") → ?? // what do we get out of the map?
맵을 넣을 때 int130을 자동으로 Integer로 전환하고 Integer 유형에 대한 ==, reference equivalence 판단
equals()
이며 꺼낼 때 Integer 유형을 얻을 수 있기 때문에 a.get('c')===b.get('c')의 결과는false이다.