티스토리 뷰

자바에서 클래스 객체를 사용하면서 접하게 되는 메소드로 toString이 있다. 이것도 앞에서 살펴본 equals 처럼 Object 클래스에 정의되어있는 것을 오버라이드하는 메소드다. 

toString()의 역할을 이해하기 위해서는 먼저 자바에서 변수나 값의 타입변환 규칙을 살펴보아야 한다. 프로그램은 데이터를 읽어서 그것을 int, float, String 등의 타입으로 메모리에 저장한다. 그런데 프로그램에서 +나 == 같은 연산을 수행할 때는 두 피연산자를 같은 타입으로 만들어서 계산해야 한다. 다음 예는 이러한 타입변환이 자동으로 일어나는 예를 보여준다.

int n = 10;
double a = n;    // n을 double으로 변환하여 a에 지정
long b = n;  // n을 long으로 변환하여 b에 지정
double c = n/a;  // n이 double로 변환된 후 / 연산을 수행
String str = "["+a+", "+b+"]";   // a와 b를 스트링으로 변환하여 +함
if (a < n) ...   // n을 double로 바꾸어 비교함

한편 어떤 경우에는 타입변환을 사용자가 명시적으로 해 주어야 하는 경우도 있다. 

n = (int)c;
String number = (String)n;   
if (kwd.equals((String)n)) ...
a = 2 * Integer.parseInt(number);

위에서 살펴본 (String)n 은 정수 n을 스트링 변수에 지정하거나 스트링 kwd 값과 비교하기 위해 스트링으로 바꾸는 형변환의 예이다. (스트링끼리여야 비교 가능하므로) 마찬가지로 Integer.parseInt(kwd)는 거꾸로 스트링을 정수로 바꾸는 메소드이다. 이처럼 우리는 필요에 따라 타입을 변환해서 사용해야 하는 경우가 많이 있다. 

그럼 어떤 경우는 자동으로 형변환 되고 어떤 경우는 프로그래머가 강제로 형변환하는 코드를 써주어야 할까? 숫자 간에는 int에서 double로, 또는 int에서 long으로 변환은 자동으로 가능하다. 왜냐하면 더 큰 정밀도(메모리크기)을 나타내는 타입으로 변환은 자동으로 해주어도 값의 손실이 일어나지 않기 때문이다. 반면에 double에서 int로 변환은 자동으로 되지 않는다. 소수점 이하 값이 손실되므로 컴파일러가 책임질 수 없는 일이어서 오류를 일으킨다. 프로그래머가 명시적으로 소수점 이하를 버리고 변환하라고 해야 변환할 수 있다. 

int n;
double dval = 3.14159;
n = (int)dval;

한편 숫자 타입(int, long, float, double 등)과 스트링의 관계는 좀더 복잡하다. 위 예에서 본 것처럼 지정이나 == 같은 비교에서는 숫자와 문자열 사이에 타입이 자동 변환되지 않는다. 그러나 다음 두 경우에 숫자 타입이 스트링으로 변환되는 것을 볼 수 있다. 

  • 숫자 타입의 변수나 값은 System.out.print 문 내에서는 자동으로 스트링으로 바뀐다. 
  • 스트링과의 + 연산을 만나면 자동으로 스트링으로 변환된다. 이러한 경우는 컴파일러가 자동형변환으로 문자열로 바꾸어준다. 

이것은 다른 클래스 객체에 대해서도 동일하다. 모든 객체 타입의 변수는 System.out.print 문의 매개변수로 전달되면 스트링으로 자동변환되며, 스트링과의 + 연산을 적용해도 마찬가지로 자동변환된다. 예를 들어 Random 객체를 "랜덤: " + rand 처럼 스트링에 더해 보자. 결과 스트링은 다음과 같다.

랜덤: java.util.Random@1b6d3586

여기서 찍히는 문자열은 rand 객체의 클래스 이름과 참조값을 @로 연결한 문자열이다. 이것이 Random 객체가 자동으로 스트링으로 변환되는 방법이다.

왜 이런 결과가 출력되는 것일까? 이것은 사실 Object 클래스의 toString()이 하는 일이다. 객체를 자동으로 스트링으로 변환해야 할 때 컴파일러는 그 클래스의 toString() 메소드를 이용한다. Random 클래스가 toString()을 따로 정의하지 않았다면 Object에 정의된 toString()이 불려질 것이다. 다음은 실제 Object의 toString() 메소드의 코드이다.

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

이제 @ 뒤에 있는 숫자값의 비밀이 풀렸다. 객체의 위치를 나타내는 고유한 해시코드를 16진수로 표시한 것이다. (이것은 C에서 포인터를 그냥 %p나 %x로 출력했을 때 주소값이 찍히는 것과 유사하다. 단 여기서 해시코드는 주소값은 아니고 객체를 나타내는 고유한 값이다.) 

그럼 이제 toString()을 좀더 유용하게 쓰는 방법을 살펴보자. 

Student st = ...;
System.out.println("대표학생 : " + st);

여기서 st가 Student@12a938bf0 이런 값을 찍는다면 참 불편할 것이다. 물론 그 자리에 st.name이라고 써 줄 수도 있다. 그러나 이름과 학과를 같이 쓰고 싶다면? 또 두 학생의 이름과 학과를 다음과 같이 출력하고 싶다면?

Student st1 = ...;
Student st2 = ...;
System.out.printf("대표 : %s(%s), 부대표: %s(%s)\n" + st1.name, st1.dept, st2.name, st2.dept);

이름과 학과를 이런 식으로 출력해야 되는 일이 자주 생긴다면 이건 좋은 방법이 아니다. 이런 경우 우리는 다음과 같이 toString()을 이용할 수 있다.

System.out.printf("대표 : %s, 부대표: %s\n", st1.toString(), st2.toString());

이제 여기서 toString()의 역할이 빛을 발하게 된다. 굳이 toString()이라고 써주지 않아도 다음 코드에서 컴파일러가 자동으로 toString()을 호출하여 학생을 스트링으로 바꾸어 준다.

System.out.printf("대표 : %s, 부대표: %s\n", st1, st2);

위와 같이 동작하게 toString을 정의해 보자. 앞의 글에서 equals를 정의할 때와 같이 오버라이드 표시를 해주고 Object 클래스의 toString과 같은 시그너처를 가지도록 정의해 주어야 한다.

@Override
public String toString() {
    return name+"("+dept+")";
}

객체를 간단한 문자열로 바꾸어 출력해야 하는 일은 의외로 많다. 그럴 때 toString()에 정의해 둔 대로 출력 형태가 결정되므로 객체를 어떻게 나타낼지를 클래스 작성자가 정해줄 수 있다. 그리고 컴파일러는 객체를 스트링으로 자동변환할 때마다 클래스가 가진 toString() 메소드를 호출하여 실행해 준다. (가상함수 호출 방식에 의해 오버라이드된 메소드가 있으면 그것을 부르고 없으면 조상 클래스가 가진 toString()을 찾아서 호출한다)

또 하나의 흥미로운 예로 다음의 코드를 테스트해 보자.

ArrayList<String> strList = new ArrayList<>();
...
System.out.println(strList);
// 출력 결과 : 
// [abc, ABC, 123, ..., 가나다]

ArrayList 클래스가 정의한 toString에 의해 위와 같이 배열 형태로 원소들을 차례로 출력해 준다. 이 때 각 원소의 toString이 불려졌음은 짐작할 수 있을 것이다.

객체를 콘솔 창에 출력하거나 객체의 정보를 문자열로 웹페이지에 보여주거나 팝업 창에 문자열로 보여주는 등의 일에서 toString()은 매우 유용하게 쓰인다. 그러므로 새로 정의한 모든 클래스에 대해 toString()을 잘 정의해 두는 것은 코드를 한 단계 업그레이드시키는 팁이다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함