diff --git a/docs/ch12/ch12 b/docs/ch12/ch12 new file mode 100644 index 0000000..b263ef2 --- /dev/null +++ b/docs/ch12/ch12 @@ -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`로 시간대 규칙을 적용한다. diff --git a/docs/ch13/ch13 b/docs/ch13/ch13 new file mode 100644 index 0000000..44a1b62 --- /dev/null +++ b/docs/ch13/ch13 @@ -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 c) { + Collections.sort(this, c); +} + +default Stream 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를 발전시킨다. +- 선택형 메서드와 동작 다중 상속에 활용할 수 있다. +- 충돌 규칙은 클래스 우선 → 더 구체적인 인터페이스 → 명시적 선택 순서다.