본문 바로가기

프로그래밍 언어/JAVA

JAVA 입문- Object 클래스

지난 글: [프로그래밍 언어/JAVA] - JAVA 입문 - 인터페이스

 

java.lang 패키지

지금까지 프로그램을 공부하고 구현하면서 String, Integer 같은 클래스를 사용했다. 이런 클래스들은 java.lang 패키지에 속해 있다. 자바 프로그래밍에서 외부 패키지에 선언한 클래스를 사용할 때는 import 문으로 클랠스가 어느 패키지에 속해 있는지 선언해야 하는데 여태 공부하면서 import java.lang.String; 같은 문장을 쓴 기억이 없다. 이유는 java.lang 패키지는 컴파일할 때 import java.lang 문장이 자동으로 추가되어 java.lang 패키지 하위 클래스를 모두 사용할 수 있기 때문이다. 오늘은 모든 자바 클래스의 최상위 클래스인 java.lang.Object 를 알아보자.

 

모든 클래스의 최상위 클래스 Object

Object 클래스는 모든 자바 클래스의 최상위 클래스, 즉 모든 클래스는 Object로부터 상속을 받는다. 하지만 나는 공부하며 extends Object와 같은 문장을 사용하지 않았다. 이유는 컴파일 과정에서 extends Object가 자동으로 쓰인다고 한다. 우리가 직접 만드는 클래스뿐 아니라 JDK에서 제공하는 클래스도 모두 Object 클래스를 상속 받는다. 모든 클래스가 Object 클래스를 상속받았으니 Object의 메서드를 사용할 수 있고, 재정의할 수 있으며 Object형으로 변환할 수도 있다. 자바로 프로그래밍을 하다 보면 클래스가 Object형으로 변환되거나 Object에서 원래 클래스형으로 다운 캐스팅되는 경우도 있다 한다.

주로 사용되는 Object 메서드는 다음과 같다.

Object 메서드 중에는 재정의할 수 있는 메서드도 있고, 그렇지 않은 메서드도 있다. 오늘은 자주 재정의하여 사용하는 메서드를 배운다.

 

toString( ) 메서드

Object 클래스에서 기본으로 제공하는 메서드이며 이름처럼 객체 정보를 문자열(String)로 바꿔 준다. Object 클래스를 상속받은 모든 클래스는 toString( )을 재정의할 수 있다. String이나 Integer 등 여러 JDK 클래스에는 toString( ) 메서드가 이미 재정의되어 있다.

 

Object 클래스의 toString( ) 메서드

toString( ) 메서드는 인스턴스 정보를 문자열로 반환하는 메서드이다. toString( ) 메서드의 원형은 생성된 인스턴스의 클래스 이름과 주소 값을 보여준다. 아래 사진은 책 번호와 제목을 담고 있는 Book 클래스의 인스턴스를 생성하여 그 참조 변수를 출력한다.

출력 결과

17행 System.out.println( ) 출력문에 참조 변수를 넣으면 인스턴스 정보가 출력되는데, 이 때 자동으로 호출되는 메서드가 toString( ) 이다. 여기에서 호출되는 toString( )은 Book 클래스의 메서드가 아닌 Object 클래스의 메서드다. Object 클래스의 toString( ) 메서드 원형은 아래와 같다.

getClass( ).getName( ) + '@' + Integer.toHexString(hashCode( ))

위 내용을 보면 '클래스 이름@해시 코드 값'임을 알 수 있다.

 

String과 Integer 클래스의 toString( ) 메서드

toString( ) 메서드가 호출된 경우라도 출력 결과가 '클래스 이름@해시 코드 값'이 아닌 경우가 있다고 한다. 아래 코드를 살펴보자.

String과 Integer 클래스로 인스턴스를 생성하여 System.out.println( ) 출력문에 참조 변수를 넣으면 '클래스 이름@해시 코드 값'이 아닌 test와 100이 나온다. 그 이유는 String과 Integer 클래스는 toString( ) 메서드를 미리 재정의해 두었기 때문이다. JDK에서 제공하는 클래스 중에는 toString( ) 메서드를 미리 재정의한 클래스가 많다. 이런 클래스들은 '클래스 이름@해시 코드 값'을 출력하는 toString( ) 메서드의 원형이 아닌 재정의된 메서드가 호출되는 것이다.

 

Book 클래스에서 toString( ) 메서드 직접 재정의하기

이번에는 위에서 만든 Book 클래스에서 toString( ) 메서드를 직접 재정의해 보자. Book 클래스의 참조 변수를 사용해 '책 이름, 책 번호'를 출력해 보자.

출력 결과

toString( ) 메서드를 직접 재정의하면 객체의 참조 변수를 이용해 원하는 문자열을 표현할 수 있다.

 

equals( ) 메서드

equals( ) 메서드의 원래 기능은 두 인스턴스의 주소 값을 비교하여 boolean 값(true/false)을 반환해 주는 것이다. 주소 값이 같다면 당연히 같은 인스턴스다. 그런데 서로 다른 주소 값일 때도 같은 인스턴스라고 정의할 수 있는 경우가 있다. 즉 물리적 동일성(인스턴스의 메모리 주소가 같음)뿐 아니라 논리적 동일성(논리적으로 두 인스턴스가 같음)을 구현할 때도 equal s( ) 메서드를 재정의하여 사용한다. 무슨 말인지 자세히 살펴보자.

 

Object 클래스의 equals( ) 메서드

생성된 두 인스턴스가 '같다'는 것은 무엇을 의미할까? 두 인스턴스가 물리적으로 같다는 것은 두 인스턴스의 주소 값이 같은 경우를 말한다. 예로 학생 객체를 구현한 Student 클래스가 있다. 아래처럼 Student 클래스를 생성하고 생성된 인스턴스를 가리키는 참조 변수(student1)를 다른 변수(student2)에 복사한다.

Student student1 = new Student(100, "개발 초보");
Student student2 = student1;

이러면 두 변수는 동일한 인스턴스를 가리킨다. 이 때 equals( ) 메서드를 이용해 두 변수를 비교하면 동일하다는 결과가 나온다.

아래 코드는 이름과 학번이 동일한 학생을 한 명 더 생성하고 다른 변수(student3)가 가리키도록 만들었다.

Student student1 = new Student(100, "개발 초보");
Student student2 = student1;
Student student3 = new Student(100, "개발 초보");

student1, student2가 가리키는 인스턴스와 student3가 가리키는 인스턴스는 서로 다른 주소를 갖고 있지만, 저장된 학생의 정보는 같다. 이런 경우 논리적으로는 student1,student2와 student3를 같은 학생으로 처리하는 것이 맞을 것이다. 이 상황을 구현한 예제를 만들며 살펴보자.

출력 결과

Object의 equals( ) 메서드의 원래 기능은 두 인스턴스의 주소를 비교하는 것이기에 같은 주소인 경우만 equals( ) 메서드의 결과가 true가 된다. 그런데 두 인스턴스의 주소가 같은 경우만 같은 것일까? 인스턴스 주소가 다르다 해도 저장된 학생의 정보가 같으면 사실 같은 학생의 정보이다. 따라서 인스턴스의 주소가 달라도 동일한 객체임을 확인할 수 있어야 한다. 즉 두 인스턴스가 있을 때 == 는 단순히 물리적으로 같은 메모리 주소인지 여부를 확인할 수 있고, Object의 equals( ) 메서드는 재정의를 하여 논리적으로 같은 인스턴스인지 확인하도록 구현할 수 있다.

 

String과 Integer 클래스의 equals( ) 메서드

JDK에서 제공하는 String 클래스와 Integer 클래스에는 equasl( ) 메서드가 이미 재정의되어 있다. 재정의된 equals( ) 메서드의 사용법을 예제를 만들며 살펴보자.

출력 결과

코드의 내용을 보면 str1과 str2는 서로 다른 인스턴스를 가리키기에 str1 == str2의 결과는 false다. 하지만 String 클래스의 equals( ) 메서드는 같은 문자열의 경우 true를, 그렇지 않은 경우 false를 반환하도록 재정의되어 있다. Integer 클래스의 경우도 정수 값이 같은 경우 true를 반환하도록 equals( ) 메서드가 재정의되어 있다.

 

Student 클래스에서 equals( ) 메서드 직접 재정의하기

직접 만든 Student 클래스에서 equals( ) 메서드는 어떻게 재정의할 수 있을까? 학교에서 두 학생이 같다는 논리 정의는 학번이 같은 경우일 것이다. 회사에서는 사번, 은행에서는 계좌번호와 같은 것이다. 따라서 Student 클래스의 equals( ) 메서드를 아래와 같이 재정의할 수 있다.

출력 결과

위 사진에서 equals( ) 메서드를 재정의했다. equals( ) 메서드의 매개변수는 Object형이다. 

 

hashCode( ) 메서드

해시(hash)는 정보를 저장하거나 검색할 때 사용하는 자료 구조다. 정보를 어디에 저장할 것인지, 어디서 가져올 것인지 해시 함수를 사용하여 구현한다. 해시 함수는 객체의 특정 정보(키 값)를 매개변수 값으로 넣으면 그 객체가 저장되어야 할 위치나 저장된 해시 테이블 주소(위치)를 반환한다. 따라서 객체 정보를 알면 해당 객체의 위치를 빠르게 검색할 수 있다. 해시 함수[hash(key)]는 개발하는 프로그램 특성에 따라 다르게 구현한다고 한다.

hashCode = hash(key);

Object 클래스의 toString( ) 메서드 원형을 살펴보면 getClass( ).getName( ) + '@' + Integer.toHexString(hashCode( ))이다. 즉 우리가 참조 변수를 출력할 때 본 16진수 숫자 값이 '해시 코드 값'이고, 이 값은 자바 가상 머신이 힙 메모리에 저장한 '인스턴스의 주소 값'이다. 즉 자바에서는 두 인스턴슥 같다면 hashCode( ) 메서드에서 반환하는 해시 코드 값이 같아야 한다. 따라서 논리적으로 같은 두 객체도 같은 해시 코드 값을 반환하도록 hashCode( ) 메서드를 재정의해야 한다. 한마디로 equals( ) 메서드를 재정의했다면 hashCode( ) 메서드도 재정의해야 한다.

 

String과 Integer 클래스의 hashCode( ) 메서드

String과 Integer 클래스의 equals( ) 메서드는 재정의되어있다고 말했다. 그러면 String과 Integer 클래스의 hashCode( ) 메서드도 재정의되어 있을 것이다. 아래 사진을 보자.

출력 결과

8~9행을 보면 String 클래스는 같은 문자열을 가진 경우, 즉 equals( ) 메서드의 결과 값이 true인 경우 hashCode( ) 메서드는 동일한 해시 코드 값을 반환한다. Integer 클래스의 hashCode( ) 메서드는 정수 값을 반환하도록 재정의되어 있다.

 

Student 클래스에서 hashCode( ) 메서드 재정의하기

위에서 서로 다른 인스턴스로 생성된 두 학생이 논리적으로 같은 학생이라는 의미를 구현하기 위해 equals( ) 메서드를 재정의했다. 논리적으로 동일한 두 학생은 같은 해시 코드 값을 반환하도록 hashCode( ) 메서드도 재정의해 보자.

 

student 클래스에서 hashCode( )를 재정의할 때 어떤 값을 반환하도록 만드는게 가장 합리적일까? 일반적으로 hashCode( ) 메서드를 재정의할 때는 equals( ) 메서드에서 논리적으로 같다는 것을 구현할 때 사용한 멤버 변수를 활용하는 것이 좋다. 따라서 Student 클래스에서는 hashCode( ) 메서드가 학번을 반환하는 것이 가장 합리적이다. 그럼 앞에서 구현한 Student 클래스에 hashCode( )를 재정의하여 추가해보자.

해시 코드 값으로 학번을 반환하도록 메서드 재정의
출력 결과

hashCode( ) 메서드를 재정의했을 때 실제 인스턴스의 주소 값은 System.identityHashCode( ) 메서드를 사용하면 알 수 있다. student1과 student3는 논리적으로는 같지만 실제로는 다른 인스턴스이다.

 

clone( ) 메서드

객체 원본을 유지해 놓고 복사본을 사용한다거나, 기본 틀(prototype)의 복사본을 사용해 동일한 인스턴스를 만들어 복잡한 생성과정을 간단히 하려는 경우에 clone( ) 메서드를 사용할 수 있다. clone( ) 메서드는 Object에 아래와 같이 선언되어 있으며, 객체를 복제해 또 다른 객체를 반환해 주는 메서드이다.

protected Object clone( );

예제를 통해 객체가 복제되는 과정을 구현해 보자. 다음 예제는 하나의 원점과 반지름을 멤버 변수로 갖는 Circle 클래스의 인스턴스를 생성하고 이를 clone( ) 메서드를 사용하여 복제하는 프로그램이다. 원점은 Point 클래스를 사용했다.

출력 결과

clone( ) 메서드를 사용하려면 객체를 복제해도 된다는 의미로 클래스에 Cloneable 인터페이스를 구현해야 한다. 이 예제의 clone( ) 메서드는 Object의 clone( ) 메서드를 그대로 사용하고 있다. Object의 clone( ) 메서드는 클래스의 인스턴스를 새로 복제해여 생성해 준다. 멤버 변수가 동일한 인스턴스가 다른 메모리에 새로 생성되는 것이다. 출력 결과를 보면 인스턴스의 멤버 변수 값은 같고 주소 값은 다른 copyCircle이 생성되었다.

 

참고 서적: 자바 프로그래밍 입문 - 박은종