우리는 지금까지 String, Integer와 같은 참조형 클래스를 사용하고 있었습니다.
이들 클래스는 java.lang 패키지에 속해있으며 String 클래스의 전체 이름은 java.lang.String입니다.
외부 패키지에 선언된 클래스를 사용할 때는 import문으로 해당 클래스의 패키지와 함께 선언해줘야 합니다.
그런데 import java.lang.String;이라는 문장을 써준 적이 없죠?
이 말인즉슨 import java.lang.*; 문장이 자동으로 추가되어 암묵적으로 사용할 수 있었던 것이었습니다.
오늘 이 글에서 다룰 JDK 기본 클래스는 Object 클래스입니다.
Object 클래스는 모든 클래스의 최상위 클래스입니다.
모든 클래스는 Object 클래스를 상속받습니다.
상속받으려면 extends 키워드를 사용하여 상속받아야 하는데, 컴파일러가 자동으로 Object 클래스를 상속시켜줍니다.
그래서 Object 클래스에 포함된 메서드를 사용할 수 있고, 재정의할 수도 있고 형 변환할 수도 있습니다.
하지만 JavaDoc(F1)을 보면 알겠지만 final 키워드를 사용해 상속된 메서드는 재정의할 수 없습니다.
Object 클래스에서 자주 쓰이는 메서드는 아래와 같습니다.
메서드 | 설명 |
String toString() | 객체를 문자열로 표현하여 반환함, 재정의하여 객체에 대한 설명이나 특정 멤버 변수 값을 반환함 |
boolean equals(Object obj) | 두 인스턴스가 동일한지 여부를 반환, 재정의하여 논리적으로 동일한 인스턴스임을 정의할 수 있음 |
int hashCode() | 객체의 해시 코드 값을 반환함 |
Object clone() | 객체를 복제하여 동일한 멤버 변수 값을 가진 새로운 인스턴스를 생성함 |
Class getClass() | 객체의 Class 클래스를 반환함 |
void finalize() | 인스턴스가 힙 메모리에서 제거될 때 가비지컬렉터(GC)에 의해 호출되는 메서드임. 네트워크 연결 해제, 열려 있는 파일 스트림 해제 등을 구현할 때 사용함 |
void wait() | 멀티스레드 프로그램에서 사용하는 메서드임, 스레드를 '기다리는 상태' (non runnable)로 만듬 |
void notify() | wait() 메서드에 의해 기다리고 있는 스레드(non runnable 상태)를 실행 가능한 상태(runnable)로 가져옴 |
위 표에 적어둔 메서드들은 Object 클래스에서 주로 쓰이는 메서드이지만 더 주로 쓰이는 메서드 몇 가지를 자세히 설명하겠습니다.
1. toString() 메서드
인스턴스 정보를 문자열로 반환하는 메서드입니다.
public class Test {
public static void main(String[] args) {
Book book1 = new Book(200, "칼스의블로그");
System.out.println(book1);
System.out.println(book1.toString());
}
}
class Book {
int bookNumber;
String bookTitle;
public Book(int bookNumber, String bookTitle) {
this.bookNumber = bookNumber;
this.bookTitle = bookTitle;
}
}
위와 같이 생성자를 만들고 toString() 메서드 없이 출력한 것과 붙이고 출력한 것의 결과는 아래와 같이 동일합니다.
'클래스명@16진수 해시 코드 값'으로 출력됩니다.
2. equals() 메서드
두 인스턴스의 주소 값을 비교하여 boolean 타입으로 반환해주는 메서드입니다.
그런데 서로 다른 주소 값을 가질 때도 같은 인스턴스라고 정의할 수 있는 경우가 있습니다.
생성된 두 인스턴스가 '같다'라는 것은 무엇을 의미할까요?
인스턴스를 가리키는 참조 변수가 두 개 있을 때 이 두 인스턴스가 물리적으로 같다는 것은,
두 인스턴스의 주소 값이 같은 경우를 말합니다.
다시 말해 두 변수가 같은 메모리 주소를 가리키고 있다는 뜻입니다.
다만 물리적인 것과 논리적인 것을 기준으로 생각했을 때는 equals() 메서드의 재정의가 필요할 수 있습니다.
0x100 : studentLee, 학번 100, 이름: 이칼스
0x200 : studentKals, 학번 100, 이름: 이칼스
위처럼 물리적인 주소는 다르지만 중복 없는 학번과 이름이 같으니 논리적으로는 같은 학생입니다.
따라서 equals() 메서드가 재정의해줄 필요성이 생겼습니다.
class Student {
...
@Override
public boolean equals(Object obj) {
if(obj instanceof Student) { // obj 파라미터가 Student 자료형인가?
Student st = (Student)obj;
if(this.studentId == st.studentId) return true;
else return false;
}
return false;
}
위와 같이 겹칠 수 없는 학번을 기준으로 학번이 동일할 때 true를 반환하도록 오버라이딩해주면 되겠지요.
# String 클래스와 Integer 클래스에서의 equalse() 메서드는 아래와 같이 재정의되어 있다.
String의 경우 문자열이 동일한 경우 true, 그렇지 않으면 false.
Integer의 경우 정수가 동일한 경우 true, 그렇지 않으면 false.
3. hashCode() 메서드
해시(hash)는 정보를 저장하거나 검색할 때 사용하는 자료 구조입니다.
정보를 어디에 저장할 것인지, 어디서 가져올 것인지 해시 함수를 사용하여 구현합니다.
해시 함수는 객체의 특정 정보(키 값)를 매개변수 값으로 넣으면 그 객체가 저장되어야 할 위치나
저장된 해시 테이블 주소(위치)를 반환합니다.
따라서 객체 정보를 알면 해당 객체의 위치를 빠르게 검색할 수 있습니다.
해시 함수는 개발하는 프로그램 특성에 따라 다르게 구현됩니다.
자바에서는 두 인스턴스가 같다면 hashCode() 메서드에서 반환하는 해시 코드 값이 같아야 합니다.
따라서 논리적으로 같은 두 객체도 같은 해시 코드 값을 반환하도록 hashCode() 메서드를 재정의해줄 필요성이 있습니다.
이 말인즉슨 equals() 메서드를 재정의했다면 hashCode() 메서드도 재정의해야 합니다.
이 것은 프로그래밍에서 지켜줘야 할 자바에서의 규약입니다.
String 클래스와 Integer 클래스의 equals() 메서드는 재정의되어 있다고 위에서 말했습니다.
그러면 hashCode() 메서드도 함께 재정의되어 있을 겁니다.
위에서 equals() 메서드를 재정의 해줬을 때 중복될 수 없는 '학번'을 기준으로 오버라이딩 해주었습니다.
일반적으로 hashCode() 메서드를 재정의할 때는 equals() 메서드에서 논리적으로 같다는 것을 구현할 때 사용한
멤버 변수를 활용하는 것이 좋습니다.
따라서 hashCode() 메서드는 위에서 재정의한 멤버 변수인 '학번' 멤버 변수를 활용하는 것이 좋겠죠?
@Override
public int hashCode() {
return studentId;
}
자, 이제 결과를 확인해봅시다.
hashCode() 메서드를 재정의했을 때 실제 인스턴스의 주소 값은 System.indentityHashCode() 메서드를 사용하면
확인할 수 있습니다.
실제 메모리 값을 출력해보면 두 값은 다르다고 나오지요?
즉 논리적으로는 같지만 실제로는 다른 인스턴스를 완벽하고 깔끔하게 재정의 해주었습니다.
'BackEnd > Java' 카테고리의 다른 글
구현 코드가 없는 인터페이스 (interface) (0) | 2020.04.26 |
---|---|
enum (데이터 열거형 타입) (1) | 2020.03.23 |
향상된 for문 (for each문) (0) | 2020.03.23 |
추상화 (Abstract) (0) | 2020.03.16 |
상수 (final) (0) | 2020.03.16 |