Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions docs/ch12/ch12
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# 12. 새로운 날짜와 시간 API

## 기존 날짜·시간 API의 문제

- `Date`는 이름과 달리 날짜가 아니라 특정 시점을 밀리초로 표현한다.
- 1900년 기준 연도 오프셋, 0부터 시작하는 월 인덱스 등 설계가 직관적이지 않다.
- `Date`와 `Calendar`가 함께 존재해 어떤 타입을 써야 하는지 혼란스럽다.
- `DateFormat`은 스레드 안전하지 않다.
- 대부분 가변 객체라 유지보수가 어렵다.

⇒ 자바 8은 Joda-Time의 장점을 반영한 불변 `java.time` API를 제공한다.

# 12.1 LocalDate, LocalTime, Instant, Duration, Period

## 12.1.1 LocalDate와 LocalTime

`LocalDate`는 시간대 없이 날짜를, `LocalTime`은 시간대 없이 시간을 표현한다. 둘 다 불변 객체다.

```java
LocalDate date = LocalDate.of(2017, 9, 21);
int year = date.getYear();
Month month = date.getMonth();
int day = date.getDayOfMonth();
DayOfWeek dow = date.getDayOfWeek();

LocalDate today = LocalDate.now();
LocalTime time = LocalTime.of(13, 45, 20);
LocalDate parsed = LocalDate.parse("2017-09-21");
```

- `get` 메서드와 `ChronoField`로 각 필드를 조회한다.
- 정적 팩토리 `of`, `now`와 `parse`로 생성한다.
- `LocalDate`와 `LocalTime`을 조합하면 `LocalDateTime`을 만들 수 있다.

## 12.1.2 LocalDateTime

```java
LocalDateTime dt1 = LocalDateTime.of(2017, Month.SEPTEMBER, 21, 13, 45, 20);
LocalDateTime dt2 = date.atTime(time);
LocalDateTime dt3 = date.atTime(13, 45, 20);
LocalDateTime dt4 = time.atDate(date);

LocalDate localDate = dt1.toLocalDate();
LocalTime localTime = dt1.toLocalTime();
```

## 12.1.3 Instant

기계가 이해하는 연속된 시간 모델이다. 유닉스 에포크인 `1970-01-01T00:00:00Z`부터 흐른 초와 나노초로 표현한다.

```java
Instant now = Instant.now();
Instant a = Instant.ofEpochSecond(3);
Instant b = Instant.ofEpochSecond(2, 1_000_000_000);
```

- 사람이 읽는 날짜 필드를 제공하지 않는다.
- `LocalDateTime`은 사람용, `Instant`는 기계용이므로 직접 혼용하지 않는다.

## 12.1.4 Duration과 Period

- `Duration`: 두 시간 객체 사이의 시간 간격. 초와 나노초 기반
- `Period`: 두 `LocalDate` 사이의 날짜 간격. 년·월·일 기반

```java
Duration d1 = Duration.between(time1, time2);
Duration d2 = Duration.between(dateTime1, dateTime2);
Duration d3 = Duration.between(instant1, instant2);

Period tenDays = Period.between(
LocalDate.of(2017, 9, 11),
LocalDate.of(2017, 9, 21)
);
```

# 12.2 날짜 조정, 파싱, 포매팅

모든 `java.time` 핵심 타입은 불변이다. 조정 메서드는 원본을 바꾸지 않고 새 인스턴스를 반환한다.

## 12.2.1 with, plus, minus

```java
LocalDate date1 = LocalDate.of(2017, 9, 21);
LocalDate date2 = date1.withYear(2011);
LocalDate date3 = date2.withDayOfMonth(25);
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 2);
LocalDate date5 = date4.plusWeeks(1).minusYears(3);
```

- 절대적 변경: `with` 메서드
- 상대적 변경: `plus`, `minus` 메서드
- 반환값을 저장하지 않으면 원본에는 아무 변화도 없다.

## 12.2.2 TemporalAdjuster

복잡한 날짜 조정 규칙을 캡슐화한다. `TemporalAdjusters`는 자주 쓰는 구현을 정적 팩토리로 제공한다.

```java
import static java.time.temporal.TemporalAdjusters.*;

LocalDate date1 = LocalDate.of(2014, 3, 18);
LocalDate nextSunday = date1.with(nextOrSame(DayOfWeek.SUNDAY));
LocalDate monthEnd = nextSunday.with(lastDayOfMonth());
```

- `nextOrSame`, `previousOrSame`
- `firstDayOfMonth`, `lastDayOfMonth`
- `firstDayOfNextMonth`, `firstDayOfYear`
- `dayOfWeekInMonth`
- 필요하면 `TemporalAdjuster`를 직접 구현하거나 `TemporalAdjusters.ofDateAdjuster`로 재사용할 수 있다.

## 12.2.3 DateTimeFormatter

스레드 안전한 불변 포매터다.

```java
LocalDate date = LocalDate.of(2014, 3, 18);
String basic = date.format(DateTimeFormatter.BASIC_ISO_DATE); // 20140318
String iso = date.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2014-03-18

LocalDate parsed = LocalDate.parse("2014-03-18", DateTimeFormatter.ISO_LOCAL_DATE);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
```

- 미리 정의된 ISO 포매터를 사용한다.
- `ofPattern`으로 사용자 정의 패턴을 만든다.
- `Locale`을 지정해 지역화된 포매터를 만든다.
- 복잡한 형식은 `DateTimeFormatterBuilder`를 사용한다.

# 12.3 다양한 시간대와 캘린더

## 12.3.1 ZoneId와 ZonedDateTime

- `ZoneId`는 지역 시간대 규칙을 표현한다.
- 일광 절약 시간 같은 규칙이 `ZoneRules`에 포함된다.
- `LocalDateTime`에 `ZoneId`를 결합하면 `ZonedDateTime`이 된다.
- `Instant`와 `ZonedDateTime`은 서로 변환할 수 있다.

```java
ZoneId rome = ZoneId.of("Europe/Rome");
ZonedDateTime zdt = LocalDate.of(2014, 3, 18).atStartOfDay(rome);
Instant instant = zdt.toInstant();
ZonedDateTime fromInstant = instant.atZone(rome);
```

## 12.3.2 ZoneOffset와 OffsetDateTime

`ZoneOffset`은 UTC와의 고정된 시차만 표현한다. 지역별 일광 절약 시간 규칙이 필요하면 `ZoneId`를 사용해야 한다.

## 12.3.3 대안 캘린더 시스템

- `ThaiBuddhistDate`, `MinguoDate`, `JapaneseDate`, `HijrahDate`를 제공한다.
- `ChronoLocalDate`와 `Chronology`가 대안 달력 시스템을 추상화한다.
- 날짜 저장·조작·비즈니스 규칙에는 ISO-8601 기반 `LocalDate` 사용을 권장한다.
- 대안 달력은 주로 사용자 입력과 출력의 지역화에 활용한다.

# 12.4 핵심 정리

- 사람이 읽는 날짜와 시간: `LocalDate`, `LocalTime`, `LocalDateTime`
- 기계가 읽는 시점: `Instant`
- 시간 간격: `Duration`, 날짜 간격: `Period`
- `java.time` 타입은 불변이며 스레드 안전하다.
- `with`/`plus`/`minus`와 `TemporalAdjuster`로 날짜를 조정한다.
- `DateTimeFormatter`로 안전하게 포매팅·파싱한다.
- `ZoneId`로 시간대 규칙을 적용한다.
100 changes: 100 additions & 0 deletions docs/ch13/ch13
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# 13. 디폴트 메서드

라이브러리 인터페이스에 새 메서드를 추가하면 기존 구현 클래스가 모두 깨질 수 있다. 자바 8은 소스 호환성을 유지하며 API를 발전시키기 위해 디폴트 메서드와 정적 인터페이스 메서드를 도입했다.

# 13.1 변화하는 API

## 13.1.1 호환성 종류

- 바이너리 호환성: 재컴파일 없이 기존 바이너리가 실행된다.
- 소스 호환성: 기존 소스가 다시 컴파일된다.
- 동작 호환성: 재컴파일 후에도 같은 동작을 보인다.

인터페이스에 새 추상 메서드를 추가하면 구현 클래스는 재컴파일 시 구현 의무가 생겨 소스 호환성이 깨진다.

## 13.1.2 API 버전 2의 문제

`Resizable`에 `setRelativeSize`를 추상 메서드로 추가하면 기존 `Ellipse` 같은 모든 구현 클래스가 바뀌어야 한다. 사용자 구현까지 통제할 수 없는 공개 API에서는 현실적인 방법이 아니다.

# 13.2 디폴트 메서드란?

인터페이스 안에 구현 코드를 포함하며 `default` 키워드로 정의한다.

```java
public interface Resizable {
int getWidth();
int getHeight();
void setAbsoluteSize(int width, int height);

default void setRelativeSize(int wFactor, int hFactor) {
setAbsoluteSize(getWidth() / wFactor, getHeight() / hFactor);
}
}
```

- 기존 구현 클래스는 새 메서드를 자동으로 상속한다.
- 필요하면 구현 클래스가 오버라이드할 수 있다.
- 인터페이스는 정적 메서드도 정의할 수 있다.

자바 8 API 예시

```java
default void sort(Comparator<? super E> c) {
Collections.sort(this, c);
}

default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
```

# 13.3 디폴트 메서드 활용 패턴

## 13.3.1 선택형 메서드

구현 클래스가 거의 항상 빈 구현을 제공하던 메서드를 디폴트로 제공하면 불필요한 보일러플레이트를 제거할 수 있다. `Iterator.remove`가 대표적이다.

## 13.3.2 동작 다중 상속

클래스는 여러 인터페이스를 구현해 여러 디폴트 동작을 조합할 수 있다.

```java
public class Monster implements Rotatable, Moveable, Resizable {
// 각 인터페이스의 추상 상태 메서드만 구현하면
// 회전·이동·크기 조절 디폴트 동작을 재사용할 수 있다.
}
```

- 구현 상속이 아니라 동작의 수평적 조합이다.
- 기능을 작고 응집도 높은 인터페이스로 분리한다.
- 최소 인터페이스와 조합 가능한 설계에 유리하다.

# 13.4 디폴트 메서드 충돌 해결

같은 시그니처의 디폴트 메서드를 여러 경로로 상속하면 다음 우선순위를 적용한다.

1. 클래스 우선: 클래스나 슈퍼클래스의 메서드가 디폴트 메서드보다 우선한다.
2. 더 구체적인 인터페이스 우선: 상속 관계라면 가장 하위의 서브인터페이스를 선택한다.
3. 명시적 선택: 위 규칙으로 해결되지 않으면 구현 클래스가 오버라이드한다.

```java
public class D implements B, C {
@Override
public void hello() {
B.super.hello();
}
}
```

## 13.4.1 다이아몬드 문제

`B`와 `C`가 모두 `A`를 상속하고 `A`만 `hello` 디폴트 메서드를 정의했다면 `D`는 `A`의 구현을 사용한다. `B`와 `C`가 각각 같은 시그니처의 디폴트 메서드를 제공하면 충돌하므로 `D`가 명시적으로 선택해야 한다.

# 13.5 핵심 정리

- 인터페이스는 추상 메서드, 구현을 가진 디폴트 메서드, 정적 메서드를 정의할 수 있다.
- 디폴트 메서드는 `default` 키워드와 메서드 바디를 갖는다.
- 공개 인터페이스에 추상 메서드를 추가하면 소스 호환성이 깨진다.
- 디폴트 메서드는 기존 구현을 강제로 수정하지 않고 API를 발전시킨다.
- 선택형 메서드와 동작 다중 상속에 활용할 수 있다.
- 충돌 규칙은 클래스 우선 → 더 구체적인 인터페이스 → 명시적 선택 순서다.