Object클래스의 메소드에 대해서 살펴보기 이전에 Object 클래스에 대해서 간략하게나마 짚어보도록 하겠습니다.
Object 클래스는 모든 Java 클래스의 최고 조상 클래스입니다.
-> 또한 우리가 직접 상속을 하지 않더라도 암묵적으로 Object 클래스를 상속받고 있습니다.
-> 즉, Java의 모든 클래스는 Object 클래스의 메소들을 사용할 수 있습니다.
- Object 클래스에는 다양한 메소드가 존재합니다. 이 중, 우리는 toString(), hashCode(), equals() 메소드에 대해서 집중적으로 파헤쳐 보겠습니다!
| equals()
먼저, equals() 메소드에 대해서 알아보겠습니다.
public boolean equals(Object obj) {
return (this == obj);
}
Object 클래스의 equals()메소드를 살펴보면 위와 같이 구성되어 있습니다. 또한 Object의 equals()메소드는 비교연산자인 "=="와 동일한 결과를 반환합니다. 즉, 참조값(객체의 주소값)이 같은지, 동일한 객체인지를 확인하는 역할을 합니다.
public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
System.out.println(car1.equals(car2)); // false 반환
System.out.println(car1 == car2); // false 반환
car2 = car1;
System.out.println(car1.equals(car2)); // true 반환
}
이러한 equals() 메소드는 하위 클래스에서 재정의(오버라이딩)해서 사용할 수 있습니다. 대표적인 클래스로 String 클래스를 말할 수 있습니다. String 클래스는 Object의 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;
}
재정의된 equals() 메소드를 살펴보면 먼저, 같은 String 타입인지 확인후, 값을 비교하는 형태입니다. 만약 어느 하나라도 맞지 않는다면 false가 반환되는 것이죠.
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1.equals(str2)); // true 반환
}
* 동일성 vs 동등성
비교 관련한 메소드를 공부하다보면 동일성과 동등성에 대한 언급이 자주 되는 것을 확인 할 수 있습니다. 그러므로 여기서, 잠깐 정리해보겟습니다.
동일성 비교 | "==" 비교를 의미합니다. 객체 인스턴스의 주소 값을 비교하는 것입니다. |
동등성 비교 | 객체 내부의 값을 비교하는 것입니다. |
| hashCode()
해시코드란, 객체를 식별할 수 있는 하나의 정수 값을 의미합니다. 객체의 메모리 번지를 이용해서 리턴하기 때문에 객체마다 상이한 값을 가지고 있습니다.
String str1 = "Hello";
String str2 = "Hello";
Car car1 = new Car();
Car car2 = new Car();
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
// 동일한 값
System.out.println(car1.hashCode());
System.out.println(car2.hashCode());
// 다른 값
이러한, 해시코드는 HashSet, HashTable, HashMap 등에서 동등 비교를 하는데 사용되곤 합니다. 그렇기 때문에 hashCode() 메소드에 대한 오버라이딩이 필요합니다.
HashSet,HashTable,HashMap을 이용해서 설명을 하는 이유는 모두 내부적으로 hashCode()메소드를 이용해서 key,value값을 사용하기 때문입니다.
(put() 메소드,get() 메소드 모두 hashCode() 메소드를 이용합니다.)
지금부터는 실제로 HashMap을 이용하여 예시를 확인해보겠습니다.
public static void main(String[] args) {
Key key1 = new Key(1);
Key key2 = new Key(1);
Map<Key, String> hashMap = new HashMap<Key, String>();
System.out.println(key1.hashCode());
System.out.println(key2.hashCode());
hashMap.put(key1, "Hello World");
String value = hashMap.get(key2);
System.out.println(value); // null 반환
}
- null이 반환되는 이유가 무엇일까?
-> Object 클래스의 hashCode() 메소드는 인스턴스가 다를 경우 다른 hashCode를 반환하기 때문입니다. 그러므로, hashCode()메소드를 재정의하지 않을 경우 제대로 된 key,value 구조를 활용할 수 없습니다.
- 그렇다면, hashCode()만 재정의하면 되는 것일까?
-> 그렇지 않습니다. Key 클래스의 필드인 number가 같더라도, equals() 메소드의 리턴 값이 다를 경우 다른 객체가 되기 때문입니다. HashMap, HashSet, HashTable은 아래 그림처럼, hashCode()메소드의 반환값, equals() 메소드의 반환값이 모두 같을 때 동등한 객체로 판단하기 때문입니다. 따라서, equals() 메소드에 대한 재정의도 함께 해야 합니다.
@Override
public boolean equals(Object obj) {
if (obj instanceof Key) {
Key compareKey = (Key) obj;
if (this.index == compareKey.index) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return index;
}
public static void main(String[] args) {
Key key1 = new Key(1);
Key key2 = new Key(1);
System.out.println(key1.hashCode());
System.out.println(key2.hashCode());
Map<Key, String> hashMap = new HashMap<Key, String>();
hashMap.put(key1, "Hello World");
String value = hashMap.get(key2);
System.out.println(value); // Hello World 반환
}
| toString()
Object클래스의 toString()메소드는 아래와 같습니다.
Generally, we aren't interested in knowing the hashcode of an object, but rather the contents of our object's attributes. - Baeldung
-> 해시코드를 반환하지만, 사실 우리는 객체의 특징,특성과 관련한 내용에 오히려 더 관심이 있습니다 .그렇기에, 일반적으로 우리는 toString()메소드를 재정의해서 사용합니다.
만약, name이라는 필드명을 가진 Car라는 클래스가 있을 경우의 예시를 들어보겠습니다.
public static void main(String[] args) {
Car car1 = new Car("K3");
Car car2 = new Car("BMW");
System.out.println(car1.toString());
System.out.println(car2.toString());
}
- 따라서, 객체에 대한 정보가 그대로 출력될 것입니다. 이 또한 재정의하여 원하는 형식으로 출력이 가능합니다.
@Override
public String toString() {
return "Car is " + name;
}
-> 재정의 이후, 다시 메인 메소드를 실행해보면, toString() 메소드에서 정의한 형식대로 출력이 되는 것을 확인할 수 있습니다!
- 이처럼, toString() 메소드를 이용해서 객체가 가진 정보를 반환할 형식과 내용을 지정할 수 있습니다.
(추가)
다른 Wrapper 클래스에서의 toString()메소드에 대해서도 알아보겠습니다.
public static String toString(int i, int radix) {
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
radix = 10;
/* Use the faster version */
if (radix == 10) {
return toString(i);
}
if (COMPACT_STRINGS) {
byte[] buf = new byte[33];
boolean negative = (i < 0);
int charPos = 32;
if (!negative) {
i = -i;
}
while (i <= -radix) {
buf[charPos--] = (byte)digits[-(i % radix)];
i = i / radix;
}
buf[charPos] = (byte)digits[-i];
if (negative) {
buf[--charPos] = '-';
}
return StringLatin1.newString(buf, charPos, (33 - charPos));
}
return toStringUTF16(i, radix);
}
- 이처럼, Integer 클래스 타입인 숫자를 String의 형태로 반환합니다. 이외에도 다양한 Wrapper 클래스에서 toString()메소드가 재정의 되어있는데 이는 문자열로 변환하는 역할을 합니다.
- Ex) Integer.toString() / Float.toString() / Double.toString()
| Conclusion
- equals(), hashCode(), toString() 메소드에 대한 이해와 예시를 사용해 설명을 하였으며, 이를 통해 단순히 사용만 하고 있었던 저에게 좋은 공부가 되었습니다.
- Object 클래스의 주요 메소드들을 재정의하여 활용할 수 있다는 것을 알 수 있었습니다. 이후, 좀 더 폭 넓은 활용을 할 수 있을 것이라 생각합니다.
(참고한 사이트)
https://kephilab.tistory.com/92
https://jisooo.tistory.com/entry/
https://dublin-java.tistory.com/46
https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html
'Develop > JAVA' 카테고리의 다른 글
Java 디자인 패턴 네번째 이야기 - 팩토리 메소드 패턴(Factory Method Pattern) (0) | 2021.08.16 |
---|---|
Java 디자인 패턴 세번째 이야기 - 빌더 패턴(Builder Pattern) (0) | 2021.08.13 |
Java 디자인 패턴 두번째 이야기 - 프록시 패턴(Proxy Pattern) (0) | 2021.08.10 |
Java 디자인 패턴 첫번째 이야기 - 싱글톤 패턴(Singleton Pattern) (0) | 2021.08.07 |
String과 StringBuffer 그리고 StringBuilder의 차이 (0) | 2021.08.03 |