diff --git "a/docs/\354\225\204\354\235\264\355\205\234 28. \353\260\260\354\227\264\353\263\264\353\213\244\353\212\224 \353\246\254\354\212\244\355\212\270\353\245\274 \354\202\254\354\232\251\355\225\230\353\235\274.md" "b/docs/\354\225\204\354\235\264\355\205\234 28. \353\260\260\354\227\264\353\263\264\353\213\244\353\212\224 \353\246\254\354\212\244\355\212\270\353\245\274 \354\202\254\354\232\251\355\225\230\353\235\274.md" new file mode 100644 index 0000000..e2e829a --- /dev/null +++ "b/docs/\354\225\204\354\235\264\355\205\234 28. \353\260\260\354\227\264\353\263\264\353\213\244\353\212\224 \353\246\254\354\212\244\355\212\270\353\245\274 \354\202\254\354\232\251\355\225\230\353\235\274.md" @@ -0,0 +1,143 @@ +# 아이템 28. 배열보다는 리스트를 사용하라 + +## 핵심 정리 + +`배열과 제네릭은 잘 어울리지 않는다.` + +- `배열은 공변 (covariant), 제네릭은 불공변` + - 공변: 같이 변한다 + - 상속관계에 따라 같이 변한다. + - 불공변: 같이 변하지 않는다. + +```java +public class IntegerToString { + public static void main(String[] args) { + // 공변 -> 타입이 String에서 Object 타입으로 변한다.(상속구조라서) + Object[] anything = new String[10]; + anything[0] = 1; + + // 불공변 + List names = new ArrayList<>(); +// List objects = names; + +// // 제네릭과 배열을 같이 사용할 수 있다면... +// List[] stringLists = new ArrayList[1]; +// List intList = List.of(42); +// Object[] objects = stringLists; +// objects[0] = intList; +// String s = stringLists[0].get(0); +// System.out.println(s); + } +} +``` + +- 위 코드에서 anything의 실체는 String 배열인데 정수를 넣고 있다.(컴파일러는 이것을 잡지 못한다.) +- `배열은 실체화(reify) 되지만, 제네릭은 실체화 되지 않는다.(소거)` + - 실체화란 런타임에도 타입을 보존한다는 의미 + - 실체화되지 않는다는 것은 컴파일할 때 제네릭의 타입이 사라진다는 의미 + +```java +public class MyGeneric { + + public static void main(String[] args) { + List names = new ArrayList<>(); + names.add("foo"); + String name = names.get(0); + System.out.println(name); + } +} +``` + +- `new Generic<타입>[배열] 은 컴파일 할 수 없다.` +- `제네릭 소거: 원소의 타입을 컴파일 타임에만 검사하며 런타임에는 알 수 없다.` + +```java +// 코드 28-6 배열 기반 Chooser +public class Chooser_Array { + private final Object[] choiceList; + + public Chooser_Array(Collection choices) { + choiceList = choices.toArray(); + } + + public Object choose() { + Random rnd = ThreadLocalRandom.current(); + return choiceList[rnd.nextInt(choiceList.length)]; + } + + public static void main(String[] args) { + List intList = List.of(1, 2, 3, 4, 5, 6); + + Chooser_Array chooser = new Chooser_Array(intList); + + for (int i = 0; i < 10; i++) { + Number choice = (Number) chooser.choose(); + System.out.println(choice); + } + } +} + +// 코드 28-6 리스트 기반 Chooser - 타입 안전성 확보! (168쪽) +public class Chooser { + private final List choiceList; + + public Chooser(Collection choices) { + choiceList = new ArrayList<>(choices); // 방어적 복사 + } + + public T choose() { + Random rnd = ThreadLocalRandom.current(); + 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); + } + } +} +``` + +## 완벽 공략 + +### 완벽 공략 42. @SafeVarags + +`생성자와 메서드의 제네릭 가변인자에 사용할 수 있는 애노테이션` + +- `제네릭 가변인자는 근본적으로 타입 안전하지 않다. (가변인자가 배열이니까, 제네릭 배열과 같은 문제)` +- `가변 인자 (배열)의 내부 데이터가 오염될 가능성이 있다.` + - 제네릭 타입의 배열을 사용해서… +- `@SafeVarargs를 사용하면 가변 인자에 대한 해당 오염에 대한 경고를 숨길 수 있다.` +- `아이템 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라.` + +```java +public class SafeVaragsExample { + +// @SafeVarargs // Not actually safe! + static void notSafe(List... stringLists) { + Object[] array = stringLists; // List... => List[], 그리고 배열은 공변이니까. + List tmpList = List.of(42); + array[0] = tmpList; // Semantically invalid, but compiles without warnings + String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime! + } + + @SafeVarargs + static void safe(T... values) { + for (T value: values) { + System.out.println(value); + } + } + + public static void main(String[] args) { + SafeVaragsExample.safe("a", "b", "c"); + SafeVaragsExample.notSafe(List.of("a", "b", "c")); + } +} +``` + +- 제네릭 배열 코드에서 데이터를 꺼낼때 문제가 발생한다. \ No newline at end of file diff --git "a/docs/\354\225\204\354\235\264\355\205\234 29. \354\235\264\354\231\225\354\235\264\353\251\264 \354\240\234\353\204\244\353\246\255 \355\203\200\354\236\205\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274.md" "b/docs/\354\225\204\354\235\264\355\205\234 29. \354\235\264\354\231\225\354\235\264\353\251\264 \354\240\234\353\204\244\353\246\255 \355\203\200\354\236\205\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274.md" new file mode 100644 index 0000000..c9d1dec --- /dev/null +++ "b/docs/\354\225\204\354\235\264\355\205\234 29. \354\235\264\354\231\225\354\235\264\353\251\264 \354\240\234\353\204\244\353\246\255 \355\203\200\354\236\205\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274.md" @@ -0,0 +1,148 @@ +# 아이템 29. 이왕이면 제네릭 타입으로 만들라 + +## 핵심 정리 + +- `배열을 사용하는 코드를 제네릭으로 만들 때 해결책 두 가지.` +- `첫번째 방법: 제네릭 배열 (E[]) 대신에 Object 배열을 생성한 뒤에 제네릭 배열로 형변환 한다.` + - `형변환을 배열 생성시 한 번만 한다.` + - `가독성이 좋다.` + - `힙 오염이 발생할 수 있다.` + - Object 타입 사용함으로써 런타임 시점에 ClassCastException 발생 가능 (아이템32 참고) + +```java +// E[]를 이용한 제네릭 스택 (170-174쪽) +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]; + } + ... + + // 코드 29-5 제네릭 Stack을 사용하는 맛보기 프로그램 (174쪽) + public static void main(String[] args) { + Stack stack = new Stack<>(); + for (String arg : List.of("a", "b", "c")) + stack.push(arg); + while (!stack.isEmpty()) + System.out.println(stack.pop().toUpperCase()); + } +} +``` + +- `두번째 방법: 제네릭 배열 대신에 Object 배열을 사용하고, 배열이 반환한 원소를 E로 형변환한다.` + - `원소를 읽을 때 마다 형변환을 해줘야 한다.` + +```java +// Object[]를 이용한 제네릭 Stack (170-174쪽) +public class Stack { + private Object[] elements; + private int size = 0; + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + public Stack() { + elements = new Object[DEFAULT_INITIAL_CAPACITY]; + } + + // 코드 29-4 배열을 사용한 코드를 제네릭으로 만드는 방법 2 (173쪽) + // 비검사 경고를 적절히 숨긴다. + public E pop() { + if (size == 0) + throw new EmptyStackException(); + + // push에서 E 타입만 허용하므로 이 형변환은 안전하다. + @SuppressWarnings("unchecked") E result = (E) elements[--size]; + + elements[size] = null; // 다 쓴 참조 해제 + return result; + } + ... + + // 코드 29-5 제네릭 Stack을 사용하는 맛보기 프로그램 (174쪽) + public static void main(String[] args) { + Stack stack = new Stack<>(); + for (String arg : List.of("a", "b", "c")) + stack.push(arg); + while (!stack.isEmpty()) + System.out.println(stack.pop().toUpperCase()); + } +} +``` + +- java 5 부터 타입 소거라는 방식이 도입돼서 로우 타입으로 선언해도 문제가 없긴하다. + +## 완벽 공략 + +### 완벽 공략 43. 한정적 타입 매개변수 + +`Bounded Type Parameters` + +- `매개변수화 타입을 특정한 타입으로 한정짓고 싶을 때 사용할 수 있다.` + - `, 선언할 수 있는 제네릭 타입을 Number를 상속(extnds)했거나 구현한(implements)한 클래스로 제한한다.` +- `제한한 타입의 인스턴스를 만들거나, 메서드를 호출할 수도 있다.` + - `, Number 타입이 제공하는 메서드를 사용할 수 있다.` +- `다수의 타입으로 한정 할 수 있다. 이 때 클래스 타입을 가장 먼저 선언해야 한다.` + - `, 선언할 제네릭 타입은 Integer와 Number를 모두 상속 또는 구현한 타입이어야 한다.` + +```java +// E[]를 이용한 제네릭 스택 (170-174쪽) +public class Stack { + private Number[] 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 = new Number[DEFAULT_INITIAL_CAPACITY]; + } + + public void push(E e) { + ensureCapacity(); + elements[size++] = e; + } + + public E pop() { + if (size == 0) + throw new EmptyStackException(); + @SuppressWarnings("unchecked") E result = (E)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); + } + + // 코드 29-5 제네릭 Stack을 사용하는 맛보기 프로그램 (174쪽) + public static void main(String[] args) { + Stack stack = new Stack<>(); + for (Integer arg : List.of(1, 2, 3)) + stack.push(arg); + while (!stack.isEmpty()) + System.out.println(stack.pop()); + } +} +``` + +- 한정적 타입을 선언하게 되면 컴파일되면 제네릭 타입으로 선언된 필드들이 한정 타입으로 선언한 타입으로 바뀌게된다. + - Object[] → Number[] + +# References + +- 한정적 타입 매개변수: [https://docs.oracle.com/javase/tutorial/java/generics/bounded.html](https://docs.oracle.com/javase/tutorial/java/generics/bounded.html) \ No newline at end of file diff --git "a/docs/\354\225\204\354\235\264\355\205\234 30. \354\235\264\354\231\225\354\235\264\353\251\264 \354\240\234\353\204\244\353\246\255 \353\251\224\354\204\234\353\223\234\353\241\234 \353\247\214\353\223\244\353\235\274.md" "b/docs/\354\225\204\354\235\264\355\205\234 30. \354\235\264\354\231\225\354\235\264\353\251\264 \354\240\234\353\204\244\353\246\255 \353\251\224\354\204\234\353\223\234\353\241\234 \353\247\214\353\223\244\353\235\274.md" new file mode 100644 index 0000000..7b93a80 --- /dev/null +++ "b/docs/\354\225\204\354\235\264\355\205\234 30. \354\235\264\354\231\225\354\235\264\353\251\264 \354\240\234\353\204\244\353\246\255 \353\251\224\354\204\234\353\223\234\353\241\234 \353\247\214\353\223\244\353\235\274.md" @@ -0,0 +1,117 @@ +# 아이템 30. 이왕이면 제네릭 메서드로 만들라 + +## 핵심 정리 + +- `매개변수화 타입을 받는 정적 유틸리티 메서드` + - `한정적 와일드카드 타입(아이템 31)을 사용하면 더 유연하게 개선할 수 있다.` + +```java +// 제네릭 union 메서드와 테스트 프로그램 (177쪽) +public class Union { + + // 코드 30-2 제네릭 메서드 (177쪽) + public static Set union(Set s1, Set s2) { + Set result = new HashSet<>(s1); + result.addAll(s2); + return result; + } + + // 코드 30-3 제네릭 메서드를 활용하는 간단한 프로그램 (177쪽) + public static void main(String[] args) { + Set guys = Set.of("톰", "딕", "해리"); + Set stooges = Set.of("래리", "모에", "컬리"); +// Set stooges = Set.of(1, 2, 3); + Set all = union(guys, stooges); + + for (String o : all) { + System.out.println(o); + } + } +} +``` + +- `제네릭 싱글턴 팩터리` + - `(소거 방식이기 때문에) 불변 객체 하나를 어떤 타입으로든 매개변수화 할 수 있다.` + +**befere** + +```java +// 제네릭 싱글턴 팩터리 패턴 (178쪽) +public class GenericSingletonFactory { + + public static Function stringIdentityFunction() { + return (t) -> t; + } + + public static Function integerIdentityFunction() { + return (t) -> t; + } + + // 코드 30-5 제네릭 싱글턴을 사용하는 예 (178쪽) + public static void main(String[] args) { + String[] strings = {"삼베", "대마", "나일론"}; + Function sameString = stringIdentityFunction(); + for (String s : strings) + System.out.println(sameString.apply(s)); + + Number[] numbers = {1, 2.0, 3L}; + Function sameNumber = integerIdentityFunction(); + for (Number n : numbers) + System.out.println(sameNumber.apply(n)); + } +} +``` + +**after** + +```java +// 제네릭 싱글턴 팩터리 패턴 (178쪽) +public class GenericSingletonFactory { + // 코드 30-4 제네릭 싱글턴 팩터리 패턴 (178쪽) + private static UnaryOperator IDENTITY_FN = (t) -> t; + + @SuppressWarnings("unchecked") + public static UnaryOperator identityFunction() { + return (UnaryOperator) IDENTITY_FN; + } + + // 코드 30-5 제네릭 싱글턴을 사용하는 예 (178쪽) + public static void main(String[] args) { + String[] strings = {"삼베", "대마", "나일론"}; + UnaryOperator sameString = identityFunction(); + for (String s : strings) + System.out.println(sameString.apply(s)); + + Number[] numbers = {1, 2.0, 3L}; + UnaryOperator sameNumber = identityFunction(); + for (Number n : numbers) + System.out.println(sameNumber.apply(n)); + } +} +``` + +- `재귀적 타입 한정` + - `자기 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용 범위를 한정한다.` + +```java +// 재귀적 타입 한정을 이용해 상호 비교할 수 있음을 표현 (179쪽) +public class RecursiveTypeBound { + // 코드 30-7 컬렉션에서 최댓값을 반환한다. - 재귀적 타입 한정 사용 (179쪽) + public static > E max(Collection c) { + if (c.isEmpty()) + throw new IllegalArgumentException("컬렉션이 비어 있습니다."); + + E result = null; + for (E e : c) + if (result == null || e.compareTo(result) > 0) + result = Objects.requireNonNull(e); + + return result; + } + + public static void main(String[] args) { + List argList = List.of("hello", "foo"); + System.out.println(max(argList)); + } +} +``` \ No newline at end of file diff --git a/src/main/java/item28/array_to_list/Chooser.java b/src/main/java/item28/array_to_list/Chooser.java new file mode 100644 index 0000000..1f5e2b3 --- /dev/null +++ b/src/main/java/item28/array_to_list/Chooser.java @@ -0,0 +1,32 @@ +package item28.array_to_list; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +// 코드 28-6 리스트 기반 Chooser - 타입 안전성 확보! (168쪽) +public class Chooser { + private final List choiceList; + + public Chooser(Collection choices) { + choiceList = new ArrayList<>(choices); // 방어적 복사 + } + + public T choose() { + Random rnd = ThreadLocalRandom.current(); + 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/item28/array_to_list/Chooser_Array.java b/src/main/java/item28/array_to_list/Chooser_Array.java new file mode 100644 index 0000000..e22d35f --- /dev/null +++ b/src/main/java/item28/array_to_list/Chooser_Array.java @@ -0,0 +1,31 @@ +package item28.array_to_list; + +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +// 코드 28-6 배열 기반 Chooser +public class Chooser_Array { + private final Object[] choiceList; + + public Chooser_Array(Collection choices) { + choiceList = choices.toArray(); + } + + public Object choose() { + Random rnd = ThreadLocalRandom.current(); + return choiceList[rnd.nextInt(choiceList.length)]; + } + + public static void main(String[] args) { + List intList = List.of(1, 2, 3, 4, 5, 6); + + Chooser_Array chooser = new Chooser_Array(intList); + + for (int i = 0; i < 10; i++) { + Number choice = (Number) chooser.choose(); + System.out.println(choice); + } + } +} diff --git a/src/main/java/item28/erasure/IntegerToString.java b/src/main/java/item28/erasure/IntegerToString.java new file mode 100644 index 0000000..f2b0522 --- /dev/null +++ b/src/main/java/item28/erasure/IntegerToString.java @@ -0,0 +1,24 @@ +package item28.erasure; + +import java.util.ArrayList; +import java.util.List; + +public class IntegerToString { + public static void main(String[] args) { + // 공변 -> 타입이 String에서 Object 타입으로 변한다.(상속구조라서) + Object[] anything = new String[10]; + anything[0] = 1; + + // 불공변 + List names = new ArrayList<>(); +// List objects = names; + +// // 제네릭과 배열을 같이 사용할 수 있다면... +// List[] stringLists = new ArrayList[1]; +// List intList = List.of(42); +// Object[] objects = stringLists; +// objects[0] = intList; +// String s = stringLists[0].get(0); +// System.out.println(s); + } +} diff --git a/src/main/java/item28/erasure/MyGeneric.java b/src/main/java/item28/erasure/MyGeneric.java new file mode 100644 index 0000000..bb4ab67 --- /dev/null +++ b/src/main/java/item28/erasure/MyGeneric.java @@ -0,0 +1,14 @@ +package item28.erasure; + +import java.util.ArrayList; +import java.util.List; + +public class MyGeneric { + + public static void main(String[] args) { + List names = new ArrayList<>(); + names.add("foo"); + String name = names.get(0); + System.out.println(name); + } +} diff --git a/src/main/java/item28/safevarags/SafeVaragsExample.java b/src/main/java/item28/safevarags/SafeVaragsExample.java new file mode 100644 index 0000000..040bfff --- /dev/null +++ b/src/main/java/item28/safevarags/SafeVaragsExample.java @@ -0,0 +1,26 @@ +package item28.safevarags; + +import java.util.List; + +public class SafeVaragsExample { + +// @SafeVarargs // Not actually safe! + static void notSafe(List... stringLists) { + Object[] array = stringLists; // List... => List[], 그리고 배열은 공변이니까. + List tmpList = List.of(42); + array[0] = tmpList; // Semantically invalid, but compiles without warnings + String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime! + } + + @SafeVarargs + static void safe(T... values) { + for (T value: values) { + System.out.println(value); + } + } + + public static void main(String[] args) { + SafeVaragsExample.safe("a", "b", "c"); + SafeVaragsExample.notSafe(List.of("a", "b", "c")); + } +} diff --git a/src/main/java/item29/EmptyStackException.java b/src/main/java/item29/EmptyStackException.java new file mode 100644 index 0000000..d975c2f --- /dev/null +++ b/src/main/java/item29/EmptyStackException.java @@ -0,0 +1,4 @@ +package item29; + +public class EmptyStackException extends RuntimeException { +} diff --git a/src/main/java/item29/bounded_type/Stack.java b/src/main/java/item29/bounded_type/Stack.java new file mode 100644 index 0000000..df7787a --- /dev/null +++ b/src/main/java/item29/bounded_type/Stack.java @@ -0,0 +1,53 @@ +package item29.bounded_type; + +import item29.EmptyStackException; + +import java.util.Arrays; +import java.util.List; + +// E[]를 이용한 제네릭 스택 (170-174쪽) +public class Stack { + private Number[] 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 = new Number[DEFAULT_INITIAL_CAPACITY]; + } + + public void push(E e) { + ensureCapacity(); + elements[size++] = e; + } + + public E pop() { + if (size == 0) + throw new EmptyStackException(); + @SuppressWarnings("unchecked") E result = (E)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); + } + + // 코드 29-5 제네릭 Stack을 사용하는 맛보기 프로그램 (174쪽) + public static void main(String[] args) { + Stack stack = new Stack<>(); + for (Integer arg : List.of(1, 2, 3)) + stack.push(arg); + while (!stack.isEmpty()) + System.out.println(stack.pop()); + } +} diff --git a/src/main/java/item29/object/Stack.java b/src/main/java/item29/object/Stack.java new file mode 100644 index 0000000..4e0e789 --- /dev/null +++ b/src/main/java/item29/object/Stack.java @@ -0,0 +1,48 @@ +package item29.object; + +import item29.EmptyStackException; + +import java.util.Arrays; +import java.util.List; + +// Object를 이용한 제네릭 스택 (170-174쪽) +public class Stack { + private Object[] elements; + private int size = 0; + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + public Stack() { + elements = new Object[DEFAULT_INITIAL_CAPACITY]; + } + + public void push(Object e) { + ensureCapacity(); + elements[size++] = e; + } + + public Object pop() { + if (size == 0) + throw new EmptyStackException(); + Object 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); + } + + // 코드 29-5 제네릭 Stack을 사용하는 맛보기 프로그램 (174쪽) + public static void main(String[] args) { + Stack stack = new Stack(); + for (String arg : List.of("a", "b", "c")) + stack.push(arg); + while (!stack.isEmpty()) + System.out.println(((String)stack.pop()).toUpperCase()); + } +} diff --git a/src/main/java/item29/technqiue1/Stack.java b/src/main/java/item29/technqiue1/Stack.java new file mode 100644 index 0000000..37153a5 --- /dev/null +++ b/src/main/java/item29/technqiue1/Stack.java @@ -0,0 +1,53 @@ +package item29.technqiue1; + +import item29.EmptyStackException; + +import java.util.Arrays; +import java.util.List; + +// E[]를 이용한 제네릭 스택 (170-174쪽) +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); + } + + // 코드 29-5 제네릭 Stack을 사용하는 맛보기 프로그램 (174쪽) + public static void main(String[] args) { + Stack stack = new Stack<>(); + for (String arg : List.of("a", "b", "c")) + stack.push(arg); + while (!stack.isEmpty()) + System.out.println(stack.pop().toUpperCase()); + } +} diff --git a/src/main/java/item29/technqiue2/Stack.java b/src/main/java/item29/technqiue2/Stack.java new file mode 100644 index 0000000..eba5ca1 --- /dev/null +++ b/src/main/java/item29/technqiue2/Stack.java @@ -0,0 +1,53 @@ +package item29.technqiue2; + +import item29.EmptyStackException; + +import java.util.Arrays; +import java.util.List; + +// Object[]를 이용한 제네릭 Stack (170-174쪽) +public class Stack { + private Object[] elements; + private int size = 0; + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + public Stack() { + elements = new Object[DEFAULT_INITIAL_CAPACITY]; + } + + public void push(E e) { + ensureCapacity(); + elements[size++] = e; + } + + // 코드 29-4 배열을 사용한 코드를 제네릭으로 만드는 방법 2 (173쪽) + // 비검사 경고를 적절히 숨긴다. + public E pop() { + if (size == 0) + throw new EmptyStackException(); + + // push에서 E 타입만 허용하므로 이 형변환은 안전하다. + @SuppressWarnings("unchecked") E result = (E) 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); + } + + // 코드 29-5 제네릭 Stack을 사용하는 맛보기 프로그램 (174쪽) + public static void main(String[] args) { + Stack stack = new Stack<>(); + for (String arg : List.of("a", "b", "c")) + stack.push(arg); + while (!stack.isEmpty()) + System.out.println(stack.pop().toUpperCase()); + } +} diff --git a/src/main/java/item30/RecursiveTypeBound.java b/src/main/java/item30/RecursiveTypeBound.java new file mode 100644 index 0000000..e64ceb8 --- /dev/null +++ b/src/main/java/item30/RecursiveTypeBound.java @@ -0,0 +1,26 @@ +package item30; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +// 재귀적 타입 한정을 이용해 상호 비교할 수 있음을 표현 (179쪽) +public class RecursiveTypeBound { + // 코드 30-7 컬렉션에서 최댓값을 반환한다. - 재귀적 타입 한정 사용 (179쪽) + public static > E max(Collection c) { + if (c.isEmpty()) + throw new IllegalArgumentException("컬렉션이 비어 있습니다."); + + E result = null; + for (E e : c) + if (result == null || e.compareTo(result) > 0) + result = Objects.requireNonNull(e); + + return result; + } + + public static void main(String[] args) { + List argList = List.of("hello", "foo"); + System.out.println(max(argList)); + } +} diff --git a/src/main/java/item30/_01_before/GenericSingletonFactory.java b/src/main/java/item30/_01_before/GenericSingletonFactory.java new file mode 100644 index 0000000..972742b --- /dev/null +++ b/src/main/java/item30/_01_before/GenericSingletonFactory.java @@ -0,0 +1,28 @@ +package item30._01_before; + +import java.util.function.Function; + +// 제네릭 싱글턴 팩터리 패턴 (178쪽) +public class GenericSingletonFactory { + + public static Function stringIdentityFunction() { + return (t) -> t; + } + + public static Function integerIdentityFunction() { + return (t) -> t; + } + + // 코드 30-5 제네릭 싱글턴을 사용하는 예 (178쪽) + public static void main(String[] args) { + String[] strings = {"삼베", "대마", "나일론"}; + Function sameString = stringIdentityFunction(); + for (String s : strings) + System.out.println(sameString.apply(s)); + + Number[] numbers = {1, 2.0, 3L}; + Function sameNumber = integerIdentityFunction(); + for (Number n : numbers) + System.out.println(sameNumber.apply(n)); + } +} diff --git a/src/main/java/item30/_01_before/Union.java b/src/main/java/item30/_01_before/Union.java new file mode 100644 index 0000000..354e1c2 --- /dev/null +++ b/src/main/java/item30/_01_before/Union.java @@ -0,0 +1,27 @@ +package item30._01_before; + +import java.util.HashSet; +import java.util.Set; + +// 제네릭 union 메서드와 테스트 프로그램 (177쪽) +public class Union { + + // 코드 30-2 제네릭 메서드 (177쪽) + public static Set union(Set s1, Set s2) { + Set result = new HashSet<>(s1); + result.addAll(s2); + return result; + } + + // 코드 30-3 제네릭 메서드를 활용하는 간단한 프로그램 (177쪽) + public static void main(String[] args) { + Set guys = Set.of("톰", "딕", "해리"); + Set stooges = Set.of("래리", "모에", "컬리"); +// Set stooges = Set.of(1, 2, 3); + Set all = union(guys, stooges); + + for (String o : all) { + System.out.println(o); + } + } +} diff --git a/src/main/java/item30/_02_after/GenericSingletonFactory.java b/src/main/java/item30/_02_after/GenericSingletonFactory.java new file mode 100644 index 0000000..6f6329f --- /dev/null +++ b/src/main/java/item30/_02_after/GenericSingletonFactory.java @@ -0,0 +1,27 @@ +package item30._02_after; + +import java.util.function.UnaryOperator; + +// 제네릭 싱글턴 팩터리 패턴 (178쪽) +public class GenericSingletonFactory { + // 코드 30-4 제네릭 싱글턴 팩터리 패턴 (178쪽) + private static UnaryOperator IDENTITY_FN = (t) -> t; + + @SuppressWarnings("unchecked") + public static UnaryOperator identityFunction() { + return (UnaryOperator) IDENTITY_FN; + } + + // 코드 30-5 제네릭 싱글턴을 사용하는 예 (178쪽) + public static void main(String[] args) { + String[] strings = {"삼베", "대마", "나일론"}; + UnaryOperator sameString = identityFunction(); + for (String s : strings) + System.out.println(sameString.apply(s)); + + Number[] numbers = {1, 2.0, 3L}; + UnaryOperator sameNumber = identityFunction(); + for (Number n : numbers) + System.out.println(sameNumber.apply(n)); + } +} diff --git a/src/main/java/item30/_02_after/Union.java b/src/main/java/item30/_02_after/Union.java new file mode 100644 index 0000000..e5f6702 --- /dev/null +++ b/src/main/java/item30/_02_after/Union.java @@ -0,0 +1,23 @@ +package item30._02_after; + +import java.util.HashSet; +import java.util.Set; + +// 제네릭 union 메서드와 테스트 프로그램 (177쪽) +public class Union { + + // 코드 30-2 제네릭 메서드 (177쪽) + public static Set union(Set s1, Set s2) { + Set result = new HashSet<>(s1); + result.addAll(s2); + return result; + } + + // 코드 30-3 제네릭 메서드를 활용하는 간단한 프로그램 (177쪽) + public static void main(String[] args) { + Set guys = Set.of("톰", "딕", "해리"); + Set stooges = Set.of("래리", "모에", "컬리"); + Set aflCio = union(guys, stooges); + System.out.println(aflCio); + } +}