사전을 보면 1. 무엇이 무엇과 서로 같다.
즉, 객체 자체가 동일한 경우 동일하다고 합니다.
사전을 보면 1. 등급이나 정도가 같다.
즉, 객체의 값이 동일한 경우 동등하다고 합니다.
Integer a = -128;
Integer b = -128;
System.out.println(a == b); //true
System.out.println(a.equals(b)); //true
Integer a = 1000;
Integer b = 1000;
System.out.println(a == b); //false
System.out.println(a.equals(b)); //true
User user1 = new User("a", 1000); //name, age
User user2 = new User("a", 1000);
Integer a = new Integer(1);
Integer b = new Integer(1);
System.out.println(user1 == user2); //false
System.out.println(a == b); //false
System.out.println(user1.equals(user2)); //false
System.out.println(a.equals(b)); //true
==, equals() 차이점
==은
참조값을 비교
하고, eqauls()는값을 비교
한다.
즉, ==의 목적은동일성
이고, equals()의 목적은동등성
이다.
// Object.equals()
public boolean equals(Object obj) {
return (this == obj);
}
//Integer.equals()
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
//String.equals()
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
Object.equals(), Integer.equals() 차이점
위에서 equals는 값을 비교한다고 했다. 근데
Object.equals()는 예외적으로
참조값을 비교한다.Integer.equals()는
값만 비교
한다.
즉, Object.equlas()는동일성이 목적
, Integer.equlas()는동등성이 목적
이다.
Integer 뿐만 아니라,Boolean, Byte, Long, Double 등의 래퍼클래스
와String
의equals()
도값만 비교하도록 오버라이딩
되어 있다.
처음에 똑같은 Integer끼리 동일성을 (==) 비교했을때 왜 결과가 달랐지?
우선 Integer는 래퍼(wrapper)클래스다. 우선 래퍼클래스는 기본적으로 기본형을 감싸고 있다.
래퍼클래스는 스택에 힙메모리에 있는 객체의 주소를 저장해놓고 힙에 있는 객체를 참조합니다.
여기에서 자바는 Integer 클래스는 내부적으로 캐시 기능을 이용하는데,[-128, 127]
범위의 숫자는 캐시에 등록해놓고 똑같은 객체를 참조합니다.
주의
Integer a = new Integer(-128); Integer b = new Integer(-128); System.out.println(a == b); //false System.out.println(a.equals(b)); //true System.out.println(a.hashCode() == b.hashCode()); //true
주의하자. 위와 다르게 a와 b는 new를 통해 힙에 새로운 객체를 생성한 것으로 동일성은 성립하지 않고, 동등성만 성립한다.
hashCode() 메서드는 객체의 해시 코드를 반환합니다. 해시 코드는 객체를 구분하기 위해 사용되는 값으로, 객체의 내용이 변경되지 않으면 일관된 값을 반환해야 합니다.
일반적으로 해시 코드는 해시 테이블이나 해시 맵 같은 자료구조에서 객체를 검색할 때 사용됩니다.
User user = new User("a", 1000); //name, age
System.out.println(user.hashCode()); //1521118594
user.name = "b";
user.age = 2000;
System.out.println(user.hashCode()); //1521118594
user = new User("b", 2000);
System.out.println(user.hashCode()); //1940030785
위 결과를 보면, 처음 생성한 user객체와 객체의 값을 변경한 경우 결과는 같습니다.
하지만 user에 새로운 객체(new 연산)를 초기화 시켜주면 힙에 있는 다른 메모리를 참조하므로 해시코드 값이 달라집니다.
하지만 실제 프로젝트 에서는 객체의 내부의 값이 변경되면 다른 해시코드가 출력되도록 hascode()를 오버라이딩해서 새로 구현
해주는게 좋습니다.
즉, equals()뿐만 아니라 hashcode()도 객체 내부상태에 의존해야 합니다.
해시코드가 같을 수 있습니다. 이런 경우를 해시 충돌
이라고 하는데, 이 경우 equals()
메서드를 통해 객체들을 비교합니다.
따라서 hashCode()와 equals() 메서드를 모두 오버라이딩
하여 구현해야 하며,
두 메서드 모두 객체의 내부 상태에 의존
해야 합니다. 이렇게 구현된 클래스는 해시 테이블에서 안전하게 사용될 수 있습니다.
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
String objName = obj.name;
int objAge = obj.age;
if (obj instanceof User) {
if(objName.equals(this.name)
&& objAge == this.age) { return true;}
}
return false;
}
@Override
public int hashCode() {
int hash = 17;
hash = 31 * hash + name.hashCode();
hash = 31 * hash + age;
return hash;
}
User userA = new User("A", 0); //name, age
System.out.println("userA.hashCode() = " + userA.hashCode());
userA.age = 20;
System.out.println("userA.age = 20");
System.out.println("userA.hashCode() = " + userA.hashCode());
User userB = new User("A", 20);
System.out.println("userA.equals(userB) = " + userA.equals(userB));
System.out.println("userA.hashCode() = " + userA.hashCode());
System.out.println("userB.hashCode() = " + userB.hashCode());
System.out.print("userA.hashCode() == userB.hashCode() : "
+ (userA.hashCode() == userB.hashCode()));
System.out.println("userA == userB : " + (userA == userB));
출력
userA.hashCode() = 18352
userA.age = 20
userA.hashCode() = 18372
userA.equals(userB) = trueuserA.hashCode() = 18372
userB.hashCode() = 18372
userA.hashCode() == userB.hashCode() : true
userA == userB : false
이제는 객체의 값을 변경만 해도 해시코드가 다르게 출력되는 것을 볼 수가 있다.
즉, equals()와 hashcode()는 객체 내부상태에 의존한다.
왜냐하면 우선 userA와 userB의 이름과 나이는 같습니다.
그럼 hashcode() 계산식에서 차이점은 name.hashCode()
밖에 없는데, 같은 문자열이면 해시코드 값이 같다는 보장은 없지만, 일반적으로 같은 문자열에 대해서는 같은 해시코드 값을 반환
합니다.
그래서 위 코드는 좋지 못한 hashcode()입니다. 왜냐하면 충돌이 자주 일어날 수 있으니까요.
@Override public int hashCode() { int hash = 17; Random random = new Random(); int randomHash = random.nextInt(1000); hash = 31 * hash + name.hashCode(); hash = 31 * hash + age + randomHash; return hash; } /* 출력 userA.hashCode() = 19024 userB.hashCode() = 19271 userA.hashCode() == userB.hashCode() : false */
좋은 예시는 아니지만 이런식으로 랜덤을 이용하면 해시코드가 다릅니다.
위를 보면 equals(), hashcode() 둘다 true지만 서로 다른 객체(다른 힙주소를 참조, userA, userB
)입니다.
즉 동일하지 않습니다. userA == userB : false