일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 주키퍼
- Hyperledger fabric gossip protocol
- Play2 로 웹 개발
- 그라파나
- 블록체인
- 스칼라 동시성
- 이더리움
- 스칼라 강좌
- Play2
- hyperledger fabric
- CORDA
- Actor
- 하이퍼레저 패브릭
- 엔터프라이즈 블록체인
- 파이썬 동시성
- Adapter 패턴
- Akka
- Golang
- 스위프트
- play 강좌
- akka 강좌
- 안드로이드 웹뷰
- 파이썬
- 하이브리드앱
- 스칼라
- play2 강좌
- 플레이프레임워크
- 파이썬 강좌
- 파이썬 머신러닝
- 파이썬 데이터분석
- Today
- Total
HAMA 블로그
스칼라 강좌 (12) - 객체의 동일성 본문
객체의 동일성
꽤나 복잡하고 미묘한 일들이 도사리고 있습니다. 방심하다 망하죠. ;;
게다가 언어 마다 다릅니다. 테스트만이 살길~
결론을 내릴 정도로 상상 이상으로 실수가 많답니다. OTL
객체의 동일성을 체크하는데 두 언어는 다른 생각을 가지고 있다는 점 체크하세요~
자바에서 동일성을 체크하는 방법에는 == 와 equals 가 사용 되는데요.
값 타입에는 자연스러운 등호와 같지만 둘 다 참조 타입에 관해서는 객체의 주소가 같은지 비교합니다.
즉 위 그림처럼 == 와 equals (java.lang.Object) 둘 다 동일한 주소를 가르켜야 같다고 판정합니다.
위와 같이 가르키는 객체는 달라도 각 객체가 담고있는 값(들) 이 같으면 같다고 만들 수 도 있는데..
equals 의 오버라이딩을 이용해서 동일성 체크방법을 바꿀 수 있습니다.
String a = new String("test")
String b = new String("test")
a.equals(b)
위의 a 와 b 를 비교하면 true 가 되는것은 equals 를 String 객체에서 오버라이딩 하고 있기 때문입니다.
a.equals(b) // true
a == b // false
입니다. 자바에서 == 는 무조건 동일한 객체를 바라보고 있어야 true 가 됩니다.
결국 자신이 만든 객체를 String 처럼 객체가 가진 내용으로 동일성 여부를 판단하려면
반드시 equals 를 오버라이딩을 해 주고 equals 를 사용해야 합니다.
hashCode
" equals 메소드를 오버라이드 하는 모든 클래스는 반드시 hashCode 메소드도 오버라이드 해야 한다"
hashCode 을 오버라이드 하지 않으면 HashMap 과 HashSet 같은 모든 해쉬기반 컬렉션들을 사용할때
문제가 생깁니다.
eqauls 함수 작성법
중요하다고 생각되는 세가지만 살펴보겠습니다. 더 자세한 내용과 팁은 effective java item 8,9 를 참고하세요.
* 동치 관계로 정의하자 (반사성/대칭성/추이성/일관성 을 지키자) . ( 상세 내용 다음 포스트에 )
* equals 메소드를 오버라이드 할 때는 hashCode 를 항상 오버라이드한다.
* equals 메소드의 인자 타입을 Object 대신 다른 타입을 사용하지 말자.
public boolean equals (MyClass c) {
...
}
이렇게 하지 말라는 겁니다.
public boolean equals (Object o) {
if (! (o instanceof MyClass))
return false;
MyClass c = (MyClass) o;
...
}
이게 정석입니다.
인자를 MyClass 로 하면 equals 를 오버라이딩 하는게 아니라 오버로딩하기때문에 상위의 기존 eqauls 메소드는 그대로 남아있게되고, 여러 상황에서 상위 equals 메소드를 사용하게 됩니다.
스칼라
자바 프로그램을 작성할 때 초보자가 자주 빠지는 함정이 equals 를 오버라이드 한 후 비교해야 하는
두 객체를 == 로 비교 하는 것입니다.
스칼라의 경우 객체가 같은지 비교하는 동일성 연산자가 eq 라고 있는데 자주 사용되지는 않습니다.
'x eq y' 경우 같은 객체를 가르킬 때 만 true 입니다.
스칼라에서는 == 동일성을 '자연스러운' 동일성을 위해 예약해 뒀습니다. (코틀린도 마찬가지)
값 타입의 경우는 자바와 마찬가지로 값을 비교하구요
참조 타입에 관해서 스칼라의 == 는 자바의 equals 와 같습니다.
즉 새로운 타입에 관해 equals 메소드를 오버라이드하면 자바와는 다르게 == 의 의미도 재정의가 됩니다.
Any 클래스에 있는 equals 는 오버라이드 하지 않는 경우 자바의 == 와 같은 객체 동일성을 사용합니다.
결국 equals , == , eq 는 기본적으로 같습니다. (동일 주소인지 비교) 하지만 equals 를 오버라이드하면 자바는 그것만 재정의 되는 반면, 스칼라는 == 도 함께 재정의 된다가 중요 포인트입니다.
그림으로 간단히 풀어 보겠습니다.
자바의 == 처럼 같은 객체(주소) 를 가르켜야 같다고 인정됩니다.
== 와 equals
만약 equals 를 오버라이딩 하는 경우에는 위의 그림 처럼 객체의 내용으로 비교 가능합니다.
이때 == 도 자연스럽게 변경됩니다. 따라서 본능적(?) 으로 == 를 객체 비교하는데 사용해도 문제가 없어집니다.
* equals 를 오버라이드 안하면 equals 와 == 는 둘 다 객체의 주소를 가르켜야 같다고 판정됩니다.
equals 의 함정
1. equals 선언 시 잘못된 시그니처를 사용하는 경우
class Point (val x : Int , val y: Int) { ... } 라는 클래스의 eqauls 를 정의해본다고 하자.
def equals(other : Point) : Boolean = this.x == other.x && this.y == other.y
뭐 너무 단순해서 문제가 생길 여지가 없어보인다.
val p1,p2 = new Point(1,2)
p1.equals(p2) // true
val q = new Point(2,2)
p1 equals q // false
잘 작동한다~~
하지만 ~!! 컬렉션에 넣기 시작하면서 문제가 발생한다.
val s = HashSet(p1)
s.contains(p2) // false !!
엥~ 둘은 같은데 HashSet에 존재하지 않다네요.. 아래 예를 일단 보시죠.
val p2a : Any = p2
p1 equals p2a // false
타입을 Point 에서 Any 로 바꾸어서 equals 를 사용하니 다르다고 하네요?
사실 앞에서 정의한 equals 는 표준 메소드인 equals 메소드를 오버라이드하지 않고 그냥 오버로딩합니다.
그 상태에서 HashSet 의 contains 메소드는 제네릭 집합에 작용하기 때문에, Object 에 있는 더 일반적인 equals 를 호출하기때문에 false 가 되었던 것입니다.
다음과 같이 수정해야죠.
override def equals(other Any) = other match {
case that : Point => this.x == other.x && this.y == other.y
case _ => false
}
근데 이것만으로 충분하지 않습니다.. . (-.-;;)
2. equals 를 변경하면서 hashCode 는 그대로 놔두는 경우
val s = HashSet(p1)
s.contains(p2) // false !!
다시 contains 를 해보면 그래도 false 가 나옵니다. (true 가 나올수도..)
이랬다 저랬다 하는 이유는
HashSet 은 원소들을 해시코드에 따른 '해시 버킷' 에 담게 됩니다.
contains 는 먼저 '해시 버킷' 을 결정한다음에 , 그 버킷안에서 원소들을 찾게 되는데
hashCode를 재정의 안하면 객체 주소를 적당히 바꿔서 해시 코드로 만듭니다.
결국 p1 과 p2 는 객체 주소가 다르기 때문에 다른 해시코드를 만들고 다른 '해시버킷' 에 담길 가능성이
커지죠. 그래서 못찾게 되는겁니다. 결국 hashCode 를 동일하게 만들어 주는 작업이 필요합니다.
hashCode 를 구현 해 보겠습니다.
class Point(val x: Int, val y: Int){
override def hashCode = 41 * (41 + x) + y
...
}
이렇게 해주면 p1 과 p2 는 동일 해시코드를 가지고 있기때문에 동일 '해시버킷' 에 담기게되서
contains 는 잘 작동하게 됩니다. (hashCode 를 어떻게 만드느냐도 중요한 주제입니다만 .패스~)
3. equals 를 변경 가능한 필드의 값을 기준으로 정의한 경우
class Point (var x : Int , var y: Int) { ... } 라는 클래스의 eqauls 를 정의해본다고 하자. ( var 로 정의됨)
val p = new Point(1,2)
val s = HashSet(p)
s.contains(p) // true 이다.
여기서
p.x +=1 로 바꾸어보자.
s.contains(p) // false !
왜 이럴까요?
콜렉션안의 저장되있는 '해시버킷' 의 위치와 1 더 해진 후 계산되는 '해시버킷' 의 위치가 달라져서
시야에서 사라져 버렸기 때문입니다.
equals 와 hashCode 가 변경 가능한 상태에 의존하면 잠재적으로 문제를 야기할 수 있음을 인지합시다.
4. equals 를 동치 관계로 정의 하지 않은 경우
동치 관계로 정의하자 (반사성/대칭성/추이성/일관성 을 지키자) . ( 상세 내용 다음 포스트에)
좋은 equals 와 hashCode 를 만드는 방법
class Rational(n : Int, d : Int ) {
val numer = Iif (d<0) -n else n) / g
val denom = d.abs / g
...
override def equals (other : Any) : Boolean =
other match {
case that: Rational => (that canEqual this) && numer == that.numer && denom == that.denum
case _ => false
}
def canEqual(other : Any) : Boolean = other.isInstanceOf (Rational)
override def hashCode: Int = 41 * (41 + number) + demon
...
}
가. equals 를 final이 아닌 클래스에서 오버라이딩하면 , canEqual 메소드를 만들자.
나. canEqual 메소드는 인자 객체가 현재 클래스라면 true 로 하자. (canEqual 내용은 다음 포스트에)
다. equals 메소드 정의에서 전달 받는 파라미터의 타입은 반드시Any 이다.
라. equals 메소드의 본문을 match 표현식을 하나 사용해 작성하라.
마. equals 식에서 첫 번째 대안 부분에는 클래스의 타입과 같은 타입 패턴을 선언한다. case that: Rational =>
바. 이 case 문의 본문에, 객체들이 같기 위해 만족해야 하는 조건을 논리곱(&&) 을 사용해 작성하라.
만약 오버라이드 하는 equals 가 AnyRef 에서 온것이 아니라면, 슈퍼클래스의 equals 를 호출하도록 한다.
super.equals(that) &&
사. match 문의 두 번째 case 에는 와일드 카드 패턴을 사용해 false 반환 . case - => false
아. hashCode 의 경우 equals 메소드에서 동일성 계산에 사용했던 모든 필드('중요한필드') 를 포함시켜라.
전체 객체의 해시 코드를 계산하기 위해, 첫 필드의 해시 코드에 41을 더한 결과에 1 을 곱하고~~반복.
스칼라 요약
* equals 를 오버라이딩하면 == 도 자동으로 적용
* hashCode 를 반드시 함께 오버라이딩
* 타입 시그니처 주의
* 변경 가능한 상태를 가진 값으로 hashCode 를 계산해서는 안된다.
* 클래스가 final 이 아니라면 canEqual 메소드도 정의해야한다.
* 비교 가능한 클래스를 정의할 때 케이스 클래스로 만드는 것도 좋다.
'Scala' 카테고리의 다른 글
스칼라 강좌 (14) - 함수 와 메소드 (0) | 2016.07.23 |
---|---|
스칼라 강좌 (13) - 클래스와 객체 (0) | 2016.07.16 |
스칼라 강좌 (11) - while 루프 와 재귀 (0) | 2016.07.03 |
스칼라 강좌 (10) - 공변성,불변성,역공변성 (0) | 2016.06.27 |
스칼라 강좌 (9) - Stack / Queue (0) | 2016.06.26 |