diff --git "a/Ch04/item25/\355\206\261\353\240\210\353\262\250_\355\201\264\353\236\230\354\212\244\353\212\224_\355\225\234_\355\214\214\354\235\274\354\227\220_\355\225\230\353\202\230\353\247\214_\353\213\264\354\234\274\353\235\274.md" "b/Ch04/item25/\355\206\261\353\240\210\353\262\250_\355\201\264\353\236\230\354\212\244\353\212\224_\355\225\234_\355\214\214\354\235\274\354\227\220_\355\225\230\353\202\230\353\247\214_\353\213\264\354\234\274\353\235\274.md" new file mode 100644 index 0000000..41303d0 --- /dev/null +++ "b/Ch04/item25/\355\206\261\353\240\210\353\262\250_\355\201\264\353\236\230\354\212\244\353\212\224_\355\225\234_\355\214\214\354\235\274\354\227\220_\355\225\230\353\202\230\353\247\214_\353\213\264\354\234\274\353\235\274.md" @@ -0,0 +1,91 @@ +# Item. 25 톱레벨 클래스는 한 파일에 하나만 담으라 + +> 소스 파일 하나에는 반드시 톱레벨 클래스(혹은 톱레벨 인터페이스)를 하나만 담자 +> 이 규칙만 따른다면 컴파일러가 한 클래스에 대한 정의를 여러 개 만들어 내는 일은 사라진다. +> 소스 파일을 어떤 순서로 컴파일하든 바이너리 파일이나 프로그램의 동작이 사라지지 않는다. + +--- + +### 톱레벨 클래스란? + +- 소스 파일의 가장 바깥에 선언된 클래스를 말한다. +- 중첩 클래스가 아닌 클래스를 말한다. + +
+ +### 예시 (따라 하지 말 것!) + +```java +# Song.java +class Genre { + static final String NAME = "POP"; +} + +class Song { + static final String NAME = "SONG"; +} +``` + +```java +# Genre.java +Genre.java +class Genre { + static final String NAME = "K-POP"; +} + +class Song { + static final String NAME = "SONG"; +} +``` + +```java +# main.java +# 컴파일 에러 발생 +public class Test { + public static void main(String[] args) { + System.out.println(Genre.NAME + Song.NAME); + } +} +``` + +
+ +### 해결 방법 + +- 여러 톱레벨 클래스를 한 파일에 담고 싶다면 정적 멤버 클래스를 사용하는 방법이 있다. + +```java +public class AB { + private static class A { + + } + + private static class B { + + } +} +``` + +
+ +--- + +### Appendix + +**컴파일러의 동작:** + +- 소스 코드를 전체적으로 분석하여 중간 코드(Intermediate code) 또는 목적 코드(Object code)를 생성하는 과정이다. +- 전체 소스 코드를 한 번에 번역하므로 실행 속도가 빠르다. +- 번역된 결과물은 CPU가 직접 실행할 수 있는 기계어로 변환된다. + +**인터프리터의 동작:** + +- 소스 코드를 한 줄씩 읽어들이고, 해당 코드를 즉시 실행하는 과정이다. +- 소스 코드를 한 줄씩 해석하고 실행하기 때문에 번역과 실행이 번갈아가면서 이루어진다. +- 실행 속도가 상대적으로 느리지만, 개발 과정에서 수정 및 디버깅이 용이하다. + +**자바의 빌드 과정:** + +- 자바의 빌드 과정은 컴파일과 실행의 과정이 섞여 있음 +- 컴파일 단계에서 소스 코드를 중간 형태인 바이트 코드로 변환하고, 실행 단계에서 JVM을 통해 해당 바이트 코드를 인터프리터로 - 해석하여 실행하는 방식 +- 이러한 방식으로 자바 언어는 운영체제의 종류와 상관없이 동일한 바이트 코드를 실행하여 플랫폼 호환성을 제공 diff --git "a/Ch06/item38/\355\231\225\354\236\245\355\225\240_\354\210\230_\354\236\210\353\212\224_\354\227\264\352\261\260_\355\203\200\354\236\205\354\235\264_\355\225\204\354\232\224\355\225\230\353\251\264_\354\235\270\355\204\260\355\216\230\354\235\264\354\212\244\353\245\274_\354\202\254\354\232\251\355\225\230\353\235\274.md" "b/Ch06/item38/\355\231\225\354\236\245\355\225\240_\354\210\230_\354\236\210\353\212\224_\354\227\264\352\261\260_\355\203\200\354\236\205\354\235\264_\355\225\204\354\232\224\355\225\230\353\251\264_\354\235\270\355\204\260\355\216\230\354\235\264\354\212\244\353\245\274_\354\202\254\354\232\251\355\225\230\353\235\274.md" new file mode 100644 index 0000000..a36ed5c --- /dev/null +++ "b/Ch06/item38/\355\231\225\354\236\245\355\225\240_\354\210\230_\354\236\210\353\212\224_\354\227\264\352\261\260_\355\203\200\354\236\205\354\235\264_\355\225\204\354\232\224\355\225\230\353\251\264_\354\235\270\355\204\260\355\216\230\354\235\264\354\212\244\353\245\274_\354\202\254\354\232\251\355\225\230\353\235\274.md" @@ -0,0 +1,173 @@ +# Item 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라 + +- 열거타입 자체는 확장할 수 없지만, `인터페이스와 그 인터페이스를 구현하는 기본 열거 타입을 함께 사용해 같은 효과를 낼 수 있다.` +- 이를 통해 클라이언트는 이 인터페이스를 구현해 자신만의 열거 타입(혹은 다른 타입)을 만들 수 있다. +- API가 (기본 열거 타입을 직접 명시하지 않고) 인터페이스 기반으로 작성되었다면 기본 열거 타입의 인스턴스가 쓰이는 모든 곳을 새로 확장한 열거 타입의 인스턴스로 대체해 사용할 수 있다. + +--- + +### 타입 안전 열거 타입 + +```java +public final class Direction { + + public static final Direction NORTH = new Direction("N"); + public static final Direction SOUTH = new Direction("S"); + public static final Direction EAST = new Direction("E"); + public static final Direction WEST = new Direction("W"); + + private Direction() { + ... + } +} +``` + +- jdk1.5 이전에 enum이 없을 때 사용하던 방식 + +
+ +### 열거 타입 + +```java +enum Direction { + NORTH, SOUTH, EAST, WEST; +} +``` + +- `열거 타입은 거의 모든 상황에서 타입 안전 열거 패턴(typesafe enum pattern) 보다 우수`하다. +- 단, 예외가 하나 있으니, 타입 안전 열거 패턴은 확장할 수 있으나 `열거 타입은 확장할 수 없다`. + +- `연산 코드(operation code)`에서 이따금 API가 제공하는 `기본 연산 외 사용자 확장 연산을 추가할 수 있도록 열어줘야 할 때`외에는 대부분의 상황에서 열거 타입을 확장하는 것은 좋지 않은 생각이다. + - 열거 타입을 확장하면, 확장한 타입의 원소는 기반 타입의 원소로 취금하지만 그 반대는 성립하지 않을 수 있다. + - 열거 타입을 확장하면 기반 타입과 확장 타입들의 원소 모두를 순회할 방법도 마땅하지 않다. + - 대부분의 상황에서 enum을 확장하는 것은 좋지 않다. +- 열거 타입을 확장하려면 `열거 타입이 임의의 인터페이스를 구현할 수 있다는 사실을 이용`하면 된다. +- `연산 코드용 인터페이스를 정의`하고 `열거 타입이 이 인터페이스를 구현`하게 하면 된다. 이 때 `열거 타입이 그 인터페이스의 표준 구현체 역할`을 한다. + +다음은 Operation 타입을 확장할 수 있게 만든 코드이다. + +**인터페이스를 이용해 확장 가능 열거 타입을 흉내 낸 코드** + +```java +public interface Operation { + double apply(double x, double y); +} +``` + +```java +public enum BasicOperation implements Operation { + PLUS("+") { + public double apply(double x, double y) { return x + y; } + }, + MINUS("-") { + public double apply(double x, double y) { return x - y; } + }, + TIMES("*") { + public double apply(double x, double y) { return x * y; } + }, + DIVIDE("/") { + public double apply(double x, double y) { return x / y; } + }; + + private final String symbol; + + BasicOperation(String symbol) { + this.symbol = symbol; + } + + @Override public String toString() { + return symbol; + } +} +``` + +- 열거 타입인 BasicOperation은 확장할 수 없지만 인터페이스인 Operation은 확장할 수 있고, 이 인터페이스를 연산의 타입으로 사용하면 된다. + +- 예를 들어 앞의 연산 타입을 확장해 지수 연산(EXP)와 나머지 연산(REMAINDER)을 추가해보자. 이를 위해 우리가 할 일은 Operation 인터페이스를 구현한 열거 타입을 작성하는 것 뿐이다. + +**확장 가능 열거 타입** + +```java +public enum ExtendedOperation implements Operation { + EXP("^") { + public double apply(double x, double y) { + return Math.pow(x, y); + } + }, + REMAINDER("%") { + public double apply(double x, double y) { + return x % y; + } + }; + private final String symbol; + ExtendedOperation(String symbol) { + this.symbol = symbol; + } + @Override public String toString() { + return symbol; + } +} +``` + +- 새로 작성한 연산은 기존 연산을 쓰던 곳이면 어디든 쓸 수 있다. +- Operation 인터페이스를 사용하도록 작성되어 있기만 하면 된다. + +개별 인스턴스 수준에서 뿐 아니라 타입 수준에서도, 기본 열거 타입 대신 확장된 열거 타입을 넘겨 확장된 열거 타입의 원소 모두를 사용하게 할 수도 있다. + +### 첫번째 대안) ExtendedOperation의 모든 원소 테스트하기 + +```java +public static void main(String[] args) { + double x = Double.parseDouble(args[0]); + double y = Double.parseDouble(args[1]); + test(ExtendedOperation.class, x, y); +} +private static & Operation> void test( + Class opEnumType, double x, double y) { + for (Operation op : opEnumType.getEnumConstants()) + System.out.printf("%f %s %f = %f%n", + x, op, y, op.apply(x, y)); +} +``` + +- main 메서드는 test 메서드에 ExtendedOperation의 class 리터럴을 넘겨 확장된 연산들이 무엇인지 알려준다. 여기서 class 리터럴은 한정적 타입 토큰 역할을 한다. + +- opEnumType 매개변수의 선언(` & Operation> Class `)은 솔직히 복잡한데, `Class 객체가 열거 타입인 동시에 Oepration의 하위 타입이어야 한다`는 뜻이다. + +- 열거 타입이어야 원소를 순회할 수 있고, Operation이어야 원소가 뜻하는 연산을 수행할 수 있기 때문이다. + +### 두번쨰 대안) Class 객체 대신 한정적 와일드카드 타입인 Collection을 넘기는 방법 + +```java +public static void main(String[] args) { + double x = Double.parseDouble(args[0]); + double y = Double.parseDouble(args[1]); + test(Arrays.asList(ExtendedOperation.values()), x, y); +} +private static void test(Collection opSet, + double x, double y) { + for (Operation op : opSet) + System.out.printf("%f %s %f = %f%n", + x, op, y, op.apply(x, y)); +} +``` + +- 두번째 코드는 그나마 덜 복잡하고 여러 구현 타입의 연산을 조합해 호출할 수 있게 되었다. +- 반면, 특정 연산에서는 EnumSet과 EnumMap을 사용하지 못한다. + +두 대안 프로그램 모두 명령줄 인수로 4와 2를 넣어 실행하면 다음 결과를 출력한다. + +``` +4.000000 ^ 2.000000 = 16.000000 +4.000000 % 2.000000 = 0.000000 +``` + +--- + +### 열거 타입에서 인터페이스를 이용해 확장 하는 경우 사소한 문제점 + +- 인터페이스를 이용해 확장 가능한 열거 타입을 흉내 내는 방식에는 `열거 타입끼리 구현을 상속할 수 없다`는 사소한 문제점이 있다. + +- 아무 상태에도 의존하지 않는 경우에는 디폴트 구현을 이용해 인터페이스에 추가하는 방법이 있다. 반면 Operation 예는 연산 기호를 저장하고 찾는 로직이 BasicOperation과 ExtendedOepration 모두에 들어가야만 한다. + +- 이 경우에는 중복량이 적으니 문제되진 않지만, 공유하는 기능이 많다면 그 부분을 별도의 도우미 클래스나 정적 도우미 메서드로 분리하는 방식으로 코드 중복을 없앨 수 있을 것이다. diff --git "a/Ch08/item50/\354\240\201\354\213\234\354\227\220_\353\260\251\354\226\264\354\240\201_\353\263\265\354\202\254\353\263\270\354\235\204_\353\247\214\353\223\244\353\235\274.md" "b/Ch08/item50/\354\240\201\354\213\234\354\227\220_\353\260\251\354\226\264\354\240\201_\353\263\265\354\202\254\353\263\270\354\235\204_\353\247\214\353\223\244\353\235\274.md" new file mode 100644 index 0000000..6b8de15 --- /dev/null +++ "b/Ch08/item50/\354\240\201\354\213\234\354\227\220_\353\260\251\354\226\264\354\240\201_\353\263\265\354\202\254\353\263\270\354\235\204_\353\247\214\353\223\244\353\235\274.md" @@ -0,0 +1,102 @@ +# Item 50. 적시에 방어적 복사본을 만들라 + +### 방어적으로 프로그래밍하기 + +- 아무리 자바라고 해도 다른 클래스로부터의 침범을 아무런 노력없이 막을 수 있는 것은 아님 +- 악의적인 의도를 가진 사람들이 시스템의 보안을 뚫으려는 시도가 늘고 있음 +- 평범한 프로그래머도 순전히 실수로 클래스를 오작동하게 만들 수 있음 + +**클라이언트가 \*불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍 해야 함** + +\*`불변식`이란 프로그래밍에서 특정 조건이 항상 참이라는 것을 보장하는 성질이다. 이를 통해 코드의 안정성과 예측 가능성을 높일 수 있다. + +- 예시 1: 배열의 크기가 한 번 정해지면 변경되지 않도록 하여, 배열의 크기를 초과하는 인덱스로의 접근을 방지하는 것이다. +- 예시 2: final 키워드를 사용하여 상수를 선언함으로써, 그 값을 다른 것으로 변경하는 것을 방지하는 것이다. + +방어적 프로그래밍의 관점에서 불변식을 지키는 것은 중요하다. + +클라이언트가 불변식을 깨뜨리려 할 경우, 이를 감지하고 예외를 발생시키는 것이 좋다. + +
+ +### 예시 1 + +```java +public final class Period { + private final Date start; + private final Date end; + + public Period(Date strart, Date end) { + if (start.compareTo(end) > 0) + throw new IllegalArgumentException( + start + "가 " + end + "보다 늦다."); + this.start = start; + this.end = end; + } + + public Date start() { + return start; + } + + public Date end() { + return end; + } + +} +``` + +```java +Date start = new Date(); +Date end = new Date(); +Period p = new Period(start, end); +end.setYear(78); +``` + +이렇게 바로 end의 내부를 불변식을 깨트릴 수 있다고 한다. + +_해당 Date 에 대해서 지금의 자바는 어떻게 해야할까?_ + +- Date 대신 불변인 Instant를 사용하면 된다. +- Java8 이상부터는 LocalDateTime이나 ZonedDateTime을 사용해도 된다. +- Date는 낡은 API이니 새로운 코드를 작성할 때는 더이상 사용하면 안된다. +- 이러한 Date 말고도 다른 상황에서도 불변식은 깨질 수 있다고 본다. + +
+ +### 예시 2 + +```java +Date start = new Date(); +Date end = new Date(); +Period p = new Period(start, end); +p.end().setYear(78) +``` + +Period에 대한 인스턴스 공격을 이런식으로도 깨트릴 수 있다. 하지만 막는 것은 간단하다. + +```java +public Date start() { + return new Date(start.getTime()); +} +public Date end() { + return new Date(end.getTime()); +} +``` + +바로 위와 같이 가변 필드의 방어적 복사본을 반환하면 된다. + +- 아무리 악의적인 혹은 부주의한 프로그래머라도 시작 시간이 종료 시간보다 나중일 수 없다는 불변식을 위배할 방법이 없다. +- Period 자신 말고는 가변 필드에 접근할 방법이 없다. +- 모든 필드가 객체 안에 완벽하게 캡슐화된다. +- 해당 방법을 사용할 때는 생성자와 달리 접근 메서드에 대해 복사본을 만드는 것이기 때문에 clone을 사용해도 된다. + - Period가 가지고 있는 Date 객체가 java.util.Date 임이 확실하기 때문 + +단, 인스턴스를 복사하는 데는 일반적으로 생성자나 정적 팩터리를 쓰는 것이 좋다. + +
+ +### 정리 + +- 클래스가 클라이언트로부터 받는 혹은 클라이언트로 반환하는 구성요소가 가변이라면 그 요소는 반드시 방어적으로 복사해야 함 + +- 복사 비용이 너무 크거나, 클라이언트가 그 요소를 잘못 수정할 일이 없음을 신뢰한다면 방어적 복사를 수행하는 대신 구성요소를 수정했을 때의 책임이 클라이언트에 있음을 문서에 명시해야 함