프로그래밍을 한지 얼마 안된 친구들 중에서는 어떤 변수의 포인터를 만들어두고 같은 값이 들어가있는데 왜 다르다 라고 나오는지 이해가 안된다고 하는 경우가 있다. 이것은 비교하는 대상을 착각한 경우다.

int a=10;
int b=10;
int *pa=&a;
int *pb=&b;

이 경우 a==b = true 이지만 pa==pb 는 false 이다.
물론 조금더 아는사람이라면 *pa==*pb 가 true라는점을 알고 있을것이다.

이런것이 자바에서도 적용이 되는데 '자바는 포인터가 없잔아요? 구차취지마세여' 라고 하시면 안됩니다.

이부분은 여러사람이 착각 하는 경우가 있는데 자바에는 모든 객체는 포인터입니다.
물론  C의 포인터와 모양도 다르고 사용법도 다르지만말입니다.


일반적으로 C/C++에서는 기본형변수와 구조체, 클레스 모두 포인터 변수와 일반변수 다 사용할수 있습니다.
하지만 자바에서는 기본형 변수는 일반변수, 클레스는 포인터(참조)변수 로 정확하게 구분을 합니다.
이런 이유로 C에서 발생하던 포인터에 의한 부작용들을 제거하면서 포인터의 장점을 일부 가져올수 있었습니다.
물론 이것때문에 귀찬은게 이만저만이 아닌 경우도 많습니다. 예를들어 swap이라던지...

하여튼 이런 점들 때문에 객체들이 같은것인지 확인하기 위해서는 두가지 방법이 생깁니다.
하나는 일반적으로 생각하는  '==' 이고 또하나는 Object 클레스로부터 상속받은 equals 입니다.
일반변수 (int, long, char, float double 등) 의 경우에는 Object를 상속받은 클레스가 아니기 때문에 equals같은건 있지도 않고 '=='을 이용하면 두 변수 사이에 값이 동일한지만 비교합니다.

하지만 클레스를 이용해서 생성된 객체들 사이에는 전혀 다른 동작을 하게됩니다.
위에서 클레스의 객체는 참조 변수 라는 이름으로 포인터를 아는사람이라면 포인터라고 생각해도 이해하는데 큰 무리는 없습니다.

예를들어 String 의 객체 s1과 s2가 있습니다.
s1=new String("abcd") 이고
s2=new String("abcd") 입니다.
이때 s1==s2 가 참 이라고 생각하신다면 위에서부터 다시 한번 읽어보세요.
s1과 s2는 실제 메모리에 대한 참조, 즉 쉽게 말해서 메모리의 주소를 가지고 있는데 s1과 s2에 new String("abc") 를 한것은 각각 다른 메모리 공간에 "abc"라는 문자열을 만들고 그것의 참조(주소)를 s1과 s2에 기록한것입니다.
따라서 서로 다른 메모리주소를 비교하면 당연히 다르다 라는 결론이 나옵니다.

하지만 우리가 두 문자열의 비교에서 원하는것은 서로가 가진 내용이 같은가 에 대한것입니다. 이때 사용할수 있는것이 equals 메소드 입니다.

String 에는 당연히 equals 가 구현되어있고 s1.equals(s2) 혹은 s2.equals(s1)을 한다면 true를 반환할것입니다.

하지만 우리가 만드는 클레스에는 equals가 오버라이딩 되어있지 않습니다. 이때 equals를 호출하게 되면 Object에 만들어져 있는 equals를 호출하게 되는데 이것은 '==' 과 동일한 동작을 하게됩니다.

다시말해서 오버라이딩 하지 않으면

boolean equals(Object o){
return this==o;
}
라는겁니다.

따라서 객체의 내용물을 비교하기 위해서는 equals를 오버라이딩 해줄 필요가 있습니다.

책에 따르면 equals 는 5가지의 보편적인 계약을 따라야 한다고 합니다.

  • 재귀적이다. - x.equals(x) 는 반드시 true이다.
  • 대칭적이다. - a.equals(b)==b.equals(a) 이어야한다.
  • 이행적이다. - a.equals(b) == true, b.equals(c) == true 이라면 a.equals(c) == true 이어야한다.
  • 일관적이다. - x와 y의 정보가 변경되지 않는 이상 x.equals(y)는 항상 같은 결과를 반환해야한다.
  • null에 대해서는 false를 반환해야한다.

라고 합니다.

위의 내용들은 동일한 클레스 사이에서는 별로 신경안쓰고 만들어도 되는겁니다.
그러나 상속이 들어가고 이상한짓을 하게되면 복잡해 지는거죠

위의 내용을 지키면서 가장 간단하게 equals를 구현하는 방법은 eclipse 를 사용하는겁니다. 오오 이클립스 오오


위와 같이 이클립스의 메뉴(혹은 우클릭)>소스>hashCode() 및 equals() 생성 을선택하면


이런 창이 뜹니다. 여기서 equals 사용시 이 내용이 같다면 true 를 반환하자, 라는 필드를 선택해줍니다.
저는 Point 라는 클레스의 x,y 좌표를 기록하는것이기 때문에 뭐가되었든 x,y값만 같으면 같다고 보겠습니다.
이렇게 한뒤 확인을 하면
public class Point {
	int x,y;

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Point other = (Point) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}
	
}

위와같은 코드들이 자동으로 생성된다.
책에서는 위의 코드에 몇가지 테클을 거는데
obj==null 은 불필요한 코드이고 getClass() 를 이용한 비교는 뭔가 께름칙 하다. 라는것이다.

getClass 보다는 instanceof 라는 키워드를 사용하는것이 좋고 instanceof 를 체크하면서 null도 한번에 된다는것이다.

하지만 instanceof를 사용하게 되면 클레스를 상속했을때 문제가 생긴다.
Point를 상속한 클레스에서 Point 객체를 비교하면 false를 반환할것이다. 하지만 Point에서 Point를 상속한 클레스를 비교하면 true를 반환할지도 모른다.
Point 의 입장에서는 상속했든 뭐든 Point클레스의 인스턴스 이고 x와 y만 비교하면 되기 때문에 true를 리턴하게된다.
이는 위의 5가지 규칙중 대칭적이다. 라는 부분을 지키지 못하는 결과를 만들어낸다.

이것을 해결하는 방법은 이클립스에서 만들어내는 코드처럼 getClass를 사용하는것이고 하나는 컴포넌트로 사용하는 것이다.

만약 색과 좌표를 동시에 표현하는 클레스가 있다면, 이것을 Point 를 상속한뒤 색을 추가하는것이 아니라. 클레스의 맴버 변수로써 Point 와 Color을 가지게 하라는것이다.


class ColorPoint{
	Point p;
	Color c;
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((c == null) ? 0 : c.hashCode());
		result = prime * result + ((p == null) ? 0 : p.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof ColorPoint))
			return false;
		ColorPoint other = (ColorPoint) obj;
		if (c == null) {
			if (other.c != null)
				return false;
		} else if (!c.equals(other.c))
			return false;
		if (p == null) {
			if (other.p != null)
				return false;
		} else if (!p.equals(other.p))
			return false;
		return true;
	}
	
}

위와 같은 형식으로 말이다.
이런식으로 사용하게되면 ColorPoint는 Point의 객체도 아니며 Color의 객체도 아니게 되므로 애초에 비교할수도 없다.
만약 좌표가 같은지 궁금하다면
colorPoint.p.equals(point) 의 형식으로 비교를 하면 될일이다.

여차저차 해도 귀찬으니까 이클립스에서 자동으로 만들어주는 equals 메소드를 그냥 쓰도록하자.
이클립스를 만들고 사용하는 여러 괴물들이 이렇게도 해보고 저렇게도 해본뒤 가장 쓸만하니 만들어 놓은것이 아니겠는가?


ps.위에 나오는 hashCode라는 메소드는 항목 9에서 살펴보도록 하자. 사실 hashCode 메소드도 특별한 이유가 없으면 그냥 만들어주는거 쓰면된다.
Posted by 동적할당
: