MessageFormat
MessageFormat은 데이터를 정해진 양식에 맞게 출력할 수 있게 도와준다. 데이터가 들어갈 자리를 마련해 놓은 양식을 미리 작성하고 프로그램을 이용해서 다수의 데이터를 같은 양식으로 출력할 때 사용하면 좋다.
예를 들어 고객들에게 보낼 안내문을 출력할 때 같은 안내문 양식에 받는 사람과 이름과 같은 데이터만 달라지도록 출력할 때, 또는 하나의 데이터를 다양한 방식으로 출력할 때 사용한다. 그리고 SimpleDateFormat의 parse처럼 MessageFormat의 parse를 이용하면 지정된 양식을 이용하면 지정된 양식에서 필요한 데이터만을 손쉽게 추출해 낼 수도 있다.
import java.text.*;
public class MessageFormatEx1 {
public static void main(String[] args) {
String msg = "Name : {0} \nTel: {1} \nAGE: {2} \nBirthday: {3}";
Object[] argurmnets = {
"이자바", "02-123-1234", "27", "07-09"
};
String result = MessageFormat.format(msg, argurmnets);
System.out.println(result);
}
}
===========================
Name : 이자바
Tel: 02-123-1234
AGE: 27
Birthday: 07-09
MessageFormat에 사용할 양식인 문자열 msg를 작성할 때 '{숫자}'로 표시된 부분이 데이터가 출력될 자리이다. 이 자리는 순차적일 필요는 없고 여러번 반복해서 사용할 수 도 있다. 여기에 사용되는 숫자는 배열처럼 인덱스가 0부터 시작하며 양식에 들어갈 데이터는 객체배열엔 arguments에 지정되어 있음을 알 수 있다.
객체배열이기 때문에 String이외에도 다른 객체들이 지정될 수 있으며, 이 경우 보다 세부적인 옵션들이 사용될 수 있다. 이러한 옵션들이 사용되는 빈도가 낮기 때문에 여기서 자세한 설명은 생략한다.
import java.text.*;
public class MessageFormatEx2 {
public static void main(String[] args) {
String tableName = "CUST_INFO";
String msg = "INSERT INTO "+ tableName+" VALUES(''{0}'',''{1}'',{2},''{3}'');";
Object[][] arguments = {
{"이자바", "02-123-1234", "27", "07-09"},
{"김프로", "032-333-1234", "33", "10-07"},
};
for(int i=0; i< arguments.length; i++){
String result = MessageFormat.format(msg, arguments[i]);
System.out.println(result);
}
}
}
===================================
INSERT INTO CUST_INFO VALUES('이자바','02-123-1234',27,'07-09');
INSERT INTO CUST_INFO VALUES('김프로','032-333-1234',33,'10-07');
이전 예제를 보다 발전시켜서 반복문으로 하나 이상의 데이터 집합을 출력하는 예제이다.
다수의 데이터를 Database에 저장하기 위한 Insert문으로 변환하는 경우 등에 사용하면 좋을 것이다.
홑따옴표(')는 MessageFormat의 양식에 escape문자로 사용되기 때문에 문자열 msg내에서 홑따옴표를 사용하려면 홑따옴표를 연속으로 두 번 사용해야 한다.
import java.text.*;
public class MessageFormatEx3 {
public static void main(String[] args) throws Exception {
String[] data = {
"INSERT INTO CUST_INFO VALUES ('이자바','02-123-1234',27,'07-09');",
"INSERT INTO CUST_INFO VALUES ('김프로','03-333-1234',33,'10-07');" };
String pattern = "INSERT INTO CUST_INFO VALUES ({0},{1},{2},{3});";
MessageFormat mf = new MessageFormat(pattern);
for (int i = 0; i < data.length; i++) {
Object[] objs = mf.parse(data[i]);
for (int j = 0; j < objs.length; j++) {
System.out.print(objs[j] + ",");
}
System.out.println();
}
}
}
===========================
'이자바','02-123-1234',27,'07-09',
'김프로','03-333-1234',33,'10-07',
이전 예제에서는 데이터를 양식에 넣어서 출력했지만 이번에는 parse(String source)를 이용해서 출력된 데이터로부터 필요한 데이터만을 뽑아내는 방법을 보여준다.,
이 예제를 잘 활용하면 어려운 작업을 쉽게 처리할 수 있으니 잘 기억해두길 바란다.
위의 data와 pattern은 띄어쓰기와 따옴표 까지 정확히 일치해야 한다. 일치하지 않으면 예외가 발생한다.
import java.util.*;
import java.text.*;
import java.io.*;
public class MessageFormatEx4 {
public static void main(String[] args) throws Exception {
String tableName ="CUST_INFO";
String fileName ="data4.txt";
String msg = "INSERT INTO "+tableName+" VALUES ({0},{1},{2},{3});";
Scanner s = new Scanner(new File(fileName));
String pattern = "{0},{1},{2},{3}";
MessageFormat mf = new MessageFormat(pattern);
while(s.hasNextLine()){
String line = s.nextLine();
Object[] objs = mf.parse(line);
System.out.println(MessageFormat.format(msg, objs));
}
s.close();
}
}
============================
INSERT INTO CUST_INFO VALUES (INSERT INTO CUST_INFO VALUES ('이자바','02-123-1234',27,'07-09'));
INSERT INTO CUST_INFO VALUES (INSERT INTO CUST_INFO VALUES ('김프로','03-333-1234',33,'10-07'));
이전의 예제에서는 데이터를 객체 배열에 직접 초기화 하였는데, 이렇게 하면 데이터가 바뀔 때 마다 매번 배열을 변경해야하고 그리고는 다시 컴파일을 해줘야하므로 불편하다.
이러한 불편함을 없애기 위해 이번엔 Scanner를 통해 파일로부터 데이터를 라인별로 읽어서 처리하도록 변경했다. 이렇게 하면 파일로부터 데이터를 제공받으면 데이터가 변경되어도 다시 컴파일을 하지 않아도 된다.
- 실행 시에 입력받을 데이터가 저장된 파일명도 지정하도록 예제를 변경하면, 파일의 이름이 바뀌어도 다시 컴파일하지 않아도 되므로 더 편리할 것이다.
java.time 패키지
Java의 탄생부터 지금까지 날짜와 시간을 다루는데 사용해왔던, Date와 Calendar가 가지고 있던 단점들을 해소하기 위해 드디어 JDK1.8부터 java.time패키지가 추가되었다.
이 패키지는 다음과 같이 4개의 하위 패키지를 가지고 있다.
java.time : 날짜와 시간을 다루는데 필요한 핵심 클래스들을 제공
java.time.chrono : 표준 (ISO)이 아닌 달력 시스템을 위한 클래스들을 제공
java.time.format : 날짜와 시간을 파싱하고, 형식화하기 위한 클래스들을 제공
java.time.temporal : 날짜와 시간을 필드(field)와 단위(unit)를 위한 클래스들을 제공
java.time.zone : 시간대(time-zone)와 관련된 클래스들을 제공
패키지의 개수는 많지만 실제로 사용되는 핵심적인 클래스들은 그리 많지 않으니까 미리 겁먹지 않아도 된다. 위의 패키지들에 속한 클래스들이 가장 큰 특징은 String클래스 처럼 불변(immutable)이라는 것이다. 그래서 날짜나 시간을 변경하는 메서드들은 기존의 객체를 변경하는 대신 항상 변경된 새로운 객체를 반환한다. 기존 Calendar 클래스는 변경 가능하므로, 멀티 쓰레드 환경에서 안전하지 못하다.
멀티 쓰레드 환경에서는 동시에 여러 쓰레드가 같은 객체에 접근할 수 있기 때문에, 변경 가능한 객체는 데이터가 잘못될 가능성이 있으며, 이를 쓰레드에 안전(thread - safe)하지 않다고 한다.
새로운 패키지가 도입되었음에도 불구하고, 앞으로도 기존에 작성된 프로그램과의 호환성 때문에 Date와 Calendar는 여전히 사용될 것이다. 새로운 java.time패키지의 클래스와 기존의 날짜와 시간관련 클래스들 간의 변환방법에 대해서는 이 장의 뒷부분에서 소개한다.
java.time 패키지의 핵심 클래스
날짜와 시간을 하나로 표현하는 Calendar클래스와 달리, java.time패키지에서는 날짜와 시간을 별도의 클래스로 분리해 놓았다. 시간을 표현할 때는 LocalTime클래스를 사용하고, 날짜를 표현할 때는 LocalDate클래스를 사용한다. 그리고 날짜와 시간이 모두 필요할 때는 LocalDateTime클래스를 사용하면 된다.
LocalDate : 날짜
LocalTime : 시간
LocalDateTime : 날짜 & 시간
여기 시간대(time-zone)까지 다뤄야 한다면, ZonedDateTime클래스를 사용하자.
LocalDateTime + 시간대 → ZonedDateTime
Calendar는 ZonedDateTime처럼, 날짜와 시간 그리고 시간대까지 모두 가지고 있다.
Date와 유사한 클래스로는 instant가 있는데, 이 클래스는 날짜와 시간을 초 단위(정확히는 나노초)로 표현한다. 날짜와 시간을 초단위로 표현한 값을 타임스탬프(time-stamp)라고 부르는데, 이 값은 날짜와 시간을 하나의 정수로 표현할 수 있으므로 날짜와 시간의 차이를 계산하거나 순서를 비교하는데 유리해서 데이터베이스에 많이 사용된다.
이외에도 날짜를 더 세부적으로 다룰 수 있는 Year, YearMonth, MonthDay와 같은 클래스도 있다.
period와 Duration
날짜와 시간의 간격을 표현하기 위한 클래스도 있는데, Period는 두 날짜간의 차이를 표현하기 위한 것이고, Duration은 시간의 차이를 표현하기 위한 것이다.
날짜 - 날짜 = Period
시간 - 시간 = Duration
객체 생성하기 -now()-, of()
java.time패키지에 속한 클래스의 객체를 생성하는 가장 기본적인 방법은 now()와 of()를 사용하는 것이다. now()는 현재 날짜와 시간을 저장하는 객체를 생성한다.
LocalDate date = LocalDate.now(); //2015-11-23
LocalTime time = LocalTime.now(); //21:54:01.875
LocalDateTime dateTime = LocalDateTime.now(); //2015-11-23T23:54:01.875
ZonedDateTime dateTimeInKr = ZondedDateTime.now(); //2015-11-23T21:54:01.875+09:00(Asia/Seoul)
of()는 단순히 해당 필드의 값을 순서대로 지정해주기만 하면 된다. 각 클래스마다 다양한 종류의 of()가 정의되어 있다.
LocalDate date = LocalDate.of(2015, 11, 23); //2015년 11월 23일
LocalTime time = LocalTime.of(23,59,59); // 23시 59분 59초
LocalDateTime dateTime = LocalDateTime.of(date, time);
ZonedDateTime dateTimeInKr = ZondedDateTime.of(dateTime,ZondId.of("Asia/Seoul"));
Temporal과 TemporalAmount
LocalDate, LocalTime, LocalDateTime, ZonedDateTime등 날짜와 시간을 표현하기 위한 클래스들은 모두 Temporal, TemporalAccessor, TemporalAdjuster인터페이스를 구현했고, Duration과 Period는 TemporalAmount인터페이스를 구현하였다. 앞으로 소개할 메서드 중에서 매개변수의 타입이 Temporal로 시작하는 것들이 자주 등장할 텐데 대부분 날짜와 시간을 위한 것이므로, TemporalAmount인지 아닌지만 확인하면 된다.
- 'temporal'과 'chrono'의 의미가 모두 시간(time)인데도 'time'대신 굳이 이런 어려운 용어를 쓴 이뉴는 시간(시분초)과 더 큰 개념의 시간(년월일시분초)을 구분하기 위해서이다.
Temporal, TemporalAccessor, TemporalAdjuster를 구현한 클래스
-LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant 등
TemporalAmount를 구현한 클래스
-Period, Duration
TemporalUnit과 TemporalField
날짜와 시간의 단위를 정의해 놓은 것이 TemporalUnit인터페이스이고, 이 인터페이스를 구현한 것이 열거형 ChronoUnit이다. 그리고 TemporalField는 년,월,일 등 날짜와 시간의 필드를 정의해 놓은 것으로, 열거형 ChronoField가 이 인터페이스를 구현하였다.
- 열거형(enumeration)은 서로 관련된 상수를 묶어서 정의해 놓은 것이다.
LocalTime now = LocalTime.now(); //현재시간
int minute = now.getMinute(); //현재 시간에서 분만 뽑아낸다.
//int minute = now.get(ChronoField.MINUTE_OF_HOUR); //위의 문장과 동일
날짜와 시간에서 특정 필드의 값만을 얻을 때는 get()이나, get으로 시작하는 이름의 메서드를 이용한다. 그리고 아래와 같이 특정 날짜와 시간에서 지정된 단위의 값을 더하거나 뺄 때는 plus() 또는 minus()에 값과 함께 열거형 ChronoUnit을 사용한다.
LocalDate today = LocalDate.now(); //오늘
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);// 오늘이 1일을 더한다.
LocalDate tomorrow = today.plusDays(1);//위의 문장과 동일
참고로 get()과 plus()의 정의는 아래와 같다.
int get (TemporalField field)
LocalDate plus(long amountToAdd, Temporalunit unit)
특정 TemporalFiled나 TemporalUnit을 사용할 수 있는지 확인하는 메서드는 다음과 가탇. 이 메서드들은 날짜와 시간을 표현하는데 사용하는 모든 클래스에 포함되어 있다.
boolean isSupported (TemporalUnit unit) //Temporal에 정의
boolean isSupported (TemporalField field) //TemporalAccessor에 정의
LocalDate와 LocalTime
LocalDate와 LocalTime은 java.time패키지의 가장 기본이 되는 클래스이며, 나머지 클래스들은 이들의 확장이므로 이 두 클래스만 잘 이해하고 나면 나머지는 아주 쉬워진다.
객체를 생성하는 방법은 현재의 날짜와 시간을 LocalDate와 LocalTime으로 각각 반환하는 now()와 지정된 날짜와 시간으로 LocalDate와 LocalTime객체를 생성하는 of()가 있다. 둘 다 static메서드이다.
LocalDate today = LocalDate.now(); //오늘의 날짜
LocalTime now = LocalTime.now(); //현재 시간
LocalDate birthDate = LocalDate.of(1992,12,31); //1999년 12월 31일
LocalTime birthTime = LocalTime.of(23,59,59); //23시 59분 59초
of()는 다음과 같이 여러 가지 버전이 제공된다.
static LocalDate of(int year, Month month, int dayOfMonth)
static LocalDate of(int year, int month, int dayOfMonth)
static LocalTime of(int hour, int min)
static LocalTime of(int hour, int min, int sec)
static LocalTime of(int hour, int min, int sec, int nanoOfSecond)
참고로 다음과 같이 일 단위나 초 단위로도 지정할 수 있다. 아래의 첫번째 문장은 1999년 365번째 날, 즉 마지막날을 의미하며, 두번째 문장은 그 날의 0시 0분 0초부터 86399초 (하루는 86400초)가 지난시간 , 즉 23시 59분 59초를 의미한다.
LocalDate birthDate = LocalDate.ofYearDay(1999,365); //1999년 12월 31일
LocalTime birthTime = LocalTime.ofSecondDay(86399); //23시 59분 59초
또는 parse()를 이용하면 문자열을 날짜와 시간으로 변환할 수도 있다.
LocalDate birthDate = LocalDate.parse("1999-12-31"); //1999년 12월 31일
LocalTime birthTime = LocalTime.parse("23:59:59"); //23시 59분 59초
특정 필드의 값 가져오기 - get(), getXXX()
LocalDate와 LocalTime의 객체에서 특정 필드의 값을 가져올 때는 아래의 표에 있는 메서드를 사용한다. "1999년 12월 31일 23:59:59"를 예로 들어 각 메서드의 호출결과를 적어 놓았으니 어렵지 않게 이해할 수 있을 것이다. 주의할 점은 Calendar와 달리 월(month)의 범위가 1~12이고, 요일은 월요일이 1, 화요일이2, 일요일이 7이라는 것이다.
- Calendar는 1월을 0으로 표현하고, 일요일은 1, 월요일은2 , 토요일은 7로 표현한다.
클래스 LocalDate
메서드 : 설명 (1999-12-31 23:59:59)
int getYear() : 년도 (1999)
int getMonth() : 월(12) Month getMonth() : 월(DECEMBER) getMonth().getValue() = 12
int getDayOfMonth() : 일(31)
int getDayOfYear() : 같은 해의 1월 1일부터 몇번째 일(365)
DayOfWeek getDayOfWeek() : 요일(FRIDAY) getDayOfWeek().getValue() = 5
int lengthOfMonth() : 같은 달의 총 일수(31) int lengthOfYear() : 같은 해의 총 일수(365), 윤년이면 366
boolean isLeapYear() : 윤년여부 확인(false)
클래스 LocalTime
메서드 : 설명 (1999-12-31 23:59:59)
int getHour() : 시(23)
int getMinute() : 분(59)
int getSecond() : 초(59) int getNano() : 나노초(0)
위에서 소개된 메서드 외에도 get()과 getLong()이 있는데, 원하는 필드를 직접 지정할 수 있다. 대부분의 필드는 int타입의 범위에 속하지만 몇몇 필드는 int타입의 범위를 넘을 수 있다. 그럴 때 get()대신 getLong()을 사용해야 한다. getLong()을 사용해야 하는 필드는 아래의 필드 이름 옆에 *표시가 되어 있다.
int get(TemporalField field)
long getLong(TemporalField field)
이 메서드들의 매개변수로 사용할 수 있는 필드의 목록은 아래와 같다.
ERA : 시대
YEAR_OF_ERA, YEAR : 년
MONTH_OF_YEAR : 월
DAY_OF_WEEK : 요일(1: 월요일, 2:화요일, 7:일요일)
DAY_OF_MONTH : 일
AMPM_OF_DAY : 오전/오후
HOUR_OF_DAY : 시간(0~23)
CLOCK_HOUR_OF_DAY : 시간(1~24)
HOUR_OF_AMPM : 시간(0~11)
CLOCK_HOUR_OF_AMPM : 시간(1~12)
MINUTE_OF_HOUR : 분
SECOND_OF_MINUTE : 초
MILLI_OF_SECOND : 천분의 일초(=10^-3초)
MICRO_OF_SECOND * : 백만분의 일초(=10^-6초)
NANO_OF_SECONDE * : 10억분의 일초(=10^-9초)
DAY_OF_YEAR : 그 해의 몇번째 날
EPOCH_DAY * : EPOCH(1970.1.1)부터 몇번째날
MINUTE_OF_DAY : 그 날의 몇번째 분(시간을 분으로 환산)
SECOND_OF_DAY : 그 날의 몇번째 초(시간을 초로 환산) MILLI_OF_DAY : 그 날의 몇번째 밀리초
MICRO_OF_DAY* : 그 날의 몇번째 마이크로초
NANA_OF_DAY* : 그 날의 몇번째 나노초
ALIGNED_WEEK_OF_MONTH : 그 달의 n번째 주(1~7일 1주, 8~14일 2주,...)
ALIGNED_WEEK_OF_YEAR : 그 해의 n번째 주(1월 1~7일 1주, 8~14일 2주,...)
ALIGNED_DAY_OF_WEEK_IN_MONTH : 요일(그 달의 1일을 월요일로 간주하여 계산)
ALIGNED_DAY_OF_WEEK_IN_YEAR : 요일(그 해의 1월 1일을 월요일로 간주하여 계산)
INSTANT_SECONDS : 년월일을 초 단위로 환산(1970-01-01 00:00:00 UTC를 0초로 계산) Instant에만 사용 가능
OFFSET_SECONDS : UTC와의 시차, ZoneOffset에만 사용 가능
PROLEPTIC_MONTH : 년월일을 월 단위로 환산 (2015년 11월 = 2015*12 +11)
이 목록은 ChronoField에 정의된 모든 상수를 나열한 것 일뿐, 사용할 수 있는 필드는 클래스마다 다르다. 예를 들어 LocalDate는 날짜를 표현하기 위한 것이므로, MINUTE_OF_HOUR와 같이 시간과 관련된 필드는 사용할 수 없다.
LocalDate today = LocalDate.now(); //오늘의 날짜
System.out.println(today.get(ChronoField.MINUTE_OF_HOUR)); //예외 발생
참고로 특정 필드가 가질 수 있는 값의 범위를 알고 싶으면, 다음과 같이 하면 된다.
System.out.println(ChronoField.CLOCK_HOUR_OF_DAY.range()); // 1-24
System.out.println(ChronoField.HOUR_OF_DAY.range()); //0-23
HOUR_OF_DAY는 밤 12시를 0으로 표현하고, CLOCK_HOUR_OF_DAY는 24로 표현한다는 것을 알 수 있다.
'Back-end' 카테고리의 다른 글
02 관계형 데이터베이스와 오라클 데이터 베이스 (0) | 2021.07.05 |
---|---|
객체 지향 정리 (0) | 2021.07.05 |
01 데이터 베이스 (0) | 2021.07.04 |
10 날짜와 시간 & 형식화 (3) (0) | 2021.07.03 |
10 날짜와 시간 & 형식화 (2) (0) | 2021.07.02 |