diff --git "a/docs/\354\225\204\354\235\264\355\205\234 25. \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/docs/\354\225\204\354\235\264\355\205\234 25. \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..2d74c9f --- /dev/null +++ "b/docs/\354\225\204\354\235\264\355\205\234 25. \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,35 @@ +# 아이템 25. 톱레벨 클래스는 한 파일에 하나만 담으라 + +## 핵심 정리 + +- `한 소스 파일에 톱 레벨 클래스를 여러 개 선언하면 컴파일 순서에 따라 결과가 달라질 수 있다.` + +```java +// 코드 25-1 두 클래스가 한 파일(Utensil.java)에 정의되었다. - 따라 하지 말 것! (150쪽) +class Utensil { + static final String NAME = "pan"; +} + +class Dessert { + static final String NAME = "cake"; +} +``` + +- `다른 클래스에 딸린 부차적인 클래스는 정적 멤버 클래스로 만드는 것이 낫다. 읽기 좋으며 private으로 선언해서 접근 범위도 최소한으로 관리할 수 있다.` + +```java +// 코드 25-3 톱레벨 클래스들을 정적 멤버 클래스로 바꿔본 모습 (151-152쪽) +public class Test { + public static void main(String[] args) { + System.out.println(Utensil.NAME + Dessert.NAME); + } + + private static class Utensil { + static final String NAME = "pan"; + } + + private static class Dessert { + static final String NAME = "cake"; + } +} +``` \ No newline at end of file diff --git "a/docs/\354\225\204\354\235\264\355\205\234 26. \353\241\234 \355\203\200\354\236\205\354\235\200 \354\202\254\354\232\251\355\225\230\354\247\200 \353\247\220\353\235\274.md" "b/docs/\354\225\204\354\235\264\355\205\234 26. \353\241\234 \355\203\200\354\236\205\354\235\200 \354\202\254\354\232\251\355\225\230\354\247\200 \353\247\220\353\235\274.md" new file mode 100644 index 0000000..159014a --- /dev/null +++ "b/docs/\354\225\204\354\235\264\355\205\234 26. \353\241\234 \355\203\200\354\236\205\354\235\200 \354\202\254\354\232\251\355\225\230\354\247\200 \353\247\220\353\235\274.md" @@ -0,0 +1,197 @@ +# 아이템 26. 로 타입은 사용하지 말라 + +## 핵심 정리 + +```java +public class GenericBasic { + + public static void main(String[] args) { + // Generic 사용하기 전 + List numbers = new ArrayList(); // List는 로 타입 + numbers.add(10); + numbers.add("whiteship"); + + for (Object number: numbers) { + System.out.println((Integer)number); + } + + // Generic 등장 이후 + List nuberms = new ArrayList<>(); + nuberms.add(10); + nuberms.add("whiteship"); + + for (Integer number: nuberms) { + System.out.println(number); + } + } +} +``` + +- `로 타입: List` +- `제네릭 타입: List` +- `매개변수화 타입: List` +- `타입 매개변수: E` +- `실제 타입 매개변수: String` +- `한정적 타입 매개변수: List` +- `비한정적 와일드카드 타입: Class` + - 아무런 타입을 받을 수 있다. +- `한정적 와일드카드 타입: Class` + +```java +public class Box { + + private E item; + + private void add(E e) { + this.item = e; + } + + private E get() { + return this.item; + } + + public static void main(String[] args) { + Box box = new Box<>(); + box.add(10); + System.out.println(box.get() * 100); + + printBox(box); + } + + private static void printBox(Box box) { + System.out.println(box.get()); + } + +} +``` + +- Box는 컴파일 시점에 타입이 사라진다. + - Box의 타입을 Object(로 타입으로)로 꺼내고 Integer로 타입 캐스팅 수행 + +`매개변수화 타입을 사용해야 하는 이유` + +- `런타임이 아닌 컴파일 타임에 문제를 찾을 수 있다. (안정성)` +- `제네릭을 활용하면 이 정보가 주석이 아닌 타입 선언 자체에 녹아든다. (표현력)` +- `“로 타입”을 사용하면 안정성과 표현력을 잃는다.` +- `그렇다면 자바는 “로 타입”을 왜 지원하는가?` + - 위 Box 예제에서 알 수 있듯이 컴파일러가 로 타입으로 꺼내는 이유는 하위 호환성을 유지하기 위해서이다. +- `List와 List의 차이는?` + +```java +// 코드 26-4 런타임에 실패한다. - unsafeAdd 메서드가 로 타입(List)을 사용 (156-157쪽) +public class Raw { + public static void main(String[] args) { + List strings = new ArrayList<>(); + unsafeAdd(strings, Integer.valueOf(42)); + String s = strings.get(0); // 컴파일러가 자동으로 형변환 코드를 넣어준다. + } + + private static void unsafeAdd(List list, Object o) { + list.add(o); + } +} +``` + +- String의 리스트는 Object의 리스트가 아니기 때문에 List로 선언하면 컴파일 에러가 발생한다. + - 즉, 배열을 **공변**이지만 제네릭은 **불공변** + - **공변**: 타입 A가 B의 하위 타입일 때, Foo는 Foo의 하위 타입인 경우 + - **불공변**: 타입 A가 B의 하위 타입일 때, Foo는 Foo의 하위 타입이 아니다. +- `Set과 Set의 차이는?` + +```java +public class Numbers { + + static int numElementsInCommon(Set s1, Set s2) { + int result = 0; + for (Object o1 : s1) { + if (s2.contains(o1)) { + result++; + } + } + + return result; + } + + public static void main(String[] args) { + System.out.println(Numbers.numElementsInCommon(Set.of(1, 2, 3), Set.of(1, 2))); + } +} +``` + +- Set과 같이 사용하면 컬렉션의 안정성이 깨진다. +- Set는 아무런 타입, 어떤 한 종류를 다루는 타입을 사용한다는 의미 +- 로 타입(Set)은 안정적이지 않고 어떠한 값이던 넣을 수 있지만, Set의 경우 안정성이 보장이되고 Set로 선언한 경우 null 이외의 값을 넣을 수 없다. +- `예외: class 리터럴과 instanceof 연산자` + +```java +public class UseRawType { + private E e; + + public static void main(String[] args) { + // UseRawType.class 이러한 클래스는 존재하지 않는다. + // 컴파일 시점에 소거되기 때문에 + // 반드시 로 타입의 클래스만 존재한다. + System.out.println(UseRawType.class); + + UseRawType stringType = new UseRawType<>(); + + System.out.println(stringType instanceof UseRawType); + // 컴파일은 되지만 소거되기 때문에 의미없다. + //System.out.println(stringType instanceof UseRawType); + } +} +``` + +- 클래스 타입은 로 타입의 클래스만 존재한다. + - 컴파일 시점에 타입은 소거되기 때문이다. + +## 완벽 공략 + +- `p156, 마이그레이션 호환성을 위해 로 타입을 지원하고 제네릭 구현에는 소거 방식을 사용하기로 했다. (아이템 28)` +- `p158, 제네릭 메서드 (아이템 30)` +- `p158, 한정적 와일드카드 타입 (아이템 31)` +- `Generic DAO 만들기` + +### 완벽 공략 40. GenericRepository + +`자바 Generic을 활용한 중복 코드 제거 예제` + +```java +public class AccountRepository { + + private Set accounts; + ... +} + +public class MessageRepository { + + private Set messages; + ... +} +``` + +```java +public class GenericRepository { + + private Set entities; + + public GenericRepository() { + this.entities = new HashSet<>(); + } + + public Optional findById(Long id) { + return entities.stream().filter(a -> a.getId().equals(id)).findAny(); + } + + public void add(E account) { + this.entities.add(account); + } +} + +// 하위 클래스 +public class GenericMessageRepository extends GenericRepository { +} + +public class GenericAccountRepository extends GenericRepository { +} +``` \ No newline at end of file diff --git "a/docs/\354\225\204\354\235\264\355\205\234 27. \353\271\204\352\262\200\354\202\254 \352\262\275\353\241\234\353\245\274 \354\240\234\352\261\260\355\225\230\353\235\274.md" "b/docs/\354\225\204\354\235\264\355\205\234 27. \353\271\204\352\262\200\354\202\254 \352\262\275\353\241\234\353\245\274 \354\240\234\352\261\260\355\225\230\353\235\274.md" new file mode 100644 index 0000000..8307b47 --- /dev/null +++ "b/docs/\354\225\204\354\235\264\355\205\234 27. \353\271\204\352\262\200\354\202\254 \352\262\275\353\241\234\353\245\274 \354\240\234\352\261\260\355\225\230\353\235\274.md" @@ -0,0 +1,83 @@ +# 아이템 27. 비검사 경로를 제거하라 + +## 핵심 정리 + +- `“비검사 (unchecked) 경고”란?` + - `컴파일러가 타입 안정성을 확인하는데 필요한 정보가 충분치 않을 때 발생시키는 경고.` + +```java +public class SetExample { + + public static void main(String[] args) { + Set names = new HashSet(); // 컴파일 경고! + + Set strings = new HashSet<>(); + } +} +``` + +- java7 부터 다이아몬드 연산자를 생략해도된다. + - `Set names = new HashSet()` 와 같이 타입을 반복하지 않아도 된다는 의미 new HashSet<>() +- `할 수 있는 한 모든 비검사 경고를 제거하라.` +- `경고를 제거할 수 없지만 안전하다고 확신한다면 @SuppressWarnings(“unchecked”) 애노테이션을 달아 경고를 숨기자.` +- `@SuppressWarnings 애너테이션은 항상 가능한 한 좁은 범위에 적용하자.` +- `@SuppressWarnings(“unchecked”) 애너테이션을 사용할 때면 그 경고를 무시해도 안전한 이유를 항상 주석으로 남겨야 한다.` + +```java +public class ListExample { + + private int size; + + Object[] elements; + + public T[] toArray(T[] a) { + if (a.length < size) { + /** + * 이 애노테이션을 왜 여기서 선언했는지.. + */ + @SuppressWarnings("unchecked") + T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass()); + return result; + } + System.arraycopy(elements, 0, a, 0, size); + if (a.length > size) + a[size] = null; + return a; + } +} +``` + +- 내가 이미 알고 있는 경고라면 @SuppressWarnings를 선언해서 경고를 없애주자. + +## 완벽공략 + +### 완벽 공략 41. 애너테이션 + +`자바 애너테이션을 정의하는 방법` + +- `@Retention: 애노테이션의 정보를 얼마나 오래 유지할 것인가.` + - `Runtime, Source, Class` +- `@Target: 애노테이션을 사용할 수 있는 위치.` + - `Type, Field, Method, Parameter, …` + +```java +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface MyAnnotation { +} +``` + +- `@Documented`: javadoc 생성시 해당 애너테이션를 포함해서 생성하게 한다. + - Retention.Source로 선언하게되면 리플랙션, 바이트코드 조작을 통해 정보를 가져오는 것이 불가하다. + - 그 외 Runtime, Class는 리플랙션, 바이트코드 조작으로 정보를 가져오는 것이 가능하다는 의미 + +```java +@MyAnnotation +public class MyClass { + + @MyAnnotation + public static void main(String[] args) { + Arrays.stream(MyClass.class.getAnnotations()).forEach(System.out::println); + } +} +``` \ No newline at end of file diff --git a/src/main/java/item25/Dessert.java b/src/main/java/item25/Dessert.java new file mode 100644 index 0000000..aa20339 --- /dev/null +++ b/src/main/java/item25/Dessert.java @@ -0,0 +1,10 @@ +package item25; + +// 코드 25-2 두 클래스가 한 파일(Dessert.java)에 정의되었다. - 따라 하지 말 것! (151쪽) +//class Utensil { +// static final String NAME = "pot"; +//} +// +//class Dessert { +// static final String NAME = "pie"; +//} diff --git a/src/main/java/item25/Main.java b/src/main/java/item25/Main.java new file mode 100644 index 0000000..fddff4d --- /dev/null +++ b/src/main/java/item25/Main.java @@ -0,0 +1,8 @@ +package item25; + +// (150쪽) +public class Main { + public static void main(String[] args) { + System.out.println(Utensil.NAME + Dessert.NAME); + } +} diff --git a/src/main/java/item25/Test.java b/src/main/java/item25/Test.java new file mode 100644 index 0000000..7db76cc --- /dev/null +++ b/src/main/java/item25/Test.java @@ -0,0 +1,16 @@ +package item25; + +// 코드 25-3 톱레벨 클래스들을 정적 멤버 클래스로 바꿔본 모습 (151-152쪽) +public class Test { + public static void main(String[] args) { + System.out.println(Utensil.NAME + Dessert.NAME); + } + + private static class Utensil { + static final String NAME = "pan"; + } + + private static class Dessert { + static final String NAME = "cake"; + } +} diff --git a/src/main/java/item25/Utensil.java b/src/main/java/item25/Utensil.java new file mode 100644 index 0000000..da70555 --- /dev/null +++ b/src/main/java/item25/Utensil.java @@ -0,0 +1,10 @@ +package item25; + +// 코드 25-1 두 클래스가 한 파일(Utensil.java)에 정의되었다. - 따라 하지 말 것! (150쪽) +class Utensil { + static final String NAME = "pan"; +} + +class Dessert { + static final String NAME = "cake"; +} diff --git a/src/main/java/item26/genericdao/Account.java b/src/main/java/item26/genericdao/Account.java new file mode 100644 index 0000000..ec54dd5 --- /dev/null +++ b/src/main/java/item26/genericdao/Account.java @@ -0,0 +1,22 @@ +package item26.genericdao; + +public class Account implements Entity { + + private Long id; + + private String username; + + public Account(Long id, String username) { + this.id = id; + this.username = username; + } + + @Override + public Long getId() { + return this.id; + } + + public String getUsername() { + return username; + } +} diff --git a/src/main/java/item26/genericdao/AccountRepository.java b/src/main/java/item26/genericdao/AccountRepository.java new file mode 100644 index 0000000..b788ae7 --- /dev/null +++ b/src/main/java/item26/genericdao/AccountRepository.java @@ -0,0 +1,23 @@ +package item26.genericdao; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +public class AccountRepository { + + private Set accounts; + + public AccountRepository() { + this.accounts = new HashSet<>(); + } + + public Optional findById(Long id) { + return accounts.stream().filter(a -> a.getId().equals(id)).findAny(); + } + + public void add(Account account) { + this.accounts.add(account); + } +} + diff --git a/src/main/java/item26/genericdao/Entity.java b/src/main/java/item26/genericdao/Entity.java new file mode 100644 index 0000000..4ae1c07 --- /dev/null +++ b/src/main/java/item26/genericdao/Entity.java @@ -0,0 +1,6 @@ +package item26.genericdao; + +public interface Entity { + + Long getId(); +} diff --git a/src/main/java/item26/genericdao/GenericAccountRepository.java b/src/main/java/item26/genericdao/GenericAccountRepository.java new file mode 100644 index 0000000..161d8a0 --- /dev/null +++ b/src/main/java/item26/genericdao/GenericAccountRepository.java @@ -0,0 +1,5 @@ +package item26.genericdao; + +public class GenericAccountRepository extends GenericRepository { +} + diff --git a/src/main/java/item26/genericdao/GenericMessageRepository.java b/src/main/java/item26/genericdao/GenericMessageRepository.java new file mode 100644 index 0000000..d1bd38a --- /dev/null +++ b/src/main/java/item26/genericdao/GenericMessageRepository.java @@ -0,0 +1,4 @@ +package item26.genericdao; + +public class GenericMessageRepository extends GenericRepository { +} diff --git a/src/main/java/item26/genericdao/GenericRepository.java b/src/main/java/item26/genericdao/GenericRepository.java new file mode 100644 index 0000000..5a65f5d --- /dev/null +++ b/src/main/java/item26/genericdao/GenericRepository.java @@ -0,0 +1,23 @@ +package item26.genericdao; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +public class GenericRepository { + + private Set entities; + + public GenericRepository() { + this.entities = new HashSet<>(); + } + + public Optional findById(Long id) { + return entities.stream().filter(a -> a.getId().equals(id)).findAny(); + } + + public void add(E account) { + this.entities.add(account); + } +} + diff --git a/src/main/java/item26/genericdao/Message.java b/src/main/java/item26/genericdao/Message.java new file mode 100644 index 0000000..9b90766 --- /dev/null +++ b/src/main/java/item26/genericdao/Message.java @@ -0,0 +1,17 @@ +package item26.genericdao; + +public class Message implements Entity { + + private Long id; + + private String body; + + @Override + public Long getId() { + return id; + } + + public String getBody() { + return body; + } +} diff --git a/src/main/java/item26/genericdao/MessageRepository.java b/src/main/java/item26/genericdao/MessageRepository.java new file mode 100644 index 0000000..b93e995 --- /dev/null +++ b/src/main/java/item26/genericdao/MessageRepository.java @@ -0,0 +1,22 @@ +package item26.genericdao; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +public class MessageRepository { + + private Set messages; + + public MessageRepository() { + this.messages = new HashSet<>(); + } + + public Optional findById(Long id) { + return messages.stream().filter(a -> a.getId().equals(id)).findAny(); + } + + public void add(Message message) { + this.messages.add(message); + } +} diff --git a/src/main/java/item26/object/Raw.java b/src/main/java/item26/object/Raw.java new file mode 100644 index 0000000..baa061a --- /dev/null +++ b/src/main/java/item26/object/Raw.java @@ -0,0 +1,18 @@ +package item26.object; + +import java.util.ArrayList; +import java.util.List; + +// 코드 26-4 런타임에 실패한다. - unsafeAdd 메서드가 로 타입(List)을 사용 (156-157쪽) +public class Raw { + public static void main(String[] args) { + List strings = new ArrayList<>(); + unsafeAdd(strings, Integer.valueOf(42)); + String s = strings.get(0); // 컴파일러가 자동으로 형변환 코드를 넣어준다. + } + + private static void unsafeAdd(List list, Object o) { + list.add(o); + } +} + diff --git a/src/main/java/item26/raw/UseRawType.java b/src/main/java/item26/raw/UseRawType.java new file mode 100644 index 0000000..afbfdfc --- /dev/null +++ b/src/main/java/item26/raw/UseRawType.java @@ -0,0 +1,18 @@ +package item26.raw; + +public class UseRawType { + private E e; + + public static void main(String[] args) { + // UseRawType.class 이러한 클래스는 존재하지 않는다. + // 컴파일 시점에 소거되기 때문에 + // 반드시 로 타입의 클래스만 존재한다. + System.out.println(UseRawType.class); + + UseRawType stringType = new UseRawType<>(); + + System.out.println(stringType instanceof UseRawType); + // 컴파일은 되지만 소거되기 때문에 의미없다. + //System.out.println(stringType instanceof UseRawType); + } +} diff --git a/src/main/java/item26/terms/Box.java b/src/main/java/item26/terms/Box.java new file mode 100644 index 0000000..2200074 --- /dev/null +++ b/src/main/java/item26/terms/Box.java @@ -0,0 +1,27 @@ +package item26.terms; + +public class Box { + + private E item; + + private void add(E e) { + this.item = e; + } + + private E get() { + return this.item; + } + + public static void main(String[] args) { + Box box = new Box<>(); + box.add(10); + System.out.println(box.get() * 100); + + printBox(box); + } + + private static void printBox(Box box) { + System.out.println(box.get()); + } + +} diff --git a/src/main/java/item26/terms/GenericBasic.java b/src/main/java/item26/terms/GenericBasic.java new file mode 100644 index 0000000..d3795a8 --- /dev/null +++ b/src/main/java/item26/terms/GenericBasic.java @@ -0,0 +1,24 @@ +package item26.terms; + +public class GenericBasic { + + public static void main(String[] args) { + // Generic 사용하기 전 +// List numbers = new ArrayList(); // List는 로 타입 +// numbers.add(10); +// numbers.add("whiteship"); +// +// for (Object number: numbers) { +// System.out.println((Integer)number); +// } + + // Generic 등장 이후 +// List nuberms = new ArrayList<>(); +// nuberms.add(10); +// nuberms.add("whiteship"); +// +// for (Integer number: nuberms) { +// System.out.println(number); +// } + } +} diff --git a/src/main/java/item26/unbounded/Numbers.java b/src/main/java/item26/unbounded/Numbers.java new file mode 100644 index 0000000..86131fd --- /dev/null +++ b/src/main/java/item26/unbounded/Numbers.java @@ -0,0 +1,21 @@ +package item26.unbounded; + +import java.util.Set; + +public class Numbers { + + static int numElementsInCommon(Set s1, Set s2) { + int result = 0; + for (Object o1 : s1) { + if (s2.contains(o1)) { + result++; + } + } + + return result; + } + + public static void main(String[] args) { + System.out.println(Numbers.numElementsInCommon(Set.of(1, 2, 3), Set.of(1, 2))); + } +} diff --git a/src/main/java/item27/annotation/MyAnnotation.java b/src/main/java/item27/annotation/MyAnnotation.java new file mode 100644 index 0000000..2011d6e --- /dev/null +++ b/src/main/java/item27/annotation/MyAnnotation.java @@ -0,0 +1,12 @@ +package item27.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface MyAnnotation { + +} diff --git a/src/main/java/item27/annotation/MyClass.java b/src/main/java/item27/annotation/MyClass.java new file mode 100644 index 0000000..cc78106 --- /dev/null +++ b/src/main/java/item27/annotation/MyClass.java @@ -0,0 +1,12 @@ +package item27.annotation; + +import java.util.Arrays; + +@MyAnnotation +public class MyClass { + + @MyAnnotation + public static void main(String[] args) { + Arrays.stream(MyClass.class.getAnnotations()).forEach(System.out::println); + } +} diff --git a/src/main/java/item27/suppress/ListExample.java b/src/main/java/item27/suppress/ListExample.java new file mode 100644 index 0000000..fb3505a --- /dev/null +++ b/src/main/java/item27/suppress/ListExample.java @@ -0,0 +1,25 @@ +package item27.suppress; + +import java.util.Arrays; + +public class ListExample { + + private int size; + + Object[] elements; + + public T[] toArray(T[] a) { + if (a.length < size) { + /** + * 이 애노테이션을 왜 여기서 선언했는지.. + */ + @SuppressWarnings("unchecked") + T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass()); + return result; + } + System.arraycopy(elements, 0, a, 0, size); + if (a.length > size) + a[size] = null; + return a; + } +} diff --git a/src/main/java/item27/unchecked/SetExample.java b/src/main/java/item27/unchecked/SetExample.java new file mode 100644 index 0000000..f62e2cd --- /dev/null +++ b/src/main/java/item27/unchecked/SetExample.java @@ -0,0 +1,13 @@ +package item27.unchecked; + +import java.util.HashSet; +import java.util.Set; + +public class SetExample { + + public static void main(String[] args) { + Set names = new HashSet(); // 컴파일 경고! + + Set strings = new HashSet<>(); + } +}