Formatter(2)

de_sj_awa·2021년 5월 6일
0

5. SimpleDateFormat 클래스

날짜나 시간을 정해져 있는 스타일만 사용하는 것이 싫거나, 프로그램 개발을 요청한 분이 원하는 스타일로 표시하려면 DateFormat만으로는 한계가 있다. 그래서 abstract 클래스인 DateFormat을 확장한 SimpleDateFormat 클래스가 제공되어 있다.

이 클래스에서는 원하는 대로 날짜와 시간을 표시하기 위해 미리 정해져 있는 문자가 있다.

문자 대상 컴포넌트 표시 방법
G 서기 텍스트 AD
y 년도 1996; 96
T Week year 2009; 09
M July; Jul; 09
w 1년 중 몇 번째 주 숫자 27
W 1달 중 몇 번째 주 숫자 2
D 1년 중 몇 번째 일 숫자 189
d 1달 중 몇 번째 일 숫자 10
F 1주 중 몇 번째 일 숫자 2
E 요일 텍스트 Tuesday; Tue
u 요일 숫자 (월요일=1, 일요일=7) 숫자 1
a 오전/오후 표시 텍스트 PM
H 1일 중 0시부터의 시간(0~23) 숫자 0
k 1일 중 1시부터의 시간(1~24) 숫자 24
K 오전 오후로 나눈 시간(0~11) 숫자 0
h 오전 오후로 나눈 시간(1~12) 숫자 12
m 숫자 30
s 숫자 55
S 밀리초 숫자 978
z 타임존 일반적인 타임존 Pacific Standard Time; PST; GMT -08:00
Z 타임존 RFC 822 타임존 -0800
X 타임존 ISO 8601 타임존 -08; -0800; -08:00

이 표현 문자들을 사용하여 "연/월/일 시:분"을 나타내려면 다음과 같이 묶어서 사용하면 된다.

yyyy/MM/dd hh:mm

보통 이와 같이 연, 월, 일, 시, 분을 나타내는 것이 일반적이기 때문에 여기서 사용한 문자들만 기억하고 있어도 웬만한 시간 표현은 가능하다. 앞의 예제에 다음과 같이 checkSimpleDateFormat()와 printDateWithPattern() 메소드를 추가하자.

public void checkSimpleDateFormat(){
        Date date = new Date(1328054400000L);
        String pattern = "yyyy/MM/dd hh:mm";
        printDateWithPattern(pattern, date);
    }
    public void printDateWithPattern(String pattern, Date date){
        SimpleDateFormat formatter = new SimpleDateFormat();
        formatter.applyPattern(pattern);
        System.out.println(pattern+" => " + formatter.format(date));
    }

위의 예제와 크게 다른 것은 없다. 단지 formatter에 applyPattern()이라는 메소드를 사용하여 패턴을 지정한 것만 다를 뿐이다.

yyyy/MM/dd hh:mm => 2012/02/01 09:00

이번에는 checkSimpleDateFormat() 메소드의 끝에 다음의 두 줄을 추가해보자.

pattern = "MMM d일 EEE";
printDateWithPattern(pattern, date);

이와 같이 MM이 아닌 MMM을 사용하면 자동으로 "월"이 붙는다. 만약 Locale이 US와 같이 영어를 사용하면 "Feb"로, 요일은 "Wed"로 출력될 것이다. 만약 Locale을 지정하려면 생성자 중 Locale을 지정하는 것을 사용하면 된다.

6. MessageFormat

자바에서 Format된 문자열을 처리하는 데 사용되는 클래스는 크게 두 가지가 있다. 하나는 java.Text 패키지의 MessageFormat이라는 클래스이며 다른 하나는 java.util 패키지의 Formatter라는 클래스다.

많은 사람들이 쇼핑몰에서 물건을 구매해 봤을 것이다. 구매한 후에는 친절하게 어떤 것을 질렀고 몇일 내에 배송이 된다는 메일을 받을 것이다. 예를 들면 다음과 같이 말이다.

이상민님 구매해 주셔서 감사합니다.
구매하신 "완전 비싼 카메라"는 3일내내 배송될 것입니다.
궁금하신 사항은 god@godOfjava.com으로 문의하시기 바랍니다.

이 절에서 사용할 예제 클래스를 먼저 다음과 같이 만들자.

package d.format;

public class MessageFormatSample {
    
    public static void main(String[] args){
        MessageFormatSample sample = new MessageFormatSample();
    }
}

이제 메세지를 작성하는 메소드를 만들어보자. 만약 MessageFormat을 알기 전에는 다음과 같이 구현할 것이다.

public void stringSample(String customer, String product, int days){
        String message = customer+"님 구매해 주셔서 감사합니다. \n구매하신 \""
                +product
                +"\"는 "
                +days
                +"일내에 배송될 것입니다. \n"
                +"궁금하신 사항은 god@godofjava.com으로 문의하시기 바랍니다.";
        System.out.println(message);
    }

그리고, StringBuilder라는 클래스를 알고, 자바의 성능을 조금이라도 생각한다면 다음과 같이 구현할 수도 있다.

public void stringBuilderSample(String customer, String product, int days){
        StringBuilder message = new StringBuilder();
        message.append(customer);
        message.append("님 구매해주셔서 감사합니다. \n 구매하신 \"");
        message.append(product);
        message.append("\"는 ");
        message.append(days);
        message.append("일내에 배송될 것입니다. \n");
        message.append("궁금하신 사항은 god@godofjava.com으로 문의하시기 바랍니다.");
        System.out.println(message);
    }

물론 JDK에서 자동으로 앞서 만든 String으로 작성한 부분을 StringBuilder로 변환해주기는 하지만, 두 번째 메소드처럼 작성하는 습관을 갖는 것이 좋다. 하지만, 어떤 메시지를 전달해 주기 위해서는 이렇게 문자열을 더하는 것보다 더 편한 방법이 있다. 다음의 메소드를 보자.

    public void messageFormatSample(String customer, String product, int days){
        String format="{0}님 구매해주셔서 감사합니다.\n"
                +"구매하신 \"{1}\"는 {2}일내에배송될 것입니다. \n"
                +"궁금하신 사항은 god@godOfJava.com으로 문의하시기 바랍니다.";
        String result = MessageFormat.format(format, customer, product, days);
        System.out.println(result);
    }

MessageFormat은 지금까지 본 각종 Format 클래스의 집합체라고 보면 된다. 지금까지는 단순히 숫자, 날짜, 시간 표현을 하나의 문자열로 리턴해주는 클래스를 사용했지만, 단 하나의 문자열로 문자열, 숫자, 날짜, 시간을 리턴해주는 클래스가 바로 MessageFormat이다.

위의 예제에서 보면 알겠지만, 중괄호 안에 치환하고자 하는 값의 위치를 지정해 주면 된다. 여기서 위치는 format 문자열을 제외한 매개 변수의 순서를 의미한다. 이 위치 값은 배열처럼 0부터 시작하기 때문에, 두 번째 매개 변수의 MessageFormat 위치가 0이 된다.

그러면 다른 예제를 살펴보자.

이상민님이 구매하신 총액은 120,585 원입니다.

MessageFormat 클래스를 사용해서 직접 메소드를 작성해보자.

    public void totalPriceMessage(String customer, int price){
        String format = "{0}님이 구매하신 총액은 {1} 원입니다.";
        String result = MessageFormat.format(format, customer, price);
        System.out.println(result);
    }

실행 결과는 다음과 같다.

이상민님이 구매하신 총액은 120,585 원입니다.

자동으로 1000 단위 콤마가 찍혀서 나오는 것을 알 수 있다. 그런데, 만약 소수점 두자리까지 출력해달라는 요구사항이 나오면 어떻게 해야 할까?

MessageFormat은 위의 예시처럼 String만이 아니라 숫자, 날짜, 시간 등을 지정할 수 있다. 특히 날짜 및 시간은 java.util 패키지의 Date 클래스 객체를 사용하면 된다. 일단 소수점 두자리까지 표현하는 것을 살펴보자. totalPriceMessage() 메소드의 첫 줄을 보자.

public void totalPriceMessage(String customer, int price){
        String format = "{0}님이 구매하신 총액은 {1, number, #,###.00} 원입니다.";
        String result = MessageFormat.format(format, customer, price);
        System.out.println(result);
    }

변경한 다음에 실행한 결과는 다음과 같다.

이상민님이 구매하신 총액은  120,585.00 원입니다.

이처럼 중괄호 안에 위치만이 아니라, 이미 지정되어 있는 각종 값을 지정하면 이처럼 보다 복합적인 메시지를 만들 수 있다. 여기서 중괄호 안에 첫 번째 값은 위치, 두 번째 값은 타입, 세 번째 값은 스타일을 의미한다. MessageFormat 클래스에서 사용할 수 있는 타입과 스타일은 다음과 같다.

Format 타입 Format 스타일 자동 생성되는 Format 객체
미지정 미지정 null
number 미지정 NumberFormat.getInstance(getLocale())
number integer NumberFormat.getIntegerInstance(getLocale())
number currency NumberFormat.getCurrencyInstance(getLocale())
number percent NumberFormat.getPercentInstance(getLocale())
number 패턴 new DecimalFormat(subformatPattern, DecimalFormatSymbols.getInstance(getLocale()))
date 미지정 DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())
date short DateFormat.getDateInstance(DateFormat.SHORT, getLocale())
date medium DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())
date long DateFormat.getDateInstance(DateFormat.LONE, getLocale())
date full DateFormat.getDateInstance(DateFormat.FULL, getLocale())
time 미지정 DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())
time short DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())
time medium DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())
time long DateFormat.getTimeInstance(DateFormat.LONG, getLocale())
time full DateFormat.getTimeInstance(DateFormat.FULL, getLocale())
time 패턴 new SimpleDateFormat(subformatPattern, getLocale())
choice 패턴 new ChoiceFormat(subformatPattern)

7. Formatter 클래스

MessageFormat 클래스도 어떻게 보면 편리하지만, 만약 C와 같은 언어의 Format에 익숙하다면 java.util 패키지의 Formatter 클래스를 사용하는 것이 편할 것이다. 그런데 C에서는 printf()와 같은 메소드에서 format을 할 때 형식에 맞지 않으면 무시하도록 되어 있지만, 자바에서는 이 Formatter 클래스를 사용할 때 데이터가 맞지 않으면 예외를 발생시키도록 되어 있다는 점이 다르니 유의해야 한다.

Formatter의 기본적인 사용은 다음과 같다. Formatter 클래스를 사용하여 문자열을 생성할 수도 있지만, 보통은 다음과 같이 String 클래스의 format() 메소드를 사용하는 것이 편리하다. 만약 콘솔에 출력할 것이라면 System.out.format() 메소드를 사용해도 무방하다.

String str = String.format("%s, %s, %s", "a", "b", "c");

기본적인 사용법은 MessageFormat 클래스를 사용하는 것과 비슷하다. format() 메소드의 가장 첫 매개 변수에 원하는 format을 명시한다. 그리고, 두 번째 매개변수부터는 출력하고자 하는 데이터를 명시한다. 두 번째 매개 변수의 개수부터는 제한이 없다. 이렇게 보면 Formatter 클래스에서 제공하는 Format은 중괄호가 아닌 % 뒤에 대상을 지정하는 것만 다르므로, 이해하는데 그렇게 어렵지는 않을 것이다. 하지만, 조금만 더 깊게 들어가면 매우 상세한 기능을 제공한다는 것을 알 수 있다.

먼저 Formatter의 매개 변수를 선정하는 3가지 방법을 알아보자.

  1. 절대적인 위치 사용
  2. 상대적인 위치 사용
  3. 순서에 따른 위치 사용

보통의 경우 3번의 방법을 많이 사용하지만, MessageFormat처럼 절대적인 위치를 지정할 수도 있다. 각각에 대해서 간단히 살펴보자.

절대적인 위치 사용

매개 변수의 절대적인 위치를 사용하며 그 위치는 1부터 시작한다. 0부터 시작한느 것이 아니니 유의하자.

상대적인 위치 사용

매개 변수의 상대적인 위치를 사용하며 앞에서 사용한 위치를 재활용한다는 의미로 '<'를 사용하면 된다.

순서에 따른 위치 사용

매개 변수의 나열된 순서를 별도의 위치를 지정하지 않고 사용한다.

다음의 예제를 살펴보자.

package d.format;

public class StringFormatterSample {

    public static void main(String[] args){
        StringFormatterSample sample = new StringFormatterSample();
        sample.kindSample();
    }
    public void kindSample(){
        String explicitIndexing = String.format("%1$s %2$s %3$s", "A", "B", "C");
        System.out.println(explicitIndexing);
        String relativeIndexing = String.format("%s %<s %s %s", "A", "B", "C");
        System.out.println(relativeIndexing);
        String ordinayIndexing = String.format("%s %s %s", "A", "B", "C");
        System.out.println(ordinayIndexing);
    }
}

kindSample() 메소드를 보자.

  1. explicitIndexing의 format을 보면 %1, %2와 같이 뒤에 있는 매개 변수들의 순서를 명시한 것을 볼 수 있다. 이렇게 % 뒤에 숫자가 있는 것은 해당 값의 매개 변수 순서를 의미한다. $ 뒤에 s라고 명시한 것은 문자열을 출력한다는 말이다.
  2. relativeIndexing의 첫번째 format은 %s이지만, 두 번째에 있는 format은 %<s로 지정되어 있다. 즉, 여기서는 첫 번째 값의 위치를 그대로 참조한다는 말이다.
  3. ordinaryIndexing의 %s 외에는 아무런 특별한 값이 없다. 이처럼 % 뒤에 숫자가 없이 반환 문자만 있을 때에는 매개 변수의 순서대로 출력한다.

실행 결과는 다음과 같다.

A B C
A A B C
A B C

예제에 있는 것처럼 Formatter 클래스에서 제공하는 하나의 항목을 나타내기 위한 메시지의 형식은 3개가 있다. 각각에 대해서 살펴보자.

일반적인 문자열과 숫자 형식

%[argument_index$][flags][width][.precision]conversion

여기서 대괄호 안에 있는 값들은 선택적으로 사용 가능하다는 것이다. 즉, 여기서 conversion만 반드시 명시해 주어야 하며, 나머지는 모두 선택적이다. 지금까지 %s에서 사용한 s가 바로 conversion이다. 그리고, argument_index를 쓸 경우에는 반드시 그 값 뒤에 $를 적어주어야 한다. 각 항목의 의미는 다음과 같다.

  • argument_index : 매개 변수 목록의 위치를 의미한다.
  • flags : 출력 format을 명시적으로 지정할 때 사용한다. 이 flags 값은 가장 끝에 지정하는 conversion의 값에 따라 미리 지정되어 있다.
  • width : 출력되는 값의 최소 길이를 리턴한다. 만약 2라고 명시를 해주었는데, 1개 char로 된 결과가 출력된다면 그 앞에는 공백이 출력된다.
  • precision : 소수점 이하 값의 길이를 지정한다.
  • conversion : 매개 변수가 어떤 형태로 출력되는지를 나타낸다.

날짜와 시간 형식

%[argument_index$][flags][width]conversion

날짜와 시간 형식에 포함되는 형식은 이와 같다. 날짜와 시간은 소수점이 없으므로, 이처럼 argument_index, flags, width, conversion으로 구성하면 된다.

그 외에 기타 간단한 형식

%[flags][width]conversion

간단하게 나타낼 때에는 flags, width, conversion 만으로 나타낼 수 있다.

8. Formatter의 변환 대상들

Formatter에서 사용할 수 있는 conversion에는 다음과 같은 타입들이 존재한다.

conversion 대상
b Boolean이나 boolean 타입
s 문자열(String) 타입
c 유니 코드 character
d 정수형 10진수
o 정수형 8진수
x 정수형 16진수
e 소수형 e의 배수형 표시
g 일반적인 소수형 표시
f 지역 정보를 반영한 소수형 표시
a 16진수 소수형 표시
% %
n 줄 바꿈

다음의 예제를 살펴보자. 앞서 사용한 StringFormatterSample 클래스에 다음의 메소드를 추가하자.

package d.format;

public class StringFormatterSample {

    public static void main(String[] args){
        StringFormatterSample sample = new StringFormatterSample();
        sample.kindSample();
    }
    public void kindSample(){
        String explicitIndexing = String.format("%1$s %2$s %3$s", "A", "B", "C");
        System.out.println(explicitIndexing);
        String relativeIndexing = String.format("%s %<s %s %s", "A", "B", "C");
        System.out.println(relativeIndexing);
        String ordinayIndexing = String.format("%s %s %s", "A", "B", "C");
        System.out.println(ordinayIndexing);
    }
    public void checkConversions(){
        System.out.format("This is b : %b.\n", false);
        System.out.format("This is h : %h.\n", "A");
        System.out.format("This is s : %s.\n", "String");

        System.out.format("This is c : %c.\n", 255);
        System.out.format("This is o : %o.\n", 256);
        System.out.format("This is x : %x.\n", 255);

        System.out.format("This is e : %e.\n", 3.1415927);
        System.out.format("This is g : %g.\n", 3.1415927);
        System.out.format("This is f : %f.\n", 3.1415927);
        System.out.format("This is a : %a.\n", 3.1415927);

        System.out.format("This is n : %n.\n");
        System.out.format("This is percent : %%.\n");
    }
}

위의 예제에서는 String.format()을 사용했지만, 이처럼 System.out.format() 메소드를 사용해도 결과는 동일하다. 참고로 여기서 System.out.format() 메소드를 사용할 때에는 반드시 \n을 써 줘야만 줄바꿈이 된 다는 것을 잊지 말자.

실행 결과는 다음과 같다.

This is b : false.
This is h : 41.
This is s : String.
This is c : ÿ.
This is o : 400.
This is x : ff.
This is e : 3.141593e+00.
This is g : 3.14159.
This is f : 3.141593.
This is a : 0x1.921fb5a7ed197p1.
This is n : 
.
This is percent : %.

여기서 빠진 것이 있다. 바로 날짜와 시간을 나타내는 conversion이다. 날짜와 시간을 나타내는 conversion은 조금 복잡하고, 앞서 살펴본 DateFormat과 비슷하다. 둘 중 하나만 고르라고 하면 DateFormat의 사용을 권한다.

날짜와 시간을 나타내는 conversion은 t인데, 이것을 사용할 때에는 날짜, 시, 분, 초 등을 나타내는 추가 conversion 타입과 같이 사용해야만 한다. 예를 들어 시:분 타입으로 나타내려면 다음과 같이 H와 M을 명시해야 한다.

System.out.format("%1$tH:%1$tM", new Date());

특히 날짜와 시간을 표시할 때 주의할 점은 이 예에서처럼 시와 분은 하나의 매개 변수 값을 참조해야 하기 때문에 이처럼 절대 위치를 지정하여 사용하는 것이 좋다.

이번에는 flags에 대해서 살펴보자. 자바 Formatter에서 사용 가능한 flags는 다음과 같다.

Flag 일반 char 정수 소수 날짜 설명
'-' O O O O O 좌측 정렬
'#' O - O O - conversion에 의존적인 형식 사용
'+' - - O O - 항상 부호 표시
' ' - - O O - 빈 공간을 0으로 채움
',' - - O O - 지역에서 사용하는 그룹 구분자 추가
'(' - - O O - 음수를 괄호로 묶음

이 표에서 작은 따옴표는 그냥 어떤 기호인지 나타내기 위한 것이니 크게 신경쓰지 않아도 된다. 그리고, 위에서 네 번째 flag는 공백을 의미한다. flag들을 보면 대부분 숫자에서 사용하기 위한 조건이다. 그리고, flag들은 대부분 폭을 나타내는 width와 같이 사용해야 한다. 그렇지 않으면 예외가 발생하니 조심해야 한다. 그럼 다음의 예제를 보자.

public void checkFlag(){
        System.out.format("This is %%-20g : %-20g.\n", 3.1415927);
        System.out.format("This is %%20g : %20g.\n", 3.1415927);
        System.out.format("This is %%+20g : %+20g.\n", 3.1415927);
        System.out.format("This is %% g : [% g].\n", 3.1415927);
        System.out.format("This is %% g : [% g].\n", -3.1415927);
        System.out.format("This is %%020g : %020g.\n", 3.1415927);
        System.out.format("This is %%020g : %020g.\n", -3.1415927);
        System.out.format("This is %%,20g : %,20g.\n", 123456.789);
        System.out.format("This is %%(20g : %(20g.\n", -3.1415927);
    }

실행 결과는 다음과 같다.

This is %-20g : 3.14159             .
This is %20g :              3.14159.
This is %+20g :             +3.14159.
This is % g : [ 3.14159].
This is % g : [-3.14159].
This is %020g : 00000000000003.14159.
This is %020g : -0000000000003.14159.
This is %,20g :              123,457.
This is %(20g :            (3.14159).

참고

  • 자바의 신
profile
이것저것 관심많은 개발자.

0개의 댓글