시작하기 전 ...
string에서 equals()은 단순 값(문자열) 비교로만 알고 있다가, 얼마나 또 생각없이 개발했는지 알게되었다 (부끄)
그래서 다시 정리를 차근 차근 해보고자 한다.
Goal
- equals()에 대해 알아본다
- hashcode()에 대해 알아본다
- toString()에 대해 알아본다
- equals()를 재정의할 때, hashcode(), toString()도 같이 재정의 해야하는 이유
equals()와 hashcode()
equals()와 hashcode()는 최상위 클래스인 Object 클래스에 정의되어 있다. final로 되어있지 않기 때문에 하위 클래스에서 재정의 할 수 있다.
참고
https://dev-rosiepoise.tistory.com/111
equals()란 ?
Object - 객체 동등 비교
- Object의 equals()는 ==와 동일한 결과를 리턴한다.
- 두 객체가 동일한 객체라면 true를 그렇지 않으면 false리턴
- Object의 equals() 메소드는 직접 사용되지 않고 하위 클래스에서 재정의하며 논리적으로 동등한 객체로 취급하고 싶은 경우 재정의한다.
public boolean equals(Object obj) {
return this == obj;
}
Object obj1 = new Object();
Object obj2 = new Object();
System.out.println(obj1 == obj2);
System.out.println(obj1.equals(obj2));
결과
false
false
String - 객체 저장 값 비교
- equals() 메소드는 두 객체를 비교해서 논리적으로 동등하면 true 아니면 false한다.
- 논리적으로 동등하다는 것은 같은 객체이건 다른 객체이건 상관없이 객체가 저장하고 있는 데이터가 동일함을 뜻한다.
- String equals()가 대표적인 예로 객체의 번지를 비교하는 것이 아니라, 문자열이 동일한지 확인 후 같다면 true, 아니면 false 리턴
- 이는 String 클래스가 Object의 equals()메소드를 재정의(오버라이딩)하여 문자열 비교로 변경했기 때문이다
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
} else {
if (anObject instanceof String) {
String aString = (String)anObject;
if (this.coder() == aString.coder()) {
return this.isLatin1() ? StringLatin1.equals(this.value, aString.value) : StringUTF16.equals(this.value, aString.value);
}
}
return false;
}
}
equals()를 재정의해야 하는 이유
예를 들어 Member 객체는 다르지만 id 필드값이 같으면 논리적으로 동등한 객체로 취급하고 싶은 경우가 그렇다.
백문이 불여일타 equals()를 재정의 하지 않은 코드를 살펴보자.
equals()를 재정의 하지 않은 경우
public class Member {
public String id;
public Member(String id) {
this.id = id;
}
}
public static void main(String[] args){
Member obj1 = new Member("blue");
Member obj2 = new Member("blue");
Member obj3 = new Member("red");
if(obj1.equals(obj2)){
System.out.println("obj1과 obj2는 동일하다");
}else{
System.out.println("obj1과 obj2는 동일하지 않다");
}
if(obj1.equals(obj3)){
System.out.println("obj1과 obj3는 동일하다");
}else{
System.out.println("obj1과 obj3는 동일하지 않다");
}
}
결과
obj1과 obj2는 동일하지 않다
obj1과 obj3는 동일하지 않다
-> id가 blue로 같음에도 불구하고 동일하지 않다고 나옴.
equals()를 재정의한 경우
public class Member {
public String id;
public Member(String id) {
this.id = id;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Member){
Member member = (Member) obj;
if(id.equals(member.id)){
return true;
}
}
return false;
}
}
- 매개값(비교 객체)이 기준 객체와 동일한 타입의 객체인지 instanceof로 먼저 확인
- 만약 객체 비교가 다른 타입이라면 equals() 메소드는 false로 리턴
- 비교 객체가 동일한 타입이라면 기준 객체 타입으로 강제 타입 변환하여 필드값이 동이한지 검사. 모두 동일하면 true, 아니면 false
결과
obj1과 obj2는 동일하다
obj1과 obj3는 동일하지 않다
-> 매개값이 Member 타입이지만 id 필드값이 다른 red는 false
hashcode()란 ?
- 해시코드란 객체를 식별할 하나의 정수값
- Object의 hashcode()메소드는 객체의 메모리 번지를 이용해서 해시코드를 만들어 리턴하기 떄문에 객체마다 다른 값을 가지고 있다.
참고
문제의 테스트 코드 참고 (ㅋㅋㅋ)
public static void main(String [] args){
// 객체 생성
String txt1 = new String("kim");
String txt2 = new String("kim");
// 스트링 풀 - 리터럴로 생성
String txt3 = "kim";
String txt4 = "kim";
System.out.println((txt1).equals(txt3)); // true
System.out.println(txt1 == txt3); // false
System.out.println((txt1).equals(txt2)); // true
System.out.println(txt1 == txt2); // false
System.out.println((txt3).equals(txt4)); // true
System.out.println(txt3 == txt4); // true
System.out.println("txt1 hashcode :" +txt1.hashCode());
System.out.println("txt2 hashcode :" +txt2.hashCode());
System.out.println("txt3 hashcode :" +txt3.hashCode());
System.out.println("txt4 hashcode :" +txt4.hashCode());
}
내가 기대했던 결과
true
false
true
false
txt1 hashcode : 랜덤 해쉬값1
txt2 hashcode : 랜덤 해쉬값2
txt3 hashcode : 랜덤 해쉬값3
txt4 hashcode : 랜덤 해쉬값3
-> txt1, txt2는 객체 생성한거라서 문자비교는 true, 객체 비교는 false를 예상하고
txt3, txt4는 리터럴로 생성한것 이기에 같은 객체를 참조하기에 다른 해쉬값을 가질 것이라고 예상하여 총 3가지의 해쉬값을 예상했다
실제 결과
true
false
true
false
txt1 hashcode :106191
txt2 hashcode :106191
txt3 hashcode :106191
txt4 hashcode :106191
텍스트 그대로 이해해서 서로 다른 객체면 서로 다른 hashcode를 리턴하길 기대하면서 찍어봤는데, hashcode값이 같은거다? ?...? 왜 때문에.... 궁금해서 미쳐버려 ㅠㅠ 구글링 + 멘토님한테 피드백받고 이유를 알아냈다!!
- 실제로 참조하는 메모리 주소를 기반으로 해시 값을 생성하는 Object 클래스의 hashcode 메소드는 동일한 객체라고 판단되는데도 다른 해시 코드를 반환할 수 있기 때문에, 이를 재정의하여 동일한 객체에는 동일한 해시 코드를 반환하도록 구현해야 한다.
- Object에 있는 걸 안 쓰는 경우 그 구현에 따라서 값이 나옴.
- 해시 코드는 객체가 해시 테이블과 같은 자료구조에서 어떻게 저장되고 관리될지 결정하는 값
- 서로 다른 객체가 같은 해시 코드를 가질 수 있지만, 이를 "해시 충돌"이라고 함.
- 따라서 두 객체의 hashCode 값이 같다고 해서 반드시 두 객체가 동일한 것은 아님.
- 또한, 그리고 hashCode 값이 다르더라도 두 객체가 서로 다른 것임을 보장하는 것도 아님.
- 이러한 이유로 hashCode는 동일한 객체 여부를 판단하는 데에는 정확한 수단 아님.
- 객체의 내부 내용을 기반으로 정확한 비교를 하려면 equals 메서드를 오버라이딩하여 내부 상태를 비교하도록 구현 해야함
hashcode()를 재정의해야 하는 이유
equals를 재정의하여 두 객체를 동일한 것으로 판단한다면, hashcode 메소드도 같은 값을 반환해야하기 때문이다. 이렇게 하여 동일한 객체로 판단하더라도 서로 다른 해시 코드를 가지지 않으므로 컬렉션 등에서 올바른 동작을 보장할 수 있다.
hashocde()를 재정의 하지 않은 경우
Member obj1 = new Member("blue");
Member obj2 = new Member("blue");
Member obj3 = new Member("red");
System.out.println(obj1.hashCode());
System.out.println(obj2.hashCode());
System.out.println(obj3.hashCode());
결과
589446616
1321640594
457233904
-> id가 blue지만 다른 hashcode값
hashocde()를 재정의를 한 경우
// 추가
@Override
public int hashCode() {
return id.hashCode();
}
결과
3027034
3027034
112785
-> id가 blue인 객체는 같은 hashcode값을 가짐
toString()이란 ?
- 객체 문자 정보(객체를 문자열로 표현한 값)를 리턴
- 클래스명@16진수해시코드
- 우리가 만드는 클래스도 toString() 메소드를 재정의하여 좀 더 유용한 정보를 리턴
toString()를 재정의 하지 않은 경우
public class SmartPhone {
private String company;
private String os;
public SmartPhone(String company, String os) {
this.company = company;
this.os = os;
}
}
public class SmartPhoneExample {
public static void main(String[] args){
SmartPhone myPhone = new SmartPhone("구글", "안드로이드");
String strObj = myPhone.toString();
System.out.println(strObj);
// myPhone.toString()을 자동 호출해서 리턴값을 얻은 후 출력
System.out.println(myPhone);
}
}
결과
com.string.SmartPhone@23223dd8
com.string.SmartPhone@23223dd8
=> 클래스명@16진수해시코드 출력
toString()를 재정의를 한 경우
@Override
public String toString() {
return company +", " + os;
}
결과
구글, 안드로이드
구글, 안드로이드
=> System.out.println();의 매개값은 콘솔에 출력할 내용인데, 매개값이 기본 타입(byte, short, int, long, float, double, boolean)의 값일 경우, 해당 값을 그대로 출력한다. 만약 매개값으로 객체를 주면 객체의 toString() 메소드를 호출해서 리턴값을 받아 출력하도록 되어있다.
'JAVA > about java' 카테고리의 다른 글
[thread] 멀티 스레드와 작업 스레드 생성 방법 (0) | 2023.08.31 |
---|---|
[java] 열거타입 enum (0) | 2023.08.30 |
[java] java.lang, java.util 패키지와 Object 클래스 (0) | 2023.08.24 |
[String] String vs StringBuffer vs StringBulider (0) | 2023.08.22 |
[type] 자료형, 기본 자료형 (Primitive type) vs 참조 자료형(Reference type) (0) | 2023.08.21 |