IT_Programming/Java

사용자 정의 클래스와 함께 printf 사용하기

JJun ™ 2007. 6. 28. 12:14
Java SE 1.5에서는 소수 및 새 행을 인쇄하기 위해 "%5.2f%n" 같은 서식 설정 문자열을 사용하여

출력 서식 설정 기능을 추가했다. 새로운 Formatter를 사용하여 출력 서식 설정하기라는 제목의

2004년 10월 팁에서 이에 대해 설명했다.

 

Formattable 인터페이스는 중요한 기능이지만 이전 팁에서의 주제가 아니었다. 이 인터페이스는 java.util 패키지에 있다. 사용자 클래스가 Formattable 인터페이스를 구현하는 경우 Formatter 클래스는 이를 사용하여 출력 서식을 사용자 정의할 수 있다. 이제 사용자 클래스는 toString()에 의해 인쇄되는 내용으로 제한되지 않는다. Formattable의 formatTo() 메소드를 구현하면 사용자 정의 클래스가 출력을 특정 너비 또는 정밀도로 제한할 수도 있고, 컨텐츠의 왼쪽 또는 오른쪽 맞춤을 수행할 수도 있으며, 미리 정의된 시스템 데이터 유형에 대한 지원 등과 같이 각 로케일에 대해 각기 다른 출력을 제공할 수도 있다.

 

Formattable의 단일 formatTo() 메소드는 다음과 같은 네 가지 인수를 가진다.

public void formatTo(Formatter formatter, int flags, int width, int precision)

포매터 인수는 로케일을 가져와서 완료 시 출력을 전송할 Formatter를 나타낸다.

플래그 매개변수는 FormattableFlags 세트의 비트마스크이다. 사용자는 왼쪽 맞춤(LEFT_JUSTIFY)을 지정하려는 경우 - 플래그를, 로케일 관련 대문자(UPPERCASE)를 사용할 경우 ^ 플래그를, 대체(ALTERNATE) 서식 설정을 사용할 경우 # 플래그를 사용할 수 있다.

 

너비 매개변수는 표시된 값이 너무 짧을 경우 출력을 채우기 위해 공백을 사용하는 최소 출력 너비를 나타낸다. 너비 값이 -1이면 최소값이 없음을 의미한다. 이 플래그가 설정된 경우 출력이 너무 짧으면 왼쪽 맞춤으로 출력된다. 그렇지 않은 경우 오른쪽 맞춤으로 출력된다.

정밀도 매개변수는 출력할 문자의 최대수를 지정한다. 출력 문자열이 "1234567890"이고 정밀도가 5, 너비가 10이면 처음 다섯 문자가 표시되고 나머지 다섯 문자 위치는 공백으로 채워서 너비 10의 문자열을 정의한다. 정밀도가 -1이면 한도가 없음을 의미한다.

 

너비 또는 정밀도가 -1이면 해당 설정에 대한 서식 설정 문자열에서 지정된 값이 없음을 의미한다.

printf 및 Formatter와 함께 사용될 클래스를 생성하는 경우에는 formatTo() 메소드를 직접 호출하지 않는다. 대신 인터페이스를 구현하기만 하면 된다. 그러면 사용자 클래스가 printf와 함께 사용될 때 Formatter가 사용자 클래스에 대해 formatTo()를 호출하여 해당 값의 표시 방법을 알아낸다. 예를 들어, Formattable을 구현하는 긴 이름과 짧은 이름을 모두 가진 일부 개체를 생성해 보자. 다음은 클래스 정의의 시작 부분을 나타낸다. 이 클래스에는 두 개의 등록 정보만 있다. 비어 있는 Formattable 구현과 해당 toString() 메소드가 있다.

 

import java.util.Formattable;
import java.util.Formatter;
public class SomeObject implements Formattable {
    private String shortName;
    private String longName;
    public SomeObject(String shortName, String longName) {
        this.shortName = shortName;
        this.longName = longName;
    }
    public String getShortName() {
        return shortName;
    }
    public void setShortName(String shortName) {
        this.shortName = shortName;
    }
    public String getLongName() {
        return longName;
    }
    public void setLongName(String longName) {
        this.longName = longName;
    }
    public void formatTo(Formatter formatter, int flags,
        int width, int precision) {
    }
    public String toString() {
        return longName + " [" + shortName + "]";
    }
}

이제 println()을 사용하여 개체를 인쇄하면 toString() 메소드에 정의된 각괄호 안의 짧은 이름이 긴 이름 뒤에 표시된다. Formattable 인터페이스를 사용하면 출력을 향상시킬 수 있다. 더 나은 출력은 현재 등록 정보 값과 포매터블 플래그를 사용한다. 이 예제의 경우, formatTo()FormattableFlags의 ALTERNATE 및 LEFT_JUSTIFY 플래그를 지원한다.

 

formatTo()에서 먼저 할 일은 출력 대상을 찾는 것이다. SomeObject의 경우, 긴 이름은 표시할 기본값이 되고 짧은 이름은 정밀도가 7 미만이거나 ALTERNATE 플래그가 설정된 경우 사용된다. ALTERNATE 플래그 설정 여부를 확인하려면 일반적인 비트 플래그 검사가 필요하다. 정밀도 값이 -1이면 한도가 없음을 나타내므로 주의한다. 후자의 경우 범위를 확인한다. 그리고 설정을 기반으로 시작 문자열을 선택한다.

 

String name = longName;
boolean alternate =
    (flags & FormattableFlags.ALTERNATE) == FormattableFlags.ALTERNATE;
alternate |= (precision >= 0 && precision < 7);
String out = (alternate ? shortName : name);

시작 문자열을 선택했으면 전달된 정밀도를 기반으로 필요에 따라 해당 문자열을 줄인다. 정밀도가 무제한이거나 문자열이 딱 맞으면 출력에 사용한다. 문자열이 맞지 않으면 알맞게 줄여야 한다. 일반적으로 문자열이 크기에 맞지 않으면 마지막 문자는 이 예제와 같이 *로 대체된다.

 

StringBuilder sb = new StringBuilder();
if (precision == -1 || out.length() <= precision) {
    sb.append(out);
} else {
    sb.append(out.substring(0, precision - 1)).append('*');
}

로 케일 설정의 액세스 방법을 보여 주기 위해 이 예제에서는 중국어에 대한 출력 문자열을 반대로 해 본다. 보다 일반적으로, 번역된 시작 문자열은 로케일을 기반으로 사용된다. 숫자 출력의 경우, 로케일은 십진수 및 쉼표가 숫자에서 나타나는 방식을 정의한다.

 

if (formatter.locale().equals(Locale.CHINESE)) {
    sb.reverse();
}

이제 출력 문자열이 StringBuilder 버퍼 내에 있으므로 원하는 너비와 맞춤 설정을 기반으로 출력 버퍼를 채울 수 있다. 원하는 너비 내에서 사용 가능한 각 위치에 대해 맞춤 포매터블 플래그를 기반으로 시작과 끝에 공백을 추가한다.

 

int len = sb.length();
if (len < width) {
    boolean leftJustified = (flags & FormattableFlags.LEFT_JUSTIFY)
        == FormattableFlags.LEFT_JUSTIFY;
    for (int i = 0; i < width - len; i++) {
        if (leftJustified) {
            sb.append(' ');
        } else {
            sb.insert(0, ' ');
        }
    }
}

마지막으로 할 일은 출력 버퍼를 Formatter에 보내는 것이다. 전체 String을 포매터의 format() 메소드에 보내면 된다.

 

formatter.format(sb.toString());

아래와 같이 일부 테스트 케이스에 추가하여 전체 클래스 정의를 살펴볼 수 있다.

 

import java.util.Formattable;
import java.util.FormattableFlags;
import java.util.Formatter;
import java.util.Locale;
public class SomeObject implements Formattable {
    private String shortName;
    private String longName;
    public SomeObject(String shortName, String longName) {
        this.shortName = shortName;
        this.longName = longName;
    }
    public String getShortName() {
        return shortName;
    }
    public void setShortName(String shortName) {
        this.shortName = shortName;
    }
    public String getLongName() {
        return longName;
    }
    public void setLongName(String longName) {
        this.longName = longName;
    }
    public void formatTo(Formatter formatter, int flags,
            int width, int precision) {
        StringBuilder sb = new StringBuilder();
        String name = longName;
        boolean alternate = (flags & FormattableFlags.ALTERNATE)
            == FormattableFlags.ALTERNATE;
        alternate |= (precision >= 0 && precision < 7); //
        String out = (alternate ? shortName : name);
        // Setup output string length based on precision
        if (precision == -1 || out.length() <= precision) {
            sb.append(out);
        } else {
            sb.append(out.substring(0, precision - 1)).append('*');
        }
        if (formatter.locale().equals(Locale.CHINESE)) {
            sb.reverse();
        }
        // Setup output justification
        int len = sb.length();
        if (len < width) {
            boolean leftJustified =
                    (flags & FormattableFlags.LEFT_JUSTIFY) ==
                    FormattableFlags.LEFT_JUSTIFY;
            for (int i = 0; i < width - len; i++) {
                if (leftJustified) {
                    sb.append(' ');
                } else {
                    sb.insert(0, ' ');
                }
            }
        }
        formatter.format(sb.toString());
    }
    public String toString() {
        return longName + " [" + shortName + "]";
    }
    public static void main(String args[]) {
        SomeObject obj = new SomeObject("Short", "Somewhat longer name");
        System.out.printf(">%s<%n", obj);
        System.out.println(obj); // Regular obj.toString() call
        System.out.printf(">%#s<%n", obj);
        System.out.printf(">%.5s<%n", obj);
        System.out.printf(">%.8s<%n", obj);
        System.out.printf(">%-25s<%n", obj);
        System.out.printf(">%15.10s<%n", obj);
        System.out.printf(Locale.CHINESE, ">%15.10s<%n", obj);
    }
}

이 프로그램을 실행하면 다음과 같은 출력이 생성된다.

>Somewhat longer name<
Somewhat longer name [Short]
>Short<
>Short<
>Somewha*<
>Somewhat longer name     <
>     Somewhat *<
>     * tahwemoS<

 

이 테스트 프로그램은 짧은 이름 "Short"와 긴 이름 "Somewhat longer name"을 사용하여 codeSomeObject를 생성한다. 여기서 첫 번째 행은 %s 설정을 사용하여 개체의 긴 이름을 인쇄한다. 두 번째 행은 보다 일반적인 toString()을 통해 개체를 출력한다. 세 번째 행은 대체 양식을 사용한다. 다음 행은 대체 축약형을 명시적으로 요청하지 않지만 정밀도가 너무 작으므로 어쨋든 표시한다. 다음 행에서는 대체 형식을 사용하지 않을 만큼 긴 정밀도가 지정되지만 전체 긴 이름을 표시하기에는 너무 짧다. 따라서 "*"는 더 많은 문자가 있음을 나타낸다. 다음 행에서는 더 긴 이름이 왼쪽 맞춤으로 표시된다. 마지막 두 행은 너비가 정밀도보다 넓을 때 어떻게 되는지를 보여주고 문자열의 역전된 "중국어" 버전을 역시 보여준다.

 

여기에는 사용자 클래스가 printf와 함께 작동되도록 하기 위한 모든 항목이 있다. 항목을 표시하려는 경우 서식 설정 문자열 내에 적절하게 구성된 %s 설정을 사용해야 한다.

printf 사용에 대한 의문사항이 아직 있으면 이 팁의 시작 부분에서 언급한 이전 팁(새로운 Formatter를 사용하여 출력 서식 설정하기)을 참조하기 바란다.

Formattable 인터페이스에 대한 자세한 내용은 인터페이스에 대한 문서를 참조하기 바란다.