결론
java.util.date 클래스와 Calendar 객체 대신.
1.8에서 지원하는 java.time 패키지의 날짜객체들을 사용하는 것이 좋습니다.
기존의 Java 날짜 객체의 문제점
Java 1.0은 날짜와 관련된 Date객체를 함께 출시 했습니다.
import java.util.Date;
Date now = new Date(); // 2022-12-05 23:35:02. 생성.
그러나 1.0 에서 지원하는 Date 객체는 여러가지 문제점을 가지고 있었습니다.
햇갈리는 생성자 설계
Date객체 생성자의 파라미터들은 순서대로 '연-월-일 시간-분-초'의 값을 갖습니다.
그렇다면 아래와 같이 파라미터를 입력해서 '2022-12-05 23:45:00' 값을 갖는 날짜 객체를 생성할 수 있을까요.
Date date1 = new Date(2022, 12, 5, 23, 45, 00); // 이렇게 생성하면 2022-12-05 23:45:00 으로 생성 될까?
하지만 실제 객체를 생성해 보면 아래와 같은 값으로 3923년 1월이 출력 됩니다.
System.out.println(date1); // 3923-01-05 23:45:00. 출력.
왜 그럴까요.
- Date 객체 생성자의 연도값은 1900 기준으로 생성 됩니다. 그래서 2022를 입력하면 1900 + 2022 = 3922 가 됩니다.
- 월은 0부터 시작 하기 때문에 12를 입력하면 1월이 출력 됩니다.
Date now = new Date(); // 현재 시각을 생성. ex) 2022-12-05 23:44:00
System.out.println(now.getMonth()) // 11.
Date 객체를 이용해 현재 시각 객체를 생성한 후 Month 값을 호출하면, 12월인데 11을 리턴 합니다.
위에서 보시다시피 월 값이 0부터 시작하기 때문입니다.
그렇다면 다시 Date객체의 생성자를 이용해서 '2022-12-05 23:45:52' 를 출력하려면 어떻게 해야 할까요?
그렇습니다. 연도에는 2022 - 1900 = 122를 입력해 주고, 월 값에는 12 - 1 = 11 을 입력하면 됩니다.
Date date2 = new Date(122,11,5,23,45,52);
System.out.println(date2); // 2022-12-05 23:45:52.
번거롭고 비직관적 입니다. 작성중 계산을 잘못하면 실수로 잘못된 날짜를 생성하게 될 수도 있겠죠.
그래서 Java 1.1 버전에 새로 내놓은 것이 Calendar 객체 입니다.
Calendar calendar = Calendar.getInstance();
calendar.set(2022, 11, 5, 23, 45, 52); // set 메서드로 날짜 정보 입력.
Date date2 = calendar.getTime(); // Date 인스턴스 생성.
System.out.println(date2); // 2022-12-05 23:45:52.
기존 Date 객체보다는 생성이 직관적으로 보입니다.
그러나 여전히 Month의 경우, 0부터 시작한다는 단점이 있습니다.
그래서 아래와 같이 사용하기도 합니다.
calendar.set(2022, Calendar.DECEMBER, 5, 23, 45, 52); // 2022-12-05 23:45:52.
조금은 파악하기 쉬워 졌을까요.
하지만 날짜 객체를 사용하기 위해서 Date와 Calendar 객체를 번갈아 가며 써야 한다는 점은 불편해 보이지 않나요.
객체 불변성 (Immutable)
이제 직관적이지 않아 보이는 점은 어느정도 해소가 된 것 같습니다만.
또 다른 문제점은 불변적이지 않다는 점 입니다.
객체의 불변성이 보장되지 않아 동시에 제어할 경우 의도하지 않은 값 변경이 발생할 수 있습니다.
아래 코드를 보실까요.
DateThread dt = new DateThread();
Date date = new Date(122, 11, 5, 23, 25, 50); // 2022-12-05 23:25:50.
dt.setDate(date); // 쓰레드 객체에 날짜 객체를 할당.
Thread thread = new Thread(dt);
date.setTime(60000); // 쓰레드 시작 전에 할당한 날짜 객체 값을 변경.
thread.start(); // 쓰레드 시작.
'2022-12-05 23:25:50' 로 날짜 객체를 생성하고.
쓰레드에서 날짜를 출력 받겠습니다.
위 코드를 실행하면 해당 쓰레드가 실행되는 클래스에 할당한 날짜 객체 값과는 다르게
'1970-01-01 09:01:00' 이 출력 됩니다.
그 이유는 thread.start() 직전에 date객체의 time 값을 60000 으로 변경 했기 때문 입니다.
dt 객체에 date객체의 할당이 이미 끝났지만, date 객체의 값이 복사되어 전달된 것이 아니라, 메모리 주소값이 복사되어 전달 되어서 그렇습니다.
그래서 date 객체의 값을 변경하면 해당 주소값의 객체를 잠조하고 있는 dt 객체의 날짜 값도 변경이 되는 것 입니다.
이렇게 어떤 객체의 상태값이 변경 가능하면 여러 쓰레드가 경합을 벌이는 과정에서 위에서 처럼 의도하지 않은 값 변경을 겪을 수 있습니다.
이를 'Thread-safe 하지 않다.' 고 표현 합니다.
그렇다면 Thread-Safe하게 객체를 다루기 위해서는 어떻게 해야 할까요.
여러가지 방법이 있습니다만.
객체의 상태나 값을 변경하게 되면, 객체를 새롭게 만들게 구성하면 됩니다.
이를 변하지 않는 객체라는 의미에서 불변객체 라고 합니다. (Immutable Instance)
date.setTime(60000); // 만약 위에서 처럼 이런 변경이 일어날시 아예 객체를 새로 만든다.
대표적인 불변객체에는 String 객체가 있습니다. String은 아시다시피 문자열 타입인데 값을 어떻게 변경해도 모두 새로운 객체로 생성합니다.
하지만, 위 예제에서 확인하셨다 시피 Date와 Calendar는 Thread-Safe 하지 않습니다.
Java 날짜객체 Open Source
위 단점들을 보완하고자 joda-time과 같은 오픈 소스 객체를 사용해 날짜 데이터를 사용해 왔습니다.
Java 내장이 아닌 오픈소스 이므로 maven이나 gradle에 의존성을 추가해줘야 사용할 수 있습니다.
dependencies {
implementation 'joda-time:joda-time:2.12.1'
}
joda에서 제공하는 Datetime 객체로 날짜를 생성해 보겠습니다.
DateTime dateTime = new DateTime(2022, 12, 5, 23, 45, 50);
System.out.println(dateTime); // 2022-12-05T23:45:50.000+09:00
입력된 숫자 그대로 잘 생성 됩니다.
날짜를 변경하면 새로운 객체를 생성해서 반환하고 있을까요?
DateTime dateTime1 = dateTime.plusDays(30); // 위에서 생성한 날짜 객체에 30일을 더한 객체를 생성.
System.out.println(dateTime1); // 2023-01-04 23:45:50
plusDays 객체 내부를 살펴보면 날짜객체를 신규로 생성하고 있습니다.
JodaTime의 모든 변경 메서드들은 신규 객체를 생성해 리턴 합니다.
불변객체라는 의미 입니다.
이 밖에도 JodaTime 패키지는 YearMonth와 같이 연월을 표시하는 객체를 제공 하거나,
기타 다른 날짜관련 기능을 제공 합니다.
JSR-310. Java 1.8 에서 향상된 공식 날짜 관련 클래스 지원.
Java 1.1 에서 Calendar를 지원한 것으로부터 오랫동안 날짜관련 API에 대한 지원이 없었습니다.
1.8 버전에서는 위 JodaTime의 구성을 받아들여 java.time 패키지를 공식적으로 지원 하게 되는데 이를
JSR-310 명세라 부릅니다.
JSR-310 : Java Community Process. JCP에서 관리하는 Java Specification Requests의 310 번째 안건. https://jcp.org/en/jsr/detail?id=310
내용은 request new and improved date and time API for java.
https://jcp.org/aboutJava/communityprocess/pfd/jsr310/JSR-310-guide.html
그래서 1.8 이상의 JDK를 사용한다면 jodatime을 사용하기 위해 의존성을 추가해주지 않아도 java.time 패키지에서
최신 날짜 객체를 사용할 수 있습니다.
java.time 패키지의 날짜 객체 생성은 아래와 같습니다.
LocalDate localDate = LocalDate.of(2022, 12, 24); // 년월일 생성.
LocalTime localTime = LocalTime.of(23, 50, 20); // 시분초 생성.
// LocalDate와 LocalTime으로 LocalDateTime 객체 생성 가능.
LocalDateTime localDateTime1 = LocalDateTime.of(localDate, localTime);
// 연월일 시분초를 입력해서도 가능.
LocalDateTime localDateTime = LocalDateTime.of(2022, 12, 24, 23, 50, 20);
갈무리
Date의 메서드들은 Calendar 객체가 나온 시점에 모두 Deprecated 되었습니다.
이미 Date, Calendar 클래스를 사용하는 레거시 코드가 아니라면,
java.time 패키지의 객체를 사용 하시길 권장 합니다.
기존의 레거시 코드들이 Date와 Calendar 객체를 사용하고 있는 상황이라면,
신규로 추가되는 부분은 JSR-310 으로 작성하고, 조금씩 마이그레이션 하는 것이 좋을 것 같습니다.
기존 Date객체를 JSR-310으로 변환 해야 하는 경우가 있는데,
Java의 날짜 객체들은 Instant 객체로 중간변환하여 타입변환이 가능 합니다.
'☕️Java > Java Basic' 카테고리의 다른 글
List를 1000건씩 삽입하는 방법. (0) | 2021.08.23 |
---|---|
Java partition list by size. (0) | 2021.08.18 |
new BigDecimal과 BigDecimal.valueOf의 차이. (0) | 2021.01.13 |
BigDecimal의 toString 들. (0) | 2021.01.13 |
BigDecimal - stripTrailingZeros() (0) | 2021.01.13 |