반응형

객체 비교( == , != , <, >, <=, >=, equals, hashCode)

보통 프로그램 작성 시 데이터의 값을 비교하기 위해 다음과 같은 코드를 사용한다.

 


기본 자료형은 정수/실수/문자 모두 위와 같은 비교가 가능하다.
문자열은 String을 사용하는데 다음과 같이 String객체를 비교해본다.

package exam;

public class Exam {
	public static void main(String[] args) {
		String str1 = "hello";
		char[] ch = new char[] { 'h', 'e', 'l', 'l', 'o' };
		String str2 = new String(ch);
		System.out.println("str1 : " + str1);
		System.out.println("str2 : " + str2);
		System.out.println(str1 == str2);
	}
}

실행결과


분명 동일한 문자열을 갖고 있는데 왜 false일까?
String은 문자열을 사용하기 위해 만들어진 클래스 자료형이다.
따라서 String객체는 new를 통해 생성되며 이는 참조변수를 통해 멤버에 접근한다는 것이다.
== 과 같은 일반 비교 연산자는 참조만 비교하게 되므로 객체가 가진 값이 같더라도
다른 객체로 판단하는 것이다.
이를 그림으로 표현하면 다음과 같다.


String 클래스도 Object를 상속받았다.

그리고 문자열의 비교를 위해 equals메서드가 오버라이딩 되어 있다.


String의 문자열을 비교하고 싶다면 다음과 같이 사용한다.

package exam;

public class Exam {
	public static void main(String[] args) {
		String str1 = "hello";
		char[] ch = new char[] { 'h', 'e', 'l', 'l', 'o' };
		String str2 = new String(ch);
		System.out.println("str1 : " + str1);
		System.out.println("str2 : " + str2);
		System.out.println(str1 == str2);
		System.out.println(str1.equals(str2));
	}
}

실행결과


String의 equals메서드는 String객체가 가지는 문자열 자체를 한 문자씩 비교하여 결과를 만든다.
다름과 같이 객체 내부의 값을 비교하는 것이다.


다음은 Point2D클래스를 이용하여 객체를 생성하고 이를 비교해보도록 한다.

package exam;

class Point2D {
	int x;
	int y;

	Point2D(int x, int y) {
		this.x = x;
		this.y = y;
	}

	void printPoint() {
		System.out.println("[" + x + "," + y + "]");
	}
}

public class Exam {
	public static void main(String[] args) {
		Point2D pos1 = new Point2D(10, 20);
		Point2D pos2 = new Point2D(10, 20);
		System.out.println(pos1.hashCode());
		System.out.println(pos2.hashCode());
		System.out.println(pos1 == pos2);
		System.out.println(pos1.equals(pos2));
	}
}

실행결과


Object가 가진 equals메서드는 단순히 참조변수만을 비교하도록 되어있다.
Point2D가 비록 Object를 상속했더라도 Object의 equals를 오버라이딩 하지 않았으므로
Object의 equals가 실행되어 위와 같이 false결과가 나온 것이다.
그래서 equals를 오버라이딩하여 객체의 값을 비교하는 기능을 정의하고 실행해 본다.

Point2D클래스에 다음과 같이 equals오버라이딩 한다.

package exam;

class Point2D {
	int x;
	int y;

	Point2D(int x, int y) {
		this.x = x;
		this.y = y;
	}

	void printPoint() {
		System.out.println("[" + x + "," + y + "]");
	}

	@Override
	public boolean equals(Object obj) {
		Point2D tmp = (Point2D) obj;
		boolean ret = tmp.x == this.x && tmp.y == this.y;
		return ret;
	}
}

실행결과

 


추가적으로 equals를 오버라이딩 할 때는 hashCode()도 함께 오버라이딩 하도록 한다.

equals메서드의 document일부분을 보면 equals결과를 동일하게 정의하면

hashCode()역시 동일한 값을 반환하도록 정의해야 된다고 말하고 있다.
이 hashCode는 객체의 고유한 값으로 실제 주소를 정수값으로 보여주는데
우리가 직접 사용하는 일은 그리 많지 않다.

하지만 컬렉션에서 객체를 식별할 때 이 hashCode값을 이용하도록 되어 있으므로

equals를 오버라이딩하는 경우 hashCode역시 오버라이딩 하도록 한다.

 

위 Point2D에 적용하면 다음과 같은 개념으로 정의할 수 있다.

package exam;

class Point2D {
	int x;
	int y;

	Point2D(int x, int y) {
		this.x = x;
		this.y = y;
	}

	void printPoint() {
		System.out.println("[" + x + "," + y + "]");
	}

	@Override
	public boolean equals(Object obj) {
		Point2D tmp = (Point2D) obj;
		boolean ret = tmp.x == this.x && tmp.y == this.y;
		return ret;
	}

	@Override
	public int hashCode() {
		//equals에서 동일하다고 판단하는 기준을 이용하여 값을 만들도록 정의한다.
		return this.x + this.y;
	}
}

public class Exam {
	public static void main(String[] args) {
		Point2D pos1 = new Point2D(10, 20);
		Point2D pos2 = new Point2D(10, 20);
		System.out.println(pos1.hashCode());
		System.out.println(pos2.hashCode());
		System.out.println(pos1 == pos2);
		System.out.println(pos1.equals(pos2));
	}
}

실행결과


이클립스등의 IDE에서는 사용자 정의 클래스에 대해 필요한 메서드를 자동으로 생성해주는 기능도 있다.
이를 활용하면 getter/setter나 위 equals와 hashCode등을 정의할 때 유용하다.


 

이클립스의 기능으로 생성한 hashCode와 equals메서드

package exam;

class Point2D {
	int x;
	int y;

	Point2D(int x, int y) {
		this.x = x;
		this.y = y;
	}

	void printPoint() {
		System.out.println("[" + x + "," + y + "]");
	}

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

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

public class Exam {
	public static void main(String[] args) {
		Point2D pos1 = new Point2D(10, 20);
		Point2D pos2 = new Point2D(10, 20);
		System.out.println(pos1.hashCode());
		System.out.println(pos2.hashCode());
		System.out.println(pos1 == pos2);
		System.out.println(pos1.equals(pos2));
	}
}

실행결과


OOP에서 상속개념이 중요하고 오버라이딩의 개념을 이해해야 다형성 및 뒤에 나오는 추상화도 이해할 수 있다. 그러한 개념들을 위 예제들처럼 적절하게 활용할 수 있어야 되므로 스스로 예제를 만들어 꼭 정확히 이해하도록 하자.

반응형

'교육자료 > Java' 카테고리의 다른 글

Java 인터페이스 (interface)  (0) 2017.06.25
Java 추상 클래스(abstract class)  (0) 2017.06.25
Java 클래스 구성 요소(다형성)  (0) 2017.06.25
Java 클래스 구성 요소(Overriding)  (0) 2017.06.25
Java 클래스 구성 요소  (0) 2017.06.25

+ Recent posts