diff --git "a/docs/\354\225\204\354\235\264\355\205\234 31. \355\225\234\354\240\225\354\240\201 \354\231\200\354\235\274\353\223\234\354\271\264\353\223\234\353\245\274 \354\202\254\354\232\251\355\225\264 API \354\234\240\354\227\260\354\204\261\354\235\204 \353\206\222\354\235\264\353\235\274.md" "b/docs/\354\225\204\354\235\264\355\205\234 31. \355\225\234\354\240\225\354\240\201 \354\231\200\354\235\274\353\223\234\354\271\264\353\223\234\353\245\274 \354\202\254\354\232\251\355\225\264 API \354\234\240\354\227\260\354\204\261\354\235\204 \353\206\222\354\235\264\353\235\274.md" new file mode 100644 index 0000000..2785537 --- /dev/null +++ "b/docs/\354\225\204\354\235\264\355\205\234 31. \355\225\234\354\240\225\354\240\201 \354\231\200\354\235\274\353\223\234\354\271\264\353\223\234\353\245\274 \354\202\254\354\232\251\355\225\264 API \354\234\240\354\227\260\354\204\261\354\235\204 \353\206\222\354\235\264\353\235\274.md" @@ -0,0 +1,273 @@ +# 아이템 31. 한정적 와일드카드를 사용해 API 유연성을 높이라 + +## 핵심 정리 + +### 핵심 정리 1: Chooser와 Union API 개선 + +`PECS: Producer-Extends, Consumer-Super` + +- `Producer-Extends` + - `Object의 컬렉션 Number나 Integer를 넣을 수 있다.` + - `Number의 컬렉션에 Integer를 넣을 수 있다.` + - 값을 받아서 저장하는 것은 Producer-Extends + +```java +// 코드 31-1 와일드카드 타입을 사용하지 않은 pushAll 메서드 - 결함이 있다! (181쪽) +public void pushAll(Iterable src) { + for (E e : src) + push(e); +} + +// 코드 31-2 E 생산자(producer) 매개변수에 와일드카드 타입 적용 (182쪽) +public void pushAll(Iterable src) { + for (E e : src) + push(e); +} + +// 제네릭 Stack을 사용하는 맛보기 프로그램 +public static void main(String[] args) { + Stack numberStack = new Stack<>(); + Iterable integers = Arrays.asList(3, 1, 4, 1, 5, 9); + numberStack.pushAll(integers); + + Iterable doubles = Arrays.asList(3.1, 1.0, 4.0, 1.0, 5.0, 9.0); + numberStack.pushAll(doubles); + + ... + + System.out.println(objects); +} +``` + +- 는 한정적 타입 +- 는 한정적 와일드카드 + - ?에 Number를 상속받은 어떠한 타입이던 들어갈 수 있다. +- 와 같이 선언하면? E 타입을 상속받은 어떠한 타입이던 들어갈 수 있다. + - 유연하게 처리할 수 있다. +- `Consumer-Super` + - `Integer의 컬렉션의 객체를 꺼내서 Number의 컬렉션에 담을 수 있다.` + - `Number나 Integer의 컬렉션의 객체를 꺼내서 Object의 컬렉션에 담을 수 있다.` + - 값을 꺼내서 전달해주는 로직은 Consumer-Super + +```java +// 코드 31-3 와일드카드 타입을 사용하지 않은 popAll 메서드 - 결함이 있다! (183쪽) +public void popAll(Collection dst) { + while (!isEmpty()) + dst.add(pop()); +} + +// 코드 31-4 E 소비자(consumer) 매개변수에 와일드카드 타입 적용 (183쪽) +public void popAll(Collection dst) { + while (!isEmpty()) + dst.add(pop()); +} + +// 제네릭 Stack을 사용하는 맛보기 프로그램 +public static void main(String[] args) { + Stack numberStack = new Stack<>(); + ... + + Collection objects = new ArrayList<>(); + numberStack.popAll(objects); + + System.out.println(objects); +} +``` + +### 핵심 정리 2: Comparator와 Comparable은 소비자 + +- `Comparable을 직접 구현하지 않고, 직접 구현한 다른 타입을 확장한 타입을 지원하려면 와일드카드가 필요하다.` +- `ScheduledFuture는 Comparable을 직접 구현하지 않았지만, 그 상위 타입 (Delayed)이 구현하고 있다.` + +**Producer-Extends** + +```java +// T 생산자 매개변수에 와일드카드 타입 적용 (184쪽) +public class Chooser { + private final List choiceList; + private final Random rnd = new Random(); + + // 코드 31-5 T 생산자 매개변수에 와일드카드 타입 적용 (184쪽) + public Chooser(Collection choices) { + choiceList = new ArrayList<>(choices); + } + + public T choose() { + return choiceList.get(rnd.nextInt(choiceList.size())); + } + + public static void main(String[] args) { + List intList = List.of(1, 2, 3, 4, 5, 6); + Chooser chooser = new Chooser<>(intList); + for (int i = 0; i < 10; i++) { + Number choice = chooser.choose(); + System.out.println(choice); + } + } +} +``` + +```java +// 코드 30-2의 제네릭 union 메서드에 와일드카드 타입을 적용해 유연성을 높였다. (185-186쪽) +public class Union { + public static Set union(Set s1, + Set s2) { + Set result = new HashSet<>(s1); + result.addAll(s2); + return result; + } + + // 향상된 유연성을 확인해주는 맛보기 프로그램 (185쪽) + public static void main(String[] args) { + Set integers = new HashSet<>(); + integers.add(1); + integers.add(3); + integers.add(5); + + Set doubles = new HashSet<>(); + doubles.add(2.0); + doubles.add(4.0); + doubles.add(6.0); + + Set numbers = union(integers, doubles); + +// // 코드 31-6 자바 7까지는 명시적 타입 인수를 사용해야 한다. (186쪽) +// Set numbers = Union.union(integers, doubles); + + System.out.println(numbers); + } +} +``` + +**Consumer-Super** + +```java +public class IntegerBox extends Box {} + +public class Box> implements Comparable> {} +``` + +```java +// 와일드카드 타입을 사용해 재귀적 타입 한정을 다듬었다. (187쪽) +public class RecursiveTypeBound { + public static > E max(List list) { + if (list.isEmpty()) + throw new IllegalArgumentException("빈 리스트"); + + E result = null; + for (E e : list) + if (result == null || e.compareTo(result) > 0) + result = e; + + return result; + } + + public static void main(String[] args) { + List list = new ArrayList<>(); + list.add(new IntegerBox(10, "effective")); + list.add(new IntegerBox(2, "java")); + + System.out.println(max(list)); + } +} +``` + +- IntegerBox 타입은 Comparable를 구현하지 않았지만 상위 타입(Box)은 구현한 경우가 있을 수 있다. +- 값을 꺼내서 처리하는 로직이기 때문에 Consumer-Super + +### 핵심 정리 3: 와일드카드 활용 팁 + +- `메서드 선언에 타입 매개변수가 한 번만 나오면 와일드카드로 대체하라.` + - `한정적 타입이라면 한정적 와일드카드로` + - `비한정적 타입이라면 비한정적 와일드카드로` + +```java +// 와일드카드 타입을 실제 타입으로 바꿔주는 private 도우미 메서드 (189쪽) +public class Swap { + + public static void swap(List list, int i, int j) { +// public static void swap(List list, int i, int j) { + list.set(i, list.set(j, list.get(i))); +// swapHelper(list, i, j); + } + + // 와일드카드 타입을 실제 타입으로 바꿔주는 private 도우미 메서드 +// private static void swapHelper(List list, int i, int j) { +// list.set(i, list.set(j, list.get(i))); +// } + + public static void main(String[] args) { + // 첫 번째와 마지막 인수를 스왑한 후 결과 리스트를 출력한다. + List argList = Arrays.asList(args); + swap(argList, 0, argList.size() - 1); + System.out.println(argList); + } +} +``` + +- `E`: 특정할 수 있는 타입 + - 타입을 안다! +- `?`: 특정할 수 없는 임의의 타입 + - 비한정적 와일드 카드 + - 타입을 모른다! +- `주의!` + - `비한정적 와일드카드(?)로 정의한 타입에는 null을 제외한 아무것도 넣을 수 없다.` + +## 완벽 공략 + +### 완벽 공략 44. Type Inference + +- `타입을 추론하는 컴파일러의 기능` + - 타입 추론이란 어떤 타입을 쓸지 컴파일러가 알아내는 것 + - 다이아몬드 연산자를 사용하면 컴파일러가 알아서 타입 추론를 해준다. +- `모든 인자의 가장 구체적인 공통 타입 (most specific type)` +- `제네릭 메서드와 타입 추론: 메서드 매개변수를 기반으로 타입 매개변수를 추론할 수 있다.` +- `제네릭 클래스 생성자를 호출할 때 다이아몬드 연산자 <>를 사용하면 타입을 추론한다.` +- `자바 컴파일러는 "타겟 타입"을 기반으로 호출하는 제네릭 메서드의 타입 매개볒수를 추론한다.` + - `자바 8에서 "타겟 타입"이 "메서드의 인자"까지 확장되면서 이전에 비해 타입 추론이 강화되었다.` + +```java +public class BoxExample { + + private static void addBox(U u, List> boxes) { + Box box = new Box<>(); + box.set(u); + boxes.add(box); + } + + private static void outputBoxes(List> boxes) { + int counter = 0; + for (Box box: boxes) { + U boxContents = box.get(); + System.out.println("Box #" + counter + " contains [" + + boxContents.toString() + "]"); + counter++; + } + } + + private static void processStringList(List stringList) { + + } + + public static void main(String[] args) { + ArrayList> listOfIntegerBoxes = new ArrayList<>(); + BoxExample.addBox(10, listOfIntegerBoxes); + BoxExample.addBox(20, listOfIntegerBoxes); + BoxExample.addBox(30, listOfIntegerBoxes); + BoxExample.outputBoxes(listOfIntegerBoxes); + + // Target Type + List stringlist = Collections.emptyList(); + List integerlist = Collections.emptyList(); + BoxExample.processStringList(Collections.emptyList()); + } +} +``` + +- `BoxExample.addBox()`와 같이 사용할 수 있다. + - 이를 공식문서에서는 type witness(?) 라고한단다.. + - java7에서는 위와 같이 선언이 필요하다. + +# References + +- 타입 추론: [https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html](https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html) \ No newline at end of file diff --git "a/docs/\354\225\204\354\235\264\355\205\234 32. \354\240\234\353\204\244\353\246\255\352\263\274 \352\260\200\353\263\200\354\235\270\354\210\230\353\245\274 \355\225\250\352\273\230 \354\223\270 \353\225\214\353\212\224 \354\213\240\354\244\221\355\225\230\353\235\274.md" "b/docs/\354\225\204\354\235\264\355\205\234 32. \354\240\234\353\204\244\353\246\255\352\263\274 \352\260\200\353\263\200\354\235\270\354\210\230\353\245\274 \355\225\250\352\273\230 \354\223\270 \353\225\214\353\212\224 \354\213\240\354\244\221\355\225\230\353\235\274.md" new file mode 100644 index 0000000..43da067 --- /dev/null +++ "b/docs/\354\225\204\354\235\264\355\205\234 32. \354\240\234\353\204\244\353\246\255\352\263\274 \352\260\200\353\263\200\354\235\270\354\210\230\353\245\274 \355\225\250\352\273\230 \354\223\270 \353\225\214\353\212\224 \354\213\240\354\244\221\355\225\230\353\235\274.md" @@ -0,0 +1,214 @@ +# 아이템 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라 + +## 핵심 정리 + +> 배열은 공변이고 제네릭은 불공변이고 +배열은 실체화가 되지만 제네릭은 실체화가 되지 않는다. +따라서 제네릭 배열을 사용하면 여러 문제가 발생할 수 있다. +> +- `제네릭 가변인수 배열에 값을 저장하는 것은 안전하지 않다.` + - `힙 오염이 발생할 수 있다. (컴파일 경고 발생)` + - `자바7에 추가된 @SafeVarargs 애노테이션을 사용할 수 있다.` + +```java +// 제네릭 varargs 배열 매개변수에 값을 저장하는 것은 안전하지 않다. (191-192쪽) +public class Dangerous { + // 코드 32-1 제네릭과 varargs를 혼용하면 타입 안전성이 깨진다! (191-192쪽) + static void dangerous(List... stringLists) { + List intList = List.of(42); + Object[] objects = stringLists; + objects[0] = intList; // 힙 오염 발생 + String s = stringLists[0].get(0); // ClassCastException + } + + public static void main(String[] args) { + dangerous(List.of("There be dragons!")); + } +} +``` + +- List... stringLists은 컴파일 시점에 List[]와 같은 형태가 된다. +- **제네릭을 사용하는 이유는 컴파일부터 런타임까지 타입 안정성을 보장하기 위해 사용**했지만 제네릭과 가변인자를 사용하게 되면 힙 오염이 발생할 수 있다. +- `제네릭 가변인수 배열의 참조를 밖으로 노출하면 힙 오염을 전달할 수 있다.` + - `예외적으로, @SafeVarargs를 사용한 메서드에 넘기는 것은 안전하다.` + - `예외적으로, 배열의 내용의 일부 함수를 호출하는 일반 메서드로 넘기는 것은 안전하다.` + +```java +// 코드 32-3 제네릭 varargs 매개변수를 안전하게 사용하는 메서드 (195쪽) +public class FlattenWithVarargs { + + @SafeVarargs + static List flatten(List... lists) { + List result = new ArrayList<>(); + for (List list : lists) + result.addAll(list); + return result; + } + + public static void main(String[] args) { + List flatList = flatten( + List.of(1, 2), List.of(3, 4, 5), List.of(6,7)); + System.out.println(flatList); + } +} +``` + +```java +// 미묘한 힙 오염 발생 (193-194쪽) +public class PickTwo { + // 코드 32-2 자신의 제네릭 매개변수 배열의 참조를 노출한다. - 안전하지 않다! (193쪽) + static T[] toArray(T... args) { + return args; + } + + static T[] pickTwo(T a, T b, T c) { + switch(ThreadLocalRandom.current().nextInt(3)) { + case 0: return toArray(a, b); + case 1: return toArray(a, c); + case 2: return toArray(b, c); + } + throw new AssertionError(); // 도달할 수 없다. + } + + public static void main(String[] args) { // (194쪽) + String[] attributes = pickTwo("좋은", "빠른", "저렴한"); + System.out.println(Arrays.toString(attributes)); + } +} +``` + +- 가변인자 리스트에서 값을 꺼내서 사용하는 것은 안전하지만 값을 넣는 것은 불안정해질 수 있다. +- `아이템 28의 조언에 따라 가변인수를 List로 바꾼다면` + - `배열없이 제니릭만 사용하므로 컴파일러가 타입 안정성을 보장할 수 있다.` + - `@SafeVarargs 애너테이션을 사용할 필요가 없다.` + - `실수로 안전하다고 판단할 걱정도 없다.` + +```java +// 코드 32-4 제네릭 varargs 매개변수를 List로 대체한 예 - 타입 안전하다. (195-196쪽) +public class FlattenWithList { + static List flatten(List> lists) { + List result = new ArrayList<>(); + for (List list : lists) + result.addAll(list); + return result; + } + + public static void main(String[] args) { + List flatList = flatten(List.of( + List.of(1, 2), List.of(3, 4, 5), List.of(6,7))); + System.out.println(flatList); + } +} +``` + +```java +// 배열 대신 List를 이용해 안전하게 바꿘 PickTwo (196쪽) +public class SafePickTwo { + static List pickTwo(T a, T b, T c) { + switch(ThreadLocalRandom.current().nextInt(3)) { + case 0: return List.of(a, b); + case 1: return List.of(a, c); + case 2: return List.of(b, c); + } + throw new AssertionError(); + } + + public static void main(String[] args) { + List attributes = pickTwo("좋은", "빠른", "저렴한"); + System.out.println(attributes); + } +} +``` + +## 완벽 공략 + +### 완벽 공략 45. ThreadLocal + +`쓰레드 지역 변수` + +- `모든 멤버 변수는 기본적으로 여러 쓰레드에서 공유해서 쓰일 수 있다. 이때 쓰레드 안전성과 관련된 여러 문제가 발생할 수 있다.` + - `경합 또는 경쟁조건 (Race-Condition)` + - 스레드A, B가 동일한 자원에 대해서 값을 변경한다고 할 때, A는 1이라는 값으로 B는 2라는 값으로 변경하는 상황 + - `교착상태 (deadlock)` + - 스레드가 특정 자원에 접근하고 작업을 완료할 때까지 다른 스레드가 해당 자원에 접근하지 못하는 상황에서 작업이 완료되기를 무한히 기다리는 상태 + - `Livelock` + - 스레드간에 서로 락만 주고 받다가 작업을 수행하지 못하는 상태 +- `쓰레드 지역 변수를 사용하면 동기화를 하지 않아도 한 쓰레드에서만 접근 가능한 값이기 때문에 안전하게 사용할 수 있다.` +- `한 쓰레드 내에서 공유하는 데이터로, 메서드 매개변수에 매번 전달하지 않고 전역 변수처럼 사용할 수 있다.` + +```java +public class ThreadLocalExample implements Runnable { + + // SimpleDateFormat is not thread-safe, so give one to each thread + private static final ThreadLocal formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm")); + +// private SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd HHmm"); + + public static void main(String[] args) throws InterruptedException { + ThreadLocalExample obj = new ThreadLocalExample(); + for (int i = 0; i < 10; i++) { + Thread t = new Thread(obj, "" + i); + Thread.sleep(new Random().nextInt(1000)); + t.start(); + } + } + + @Override + public void run() { + System.out.println("Thread Name= " + Thread.currentThread().getName() + " default Formatter = " + formatter.get().toPattern()); + try { + Thread.sleep(new Random().nextInt(1000)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + //formatter pattern is changed here by thread, but it won't reflect to other threads + formatter.set(new SimpleDateFormat()); + + System.out.println("Thread Name= " + Thread.currentThread().getName() + " formatter = " + formatter.get().toPattern()); + } +} +``` + +### 완벽 공략 46. ThreadLocalRandom + +`스레드 지역 랜덤값 생성기` + +- `java.util.Random은 멀티 스레드 환경에서 CAS(CompareAndSet)로 인해 실패 할 가능성이 있기 때문에 성능이 좋지 않다.` +- `Random 대신 ThreadLocalRandom을 사용하면 해당 스레드 전용 Random 이라 간섭이 발생하지 않는다.` + +```java +public class RandomExample { + + public static void main(String[] args) { + Random random = new Random(); + System.out.println(random.nextInt(10)); + + ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current(); + System.out.println(threadLocalRandom.nextInt(10)); + } + + private int value; + + public synchronized int compareAndSwap(int expectedValue, int newValue) { + int readValue = value; + if (readValue == expectedValue) + value = newValue; + return readValue; + } +} +``` + +- AtomicLong(concurrent 패키지) + - 멀티 스레드 환경에서 Lock를 사용하지 않고 안전하게 사용할 수 있는 클래스 + - 낙관적인 락 방식을 사용한다. +- synchronize lock == pessimistic(명세적인, 비관적인) + - 누가 자원에 접근하고 있으면 다른 스레드는 작업이 완료될 때까지 대기하는 Lock +- 위 코드는 내가 기대한 값이 같은 경우에만 값을 바꾸는 기능이다. +- Random()은 Optimistic Lock 방식으로 스레드 안전하게 구현돼있다. + - 일단 모든 스레드들이 자원에 접근하고 기대하는 값과 일치하는 경우에 값을 변경하게 하는…(아예 자원에 접근이 불가한 pessimistic 방식이 아님) + - 다만 두 스레드 중 한 스레드는 실패하게 되고 계속적인 재시도로 인해 성능 문제가 있다. 이를 위한 대안으로 ThreadLocalRandom을 사용하는 것을 권장한다. + +# References + +- ThreadLocal: [https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html](https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html) +- ThreadLocalRandom: [https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadLocalRandom.html](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadLocalRandom.html) \ No newline at end of file diff --git "a/docs/\354\225\204\354\235\264\355\205\234 33. \355\203\200\354\236\205 \354\225\210\354\240\204 \354\235\264\354\242\205 \354\273\250\355\205\214\354\235\264\353\204\210\353\245\274 \352\263\240\353\240\244\355\225\230\353\235\274.md" "b/docs/\354\225\204\354\235\264\355\205\234 33. \355\203\200\354\236\205 \354\225\210\354\240\204 \354\235\264\354\242\205 \354\273\250\355\205\214\354\235\264\353\204\210\353\245\274 \352\263\240\353\240\244\355\225\230\353\235\274.md" new file mode 100644 index 0000000..d40daec --- /dev/null +++ "b/docs/\354\225\204\354\235\264\355\205\234 33. \355\203\200\354\236\205 \354\225\210\354\240\204 \354\235\264\354\242\205 \354\273\250\355\205\214\354\235\264\353\204\210\353\245\274 \352\263\240\353\240\244\355\225\230\353\235\274.md" @@ -0,0 +1,218 @@ +# 아이템 33. 타입 안전 이종 컨테이너를 고려하라 + +## 핵심 정리 + +### 핵심 정리: 타입 토큰을 사용한 타입 안전 이종 컨테이너 + +- `타입 안전 이종 컨테이너: 한 타입의 객체만 담을 수 있는 컨테이너가 아니라 여러 다른 타입 (이종)을 담을 수 있는 타입 안전한 컨테이너.` + - **컨테이너**란 객체를 담을 수 있는 클래스를 의미 + - **이종 컨테이너**란 서로 다른 타입을 넣을 수 있도록하는 것을 의미 +- `타입 토큰: String.class 또는 Class` +- `타입 안전 이종 컨테이너 구현 방법: 컨테이너가 아니라 "키"를 매개변수화 하라!` + +```java +public class Favorites { + private Map, Object> map = new HashMap<>(); + + public void put(Class clazz, T value) { + this.map.put(Objects.requireNonNull(clazz), clazz.cast(value)); + } + + public T get(Class clazz) { + return clazz.cast(this.map.get(clazz)); + } + + public static void main(String[] args) { + Favorites favorites = new Favorites(); + favorites.put(String.class, "foo"); + favorites.put(Integer.class, 2); + +// favorites.put(List.class, List.of(1, 2, 3)); +// favorites.put(List.class, List.of("a", "b", "c")); + +// List list = favorites.get(List.class); +// list.forEach(System.out::println); + } +} +``` + +- Integer.class와 같은 형태를 클래스 리터럴이라고 한다. +- favorites.put((Class) String.class, 1) 와 같이 값을 넣는 경우 타입 안정성을 깨뜨릴 수 있다. + - 값을 꺼낼때 ClassCastException 발생 + - 값을 넣을때 값과 타입을 한번더 검증하는 로직 추가하는 것으로 방어 + - 컴파일 타임에 잡는 것은 어려움 + +### 핵심 정리: 한정적 타입 토큰 + +- `한정적 타입 토큰을 사용한다면, 이종 컨테이너에 사용할 수 있는 타입을 제한할 수 있다.` + - `AnnotatedElement. T getAnnotation(Class annotationClass);` +- `asSubclass 메서드` + - `메서드를 호출하는 Class 인스턴스를 인수로 명시한 클래스로 형변환 한다.` + +```java +@Retention(RetentionPolicy.RUNTIME) +public @interface FindMe { +} +``` + +```java +@FindMe +public class MyService { +} +``` + +```java +// 코드 33-5 asSubclass를 사용해 한정적 타입 토큰을 안전하게 형변환한다. (204쪽) +public class PrintAnnotation { + + static Annotation getAnnotation(AnnotatedElement element, String annotationTypeName) { + Class annotationType = null; // 비한정적 타입 토큰 + try { + annotationType = Class.forName(annotationTypeName); + } catch (Exception ex) { + throw new IllegalArgumentException(ex); + } + return element.getAnnotation(annotationType.asSubclass(Annotation.class)); + } + + // 명시한 클래스의 명시한 애너테이션을 출력하는 테스트 프로그램 + public static void main(String[] args) throws Exception { + System.out.println(getAnnotation(MyService.class, FindMe.class.getName())); + } +} +``` + +- `asSubclass(Annotation.class)`: Annotation.class의 하위 타입으로 변환해준다. + +## 완벽 공략 + +### 완벽 공략 47. 수퍼 타입 토큰 + +`익명 클래스와 제네릭 클래스 상속을 사용한 타입 토큰` + +- `닐 게프터의 슈퍼 타입 토큰` + - [https://gafter.blogspot.com/2006/12/super-type-tokens.html](https://gafter.blogspot.com/2006/12/super-type-tokens.html) + - [https://gafter.blogspot.com/2007/05/limitation-of-super-typetokens.html](https://gafter.blogspot.com/2007/05/limitation-of-super-typetokens.html) +- `상속을 사용한 경우 제네릭 타입을 알아낼 수 있다. 이 경우에는 제네릭 타입이 제거되지 않기 때문에…` + +```java +public class GenericTypeInfer { + + static class Super { + T value; + } + + public static void main(String[] args) throws NoSuchFieldException { + Super stringSuper = new Super<>(); + System.out.println(stringSuper.getClass().getDeclaredField("value").getType()); + + // (new Super(){}): 익명 내부 클래스 + Type type = (new Super(){}).getClass().getGenericSuperclass(); + ParameterizedType pType = (ParameterizedType) type; + Type actualTypeArgument = pType.getActualTypeArguments()[0]; + System.out.println(actualTypeArgument); + } +} +``` + +- 익명 내부 클래스는 클래스 선언임과 동시에 인스턴스까지 생성한 것이다. + +**리스트의 상세 타입을 추론하는 방법** + +```java +public abstract class TypeRef { + private final Type type; + + protected TypeRef() { + ParameterizedType superclass = (ParameterizedType) getClass().getGenericSuperclass(); + type = superclass.getActualTypeArguments()[0]; + } + + @Override + public boolean equals(Object o) { + return o instanceof TypeRef && ((TypeRef) o).type.equals(type); + } + + @Override + public int hashCode() { + return type.hashCode(); + } + + public Type getType() { + return type; + } +} +``` + +```java +public class Favorites2 { + + private final Map, Object> favorites = new HashMap<>(); + + public void put(TypeRef typeRef, T thing) { + favorites.put(typeRef, thing); + } + + @SuppressWarnings("unchecked") + public T get(TypeRef typeRref) { + return (T) (favorites.get(typeRref)); + } + + public static void main(String[] args) { + Favorites2 f = new Favorites2(); + + TypeRef> stringTypeRef = new TypeRef<>() { + }; + System.out.println(stringTypeRef.getType()); + + TypeRef> integerTypeRef = new TypeRef<>() { + }; + System.out.println(integerTypeRef.getType()); + + f.put(stringTypeRef, List.of("a", "b", "c")); + f.put(integerTypeRef, List.of(1, 2, 3)); + f.get(stringTypeRef).forEach(System.out::println); + f.get(integerTypeRef).forEach(System.out::println); + } +} +``` + +**슈퍼타입 토큰의 제약사항 예시** + +```java +class Oops { + static Favorites2 f = new Favorites2(); + + static List favoriteList() { + TypeRef> ref = new TypeRef<>() { + }; + System.out.println(ref.getType()); + + List result = f.get(ref); + if (result == null) { + result = new ArrayList(); + f.put(ref, result); + } + return result; + } + + public static void main(String[] args) { + List ls = favoriteList(); + + List li = favoriteList(); + li.add(1); + + for (String s : ls) System.out.println(s); + } +} +``` + +- favoriteList() 정의된 코드 중 `TypeRef> ref = new TypeRef<>(){}` 의 타입을 확인해보면 List 이다. + - 처음 가져온 ls의 hashcode 값과 li가 동일하다. + - 따라서 li에는 List인 ls의 인스턴스가 담기게 되고 결국 ls에 Integer 1이라는 값을 넣어지게 되는것이다. + - 값을 꺼낼때 ClassCastException이 발생 +- 결론은 위 코드와 같이 수퍼 타입 토큰 방식에 구멍이 있다는 사실이다. + +# References + +- 수퍼 타입 토큰: [https://gafter.blogspot.com/search?q=super+type+token](https://gafter.blogspot.com/search?q=super+type+token) \ No newline at end of file diff --git a/src/main/java/item31/Chooser.java b/src/main/java/item31/Chooser.java new file mode 100644 index 0000000..eb65010 --- /dev/null +++ b/src/main/java/item31/Chooser.java @@ -0,0 +1,30 @@ +package item31; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; + +// T 생산자 매개변수에 와일드카드 타입 적용 (184쪽) +public class Chooser { + private final List choiceList; + private final Random rnd = new Random(); + + // 코드 31-5 T 생산자 매개변수에 와일드카드 타입 적용 (184쪽) + public Chooser(Collection choices) { + choiceList = new ArrayList<>(choices); + } + + public T choose() { + return choiceList.get(rnd.nextInt(choiceList.size())); + } + + public static void main(String[] args) { + List intList = List.of(1, 2, 3, 4, 5, 6); + Chooser chooser = new Chooser<>(intList); + for (int i = 0; i < 10; i++) { + Number choice = chooser.choose(); + System.out.println(choice); + } + } +} diff --git a/src/main/java/item31/EmptyStackException.java b/src/main/java/item31/EmptyStackException.java new file mode 100644 index 0000000..3cee709 --- /dev/null +++ b/src/main/java/item31/EmptyStackException.java @@ -0,0 +1,4 @@ +package item31; + +public class EmptyStackException extends RuntimeException { +} diff --git a/src/main/java/item31/RecursiveTypeBound.java b/src/main/java/item31/RecursiveTypeBound.java new file mode 100644 index 0000000..a227e0e --- /dev/null +++ b/src/main/java/item31/RecursiveTypeBound.java @@ -0,0 +1,30 @@ +package item31; + + +import item31.exmaple.IntegerBox; + +import java.util.ArrayList; +import java.util.List; + +// 와일드카드 타입을 사용해 재귀적 타입 한정을 다듬었다. (187쪽) +public class RecursiveTypeBound { + public static > E max(List list) { + if (list.isEmpty()) + throw new IllegalArgumentException("빈 리스트"); + + E result = null; + for (E e : list) + if (result == null || e.compareTo(result) > 0) + result = e; + + return result; + } + + public static void main(String[] args) { + List list = new ArrayList<>(); + list.add(new IntegerBox(10, "effective")); + list.add(new IntegerBox(2, "java")); + + System.out.println(max(list)); + } +} diff --git a/src/main/java/item31/Stack.java b/src/main/java/item31/Stack.java new file mode 100644 index 0000000..6dc7a1e --- /dev/null +++ b/src/main/java/item31/Stack.java @@ -0,0 +1,83 @@ +package item31; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.EmptyStackException; + +// 와일드카드 타입을 이용해 대량 작업을 수행하는 메서드를 포함한 제네릭 스택 (181-183쪽) +public class Stack { + private E[] elements; + private int size = 0; + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + // 코드 29-3 배열을 사용한 코드를 제네릭으로 만드는 방법 1 (172쪽) + // 배열 elements는 push(E)로 넘어온 E 인스턴스만 담는다. + // 따라서 타입 안전성을 보장하지만, + // 이 배열의 런타임 타입은 E[]가 아닌 Object[]다! + @SuppressWarnings("unchecked") + public Stack() { + elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; + } + + public void push(E e) { + ensureCapacity(); + elements[size++] = e; + } + + public E pop() { + if (size == 0) + throw new EmptyStackException(); + E result = elements[--size]; + elements[size] = null; // 다 쓴 참조 해제 + return result; + } + + public boolean isEmpty() { + return size == 0; + } + + private void ensureCapacity() { + if (elements.length == size) + elements = Arrays.copyOf(elements, 2 * size + 1); + } + + // 코드 31-1 와일드카드 타입을 사용하지 않은 pushAll 메서드 - 결함이 있다! (181쪽) +// public void pushAll(Iterable src) { +// for (E e : src) +// push(e); +// } + + // 코드 31-2 E 생산자(producer) 매개변수에 와일드카드 타입 적용 (182쪽) + public void pushAll(Iterable src) { + for (E e : src) + push(e); + } + + // 코드 31-3 와일드카드 타입을 사용하지 않은 popAll 메서드 - 결함이 있다! (183쪽) +// public void popAll(Collection dst) { +// while (!isEmpty()) +// dst.add(pop()); +// } + + // 코드 31-4 E 소비자(consumer) 매개변수에 와일드카드 타입 적용 (183쪽) + public void popAll(Collection dst) { + while (!isEmpty()) + dst.add(pop()); + } + + // 제네릭 Stack을 사용하는 맛보기 프로그램 + public static void main(String[] args) { + Stack numberStack = new Stack<>(); + Iterable integers = Arrays.asList(3, 1, 4, 1, 5, 9); + numberStack.pushAll(integers); + + Iterable doubles = Arrays.asList(3.1, 1.0, 4.0, 1.0, 5.0, 9.0); + numberStack.pushAll(doubles); + + Collection objects = new ArrayList<>(); + numberStack.popAll(objects); + + System.out.println(objects); + } +} diff --git a/src/main/java/item31/Swap.java b/src/main/java/item31/Swap.java new file mode 100644 index 0000000..6846e55 --- /dev/null +++ b/src/main/java/item31/Swap.java @@ -0,0 +1,26 @@ +package item31; + +import java.util.Arrays; +import java.util.List; + +// 와일드카드 타입을 실제 타입으로 바꿔주는 private 도우미 메서드 (189쪽) +public class Swap { + + public static void swap(List list, int i, int j) { +// public static void swap(List list, int i, int j) { + list.set(i, list.set(j, list.get(i))); +// swapHelper(list, i, j); + } + + // 와일드카드 타입을 실제 타입으로 바꿔주는 private 도우미 메서드 +// private static void swapHelper(List list, int i, int j) { +// list.set(i, list.set(j, list.get(i))); +// } + + public static void main(String[] args) { + // 첫 번째와 마지막 인수를 스왑한 후 결과 리스트를 출력한다. + List argList = Arrays.asList(args); + swap(argList, 0, argList.size() - 1); + System.out.println(argList); + } +} diff --git a/src/main/java/item31/Union.java b/src/main/java/item31/Union.java new file mode 100644 index 0000000..02fa72c --- /dev/null +++ b/src/main/java/item31/Union.java @@ -0,0 +1,34 @@ +package item31; + +import java.util.HashSet; +import java.util.Set; + +// 코드 30-2의 제네릭 union 메서드에 와일드카드 타입을 적용해 유연성을 높였다. (185-186쪽) +public class Union { + public static Set union(Set s1, + Set s2) { + Set result = new HashSet<>(s1); + result.addAll(s2); + return result; + } + + // 향상된 유연성을 확인해주는 맛보기 프로그램 (185쪽) + public static void main(String[] args) { + Set integers = new HashSet<>(); + integers.add(1); + integers.add(3); + integers.add(5); + + Set doubles = new HashSet<>(); + doubles.add(2.0); + doubles.add(4.0); + doubles.add(6.0); + + Set numbers = union(integers, doubles); + +// // 코드 31-6 자바 7까지는 명시적 타입 인수를 사용해야 한다. (186쪽) +// Set numbers = Union.union(integers, doubles); + + System.out.println(numbers); + } +} diff --git a/src/main/java/item31/exmaple/Box.java b/src/main/java/item31/exmaple/Box.java new file mode 100644 index 0000000..8e3266c --- /dev/null +++ b/src/main/java/item31/exmaple/Box.java @@ -0,0 +1,27 @@ +package item31.exmaple; + +public class Box> implements Comparable> { + + protected T value; + + public Box(T value) { + this.value = value; + } + + public void change(T value) { + this.value = value; + } + + @SuppressWarnings("unchecked") + @Override + public int compareTo(Box anotherBox) { + return this.value.compareTo((T)anotherBox.value); + } + + @Override + public String toString() { + return "Box{" + + "value=" + value + + '}'; + } +} diff --git a/src/main/java/item31/exmaple/IntegerBox.java b/src/main/java/item31/exmaple/IntegerBox.java new file mode 100644 index 0000000..f7abbc5 --- /dev/null +++ b/src/main/java/item31/exmaple/IntegerBox.java @@ -0,0 +1,19 @@ +package item31.exmaple; + +public class IntegerBox extends Box { + + private final String message; + + public IntegerBox(int value, String message) { + super(value); + this.message = message; + } + + @Override + public String toString() { + return "IntegerBox{" + + "message='" + message + '\'' + + ", value=" + value + + '}'; + } +} diff --git a/src/main/java/item31/typeinference/Box.java b/src/main/java/item31/typeinference/Box.java new file mode 100644 index 0000000..114cc54 --- /dev/null +++ b/src/main/java/item31/typeinference/Box.java @@ -0,0 +1,14 @@ +package item31.typeinference; + +public class Box { + + private T t; + + public T get() { + return t; + } + + public void set(E e) { + this.t = e; + } +} diff --git a/src/main/java/item31/typeinference/BoxExample.java b/src/main/java/item31/typeinference/BoxExample.java new file mode 100644 index 0000000..052a679 --- /dev/null +++ b/src/main/java/item31/typeinference/BoxExample.java @@ -0,0 +1,43 @@ +package item31.typeinference; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class BoxExample { + + private static void addBox(U u, List> boxes) { + Box box = new Box<>(); + box.set(u); + boxes.add(box); + } + + private static void outputBoxes(List> boxes) { + int counter = 0; + for (Box box: boxes) { + U boxContents = box.get(); + System.out.println("Box #" + counter + " contains [" + + boxContents.toString() + "]"); + counter++; + } + } + + private static void processStringList(List stringList) { + + } + + + public static void main(String[] args) { + ArrayList> listOfIntegerBoxes = new ArrayList<>(); + // 이렇게 명시적으로 가능 + BoxExample.addBox(10, listOfIntegerBoxes); + BoxExample.addBox(20, listOfIntegerBoxes); + BoxExample.addBox(30, listOfIntegerBoxes); + BoxExample.outputBoxes(listOfIntegerBoxes); + + // Target Type + List stringlist = Collections.emptyList(); + List integerlist = Collections.emptyList(); + BoxExample.processStringList(Collections.emptyList()); + } +} diff --git a/src/main/java/item32/Dangerous.java b/src/main/java/item32/Dangerous.java new file mode 100644 index 0000000..9ed5d14 --- /dev/null +++ b/src/main/java/item32/Dangerous.java @@ -0,0 +1,18 @@ +package item32; + +import java.util.List; + +// 제네릭 varargs 배열 매개변수에 값을 저장하는 것은 안전하지 않다. (191-192쪽) +public class Dangerous { + // 코드 32-1 제네릭과 varargs를 혼용하면 타입 안전성이 깨진다! (191-192쪽) + static void dangerous(List... stringLists) { + List intList = List.of(42); + Object[] objects = stringLists; + objects[0] = intList; // 힙 오염 발생 + String s = stringLists[0].get(0); // ClassCastException + } + + public static void main(String[] args) { + dangerous(List.of("There be dragons!")); + } +} diff --git a/src/main/java/item32/FlattenWithList.java b/src/main/java/item32/FlattenWithList.java new file mode 100644 index 0000000..fbbaffe --- /dev/null +++ b/src/main/java/item32/FlattenWithList.java @@ -0,0 +1,20 @@ +package item32; + +import java.util.ArrayList; +import java.util.List; + +// 코드 32-4 제네릭 varargs 매개변수를 List로 대체한 예 - 타입 안전하다. (195-196쪽) +public class FlattenWithList { + static List flatten(List> lists) { + List result = new ArrayList<>(); + for (List list : lists) + result.addAll(list); + return result; + } + + public static void main(String[] args) { + List flatList = flatten(List.of( + List.of(1, 2), List.of(3, 4, 5), List.of(6,7))); + System.out.println(flatList); + } +} diff --git a/src/main/java/item32/FlattenWithVarargs.java b/src/main/java/item32/FlattenWithVarargs.java new file mode 100644 index 0000000..9504666 --- /dev/null +++ b/src/main/java/item32/FlattenWithVarargs.java @@ -0,0 +1,22 @@ +package item32; + +import java.util.ArrayList; +import java.util.List; + +// 코드 32-3 제네릭 varargs 매개변수를 안전하게 사용하는 메서드 (195쪽) +public class FlattenWithVarargs { + + @SafeVarargs + static List flatten(List... lists) { + List result = new ArrayList<>(); + for (List list : lists) + result.addAll(list); + return result; + } + + public static void main(String[] args) { + List flatList = flatten( + List.of(1, 2), List.of(3, 4, 5), List.of(6,7)); + System.out.println(flatList); + } +} diff --git a/src/main/java/item32/PickTwo.java b/src/main/java/item32/PickTwo.java new file mode 100644 index 0000000..ef953d2 --- /dev/null +++ b/src/main/java/item32/PickTwo.java @@ -0,0 +1,26 @@ +package item32; + +import java.util.Arrays; +import java.util.concurrent.ThreadLocalRandom; + +// 미묘한 힙 오염 발생 (193-194쪽) +public class PickTwo { + // 코드 32-2 자신의 제네릭 매개변수 배열의 참조를 노출한다. - 안전하지 않다! (193쪽) + static T[] toArray(T... args) { + return args; + } + + static T[] pickTwo(T a, T b, T c) { + switch(ThreadLocalRandom.current().nextInt(3)) { + case 0: return toArray(a, b); + case 1: return toArray(a, c); + case 2: return toArray(b, c); + } + throw new AssertionError(); // 도달할 수 없다. + } + + public static void main(String[] args) { // (194쪽) + String[] attributes = pickTwo("좋은", "빠른", "저렴한"); + System.out.println(Arrays.toString(attributes)); + } +} diff --git a/src/main/java/item32/SafePickTwo.java b/src/main/java/item32/SafePickTwo.java new file mode 100644 index 0000000..1701493 --- /dev/null +++ b/src/main/java/item32/SafePickTwo.java @@ -0,0 +1,21 @@ +package item32; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +// 배열 대신 List를 이용해 안전하게 바꿘 PickTwo (196쪽) +public class SafePickTwo { + static List pickTwo(T a, T b, T c) { + switch(ThreadLocalRandom.current().nextInt(3)) { + case 0: return List.of(a, b); + case 1: return List.of(a, c); + case 2: return List.of(b, c); + } + throw new AssertionError(); + } + + public static void main(String[] args) { + List attributes = pickTwo("좋은", "빠른", "저렴한"); + System.out.println(attributes); + } +} diff --git a/src/main/java/item32/random/RandomExample.java b/src/main/java/item32/random/RandomExample.java new file mode 100644 index 0000000..68f9bf5 --- /dev/null +++ b/src/main/java/item32/random/RandomExample.java @@ -0,0 +1,24 @@ +package item32.random; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public class RandomExample { + + public static void main(String[] args) { + Random random = new Random(); + System.out.println(random.nextInt(10)); + + ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current(); + System.out.println(threadLocalRandom.nextInt(10)); + } + + private int value; + + public synchronized int compareAndSwap(int expectedValue, int newValue) { + int readValue = value; + if (readValue == expectedValue) + value = newValue; + return readValue; + } +} diff --git a/src/main/java/item32/threadlocal/ThreadLocalExample.java b/src/main/java/item32/threadlocal/ThreadLocalExample.java new file mode 100644 index 0000000..21c5315 --- /dev/null +++ b/src/main/java/item32/threadlocal/ThreadLocalExample.java @@ -0,0 +1,35 @@ +package item32.threadlocal; + +import java.text.SimpleDateFormat; +import java.util.Random; + +public class ThreadLocalExample implements Runnable { + + // SimpleDateFormat is not thread-safe, so give one to each thread + private static final ThreadLocal formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm")); + +// private SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd HHmm"); + + public static void main(String[] args) throws InterruptedException { + ThreadLocalExample obj = new ThreadLocalExample(); + for (int i = 0; i < 10; i++) { + Thread t = new Thread(obj, "" + i); + Thread.sleep(new Random().nextInt(1000)); + t.start(); + } + } + + @Override + public void run() { + System.out.println("Thread Name= " + Thread.currentThread().getName() + " default Formatter = " + formatter.get().toPattern()); + try { + Thread.sleep(new Random().nextInt(1000)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + //formatter pattern is changed here by thread, but it won't reflect to other threads + formatter.set(new SimpleDateFormat()); + + System.out.println("Thread Name= " + Thread.currentThread().getName() + " formatter = " + formatter.get().toPattern()); + } +} diff --git a/src/main/java/item33/bounded_type_token/FindMe.java b/src/main/java/item33/bounded_type_token/FindMe.java new file mode 100644 index 0000000..1a3e5a7 --- /dev/null +++ b/src/main/java/item33/bounded_type_token/FindMe.java @@ -0,0 +1,8 @@ +package item33.bounded_type_token; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface FindMe { +} diff --git a/src/main/java/item33/bounded_type_token/MyService.java b/src/main/java/item33/bounded_type_token/MyService.java new file mode 100644 index 0000000..00e9e1f --- /dev/null +++ b/src/main/java/item33/bounded_type_token/MyService.java @@ -0,0 +1,5 @@ +package item33.bounded_type_token; + +@FindMe +public class MyService { +} diff --git a/src/main/java/item33/bounded_type_token/PrintAnnotation.java b/src/main/java/item33/bounded_type_token/PrintAnnotation.java new file mode 100644 index 0000000..ad925bd --- /dev/null +++ b/src/main/java/item33/bounded_type_token/PrintAnnotation.java @@ -0,0 +1,23 @@ +package item33.bounded_type_token; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; + +// 코드 33-5 asSubclass를 사용해 한정적 타입 토큰을 안전하게 형변환한다. (204쪽) +public class PrintAnnotation { + + static Annotation getAnnotation(AnnotatedElement element, String annotationTypeName) { + Class annotationType = null; // 비한정적 타입 토큰 + try { + annotationType = Class.forName(annotationTypeName); + } catch (Exception ex) { + throw new IllegalArgumentException(ex); + } + return element.getAnnotation(annotationType.asSubclass(Annotation.class)); + } + + // 명시한 클래스의 명시한 애너테이션을 출력하는 테스트 프로그램 + public static void main(String[] args) throws Exception { + System.out.println(getAnnotation(MyService.class, FindMe.class.getName())); + } +} diff --git a/src/main/java/item33/super_type_token/Favorites2.java b/src/main/java/item33/super_type_token/Favorites2.java new file mode 100644 index 0000000..1b56c65 --- /dev/null +++ b/src/main/java/item33/super_type_token/Favorites2.java @@ -0,0 +1,39 @@ +package item33.super_type_token; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Favorites2 { + + private final Map, Object> favorites = new HashMap<>(); + + public void put(TypeRef typeRef, T thing) { + favorites.put(typeRef, thing); + } + + @SuppressWarnings("unchecked") + public T get(TypeRef typeRref) { + return (T) (favorites.get(typeRref)); + } + + public static void main(String[] args) { + Favorites2 f = new Favorites2(); + + TypeRef> stringTypeRef = new TypeRef<>() { + }; + System.out.println(stringTypeRef.getType()); + + TypeRef> integerTypeRef = new TypeRef<>() { + }; + System.out.println(integerTypeRef.getType()); + TypeRef typeRef = new TypeRef<>() { + }; + f.put(typeRef, 1); + System.out.println(f.get(typeRef)); + f.put(stringTypeRef, List.of("a", "b", "c")); + f.put(integerTypeRef, List.of(1, 2, 3)); + f.get(stringTypeRef).forEach(System.out::println); + f.get(integerTypeRef).forEach(System.out::println); + } +} diff --git a/src/main/java/item33/super_type_token/GenericTypeInfer.java b/src/main/java/item33/super_type_token/GenericTypeInfer.java new file mode 100644 index 0000000..cdd377b --- /dev/null +++ b/src/main/java/item33/super_type_token/GenericTypeInfer.java @@ -0,0 +1,22 @@ +package item33.super_type_token; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public class GenericTypeInfer { + + static class Super { + T value; + } + + public static void main(String[] args) throws NoSuchFieldException { + Super stringSuper = new Super<>(); + System.out.println(stringSuper.getClass().getDeclaredField("value").getType()); + + // (new Super(){}): 익명 내부 클래스 + Type type = (new Super(){}).getClass().getGenericSuperclass(); + ParameterizedType pType = (ParameterizedType) type; + Type actualTypeArgument = pType.getActualTypeArguments()[0]; + System.out.println(actualTypeArgument); + } +} diff --git a/src/main/java/item33/super_type_token/Oops.java b/src/main/java/item33/super_type_token/Oops.java new file mode 100644 index 0000000..f778149 --- /dev/null +++ b/src/main/java/item33/super_type_token/Oops.java @@ -0,0 +1,30 @@ +package item33.super_type_token; + +import java.util.ArrayList; +import java.util.List; + +class Oops { + static Favorites2 f = new Favorites2(); + + static List favoriteList() { + TypeRef> ref = new TypeRef<>() { + }; + System.out.println(ref.getType()); + + List result = f.get(ref); + if (result == null) { + result = new ArrayList(); + f.put(ref, result); + } + return result; + } + + public static void main(String[] args) { + List ls = favoriteList(); + + List li = favoriteList(); + li.add(1); + + for (String s : ls) System.out.println(s); + } +} diff --git a/src/main/java/item33/super_type_token/TypeRef.java b/src/main/java/item33/super_type_token/TypeRef.java new file mode 100644 index 0000000..80c45bd --- /dev/null +++ b/src/main/java/item33/super_type_token/TypeRef.java @@ -0,0 +1,27 @@ +package item33.super_type_token; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public abstract class TypeRef { + private final Type type; + + protected TypeRef() { + ParameterizedType superclass = (ParameterizedType) getClass().getGenericSuperclass(); + type = superclass.getActualTypeArguments()[0]; + } + + @Override + public boolean equals(Object o) { + return o instanceof TypeRef && ((TypeRef) o).type.equals(type); + } + + @Override + public int hashCode() { + return type.hashCode(); + } + + public Type getType() { + return type; + } +} diff --git a/src/main/java/item33/type_token/Favorites.java b/src/main/java/item33/type_token/Favorites.java new file mode 100644 index 0000000..dcdfbe6 --- /dev/null +++ b/src/main/java/item33/type_token/Favorites.java @@ -0,0 +1,29 @@ +package item33.type_token; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class Favorites { + private Map, Object> map = new HashMap<>(); + + public void put(Class clazz, T value) { + this.map.put(Objects.requireNonNull(clazz), clazz.cast(value)); + } + + public T get(Class clazz) { + return clazz.cast(this.map.get(clazz)); + } + + public static void main(String[] args) { + Favorites favorites = new Favorites(); + favorites.put(String.class, "foo"); + favorites.put(Integer.class, 2); + +// favorites.put(List.class, List.of(1, 2, 3)); +// favorites.put(List.class, List.of("a", "b", "c")); + +// List list = favorites.get(List.class); +// list.forEach(System.out::println); + } +}