목록전체 글 (115)
도당탕탕
int enum 패턴 public static final int APPLE_FUJI = 0; public static final int APPLE_PIPPIN = 1; public static final int ORANGE_NAVEL = 0; // ... 위와 같이 정의할 경우, APPLE_FUJI == ORANGE_NAVEL 과 같은 연산에 컴파일 에러가 발생하지 않아서 타입 안전하지 않다. 그리고 함수에 특정 enum 타입을 강요할 수 없다. 두 번째 문제점은 int enum 상수를 출력 문자열로 변환하는 것이 쉽지 않다는 것이다. 왜냐하면 값을 출력하면 숫자만 나오기 때문이다. enum 타입 public enum Apple {FUJI, PIPPIN, GRANNY_SMITH } public enum ..
제네릭은 Set, Map등의 컬렉션과 ThreadLocal, AtomicReference 등의 단일 원소 컨테이너에도 흔히 쓰인다. 이렇게 매개변수화되는 대상은 컨테이너 자신이기 때문에 하나의 컨테이너에서 매개변수화 할 수 있는 타입의 수가 제한된다. 예를 들어 Set에서 원소의 타입을 뜻하는 단 하나의 타입 매개변수만 있으면 되고, Map에서는 key와 value의 타입 2개만 필요하다는 식으로 보면 된다. 하지만 종종 프로그램을 구현하다 보면 좀 더 유연한 수단이 필요할 때도 있다. 이는 쉽게 해결할 수 있는데 바로 타입 안전 이종 컨테이너 패턴으로 해결하면 된다. 타입 안전 이종 컨테이너 패턴 이 패턴은 다음과 같다. 컨테이너 대신 키를 매개변수 화한 다음, 컨테이너에 값을 넣거나 뺄 때 매개변수화..
제너릭 가변인자 배열을 사용할 때 발생하는 문제 가변인자 함수를 호출할때, 가변인자 파라미터들을 저장하기 위한 배열이 생성되고, 다음과 같은 문제가 발생할 수 있다. static void dangerous(List... stringLists) { List intList = List.of(42); Object[] objects = stringLists; objects[0] = intList; // Heap pollution String s = stringLists[0].get(0); // ClassCastException } 위 코드는 다음의 warning을 발생시킨다. warning : [unchecked] Possible heap pollution from parameterized varargs typ..
매개변수화 타입은 불공변이다. 즉 서로 다른 타입 예를 들어 String, Object이 있을 때 List 와 List은 서로 하위 타입도 상위 타입도 아니라는 얘기다. 좀 더 쉽게 말하자면, List는 어떤 객체든 넣을 수 있지만 List은 문자열만 넣을 수 있다. 즉 List은 List가 하는 일을 제대로 수행하지 못하니 하위 타입이 될 수 없다. 하지만 코딩을 하다 보면 유연한 방식이 필요할 때가 있다. 다음 예를 보자 Stack 예제 - 와일드 카드 사용 x public class Stack { public Stack() {....} public void push(E e) {...} public E pop() {...} public boolean isEmpty() {...} public void ..
위 코드는 컴파일은 되지만 타입 안전성을 보장해주지 못한다. public static Set union(Set s1, Set s2) { Set result = new HashSet(s1); result.addAll(s2); return result; } 다음은 제너릭 함수로 수정한 것이다. public static Set union(Set s1, Set s2) { Set result = new HashSet(s1); result.addAll(s2); return result; } public static void main(String[] args) { Set guys = Set.of("Tom","Dick","Harry"); Set stooges = Set.of("Larry","Moe","Curly"); ..
JDK가 제공하는 제네릭 타입과 메서드를 사용하는 일은 일반적으로 쉬운 편이지만, 제네릭 타입을 새로 만드는 일은 쉽지는 않다. 다음 예를 보자. Object 기반 스택 public class Stack { private Object[] elements; // Object 선언 private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_INITIAL_CAPACITY]; // Object로 생성 } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Obje..
배열은 제너릭 타입과 비교해서 두 가지 차이가 있다. 첫 번째로 배열은 covariant 하다. 즉, 만약 Sub 가 Super 의 서브 타입이라면, Sub[] 또한 Super[] 의 서브 타입이 된다는 뜻이다. 반면 제너릭은 invariant 하다 : 두 개의 서로 다른 타입 Type1, Type2 가 있을 때, List 은 List 의 서브 타입과 슈퍼 타입 모두 될 수 없다. 두 번째는 배열은 reified(구체화된) 하다. 이는 배열은 런타임에 그들의 요소 타입을 알아내고 강요할 수 있다는 의미이다. Object[] objectArray = new Long[1]; objectArray[0] = "I don't fit in"; // ArrayStoreException 반면 제너릭은 erasure에 ..
제네릭을 사용하다 보면 컴파일러 경고가 나타나게 된다. 예를 들어 비검사형 변환경고, 비검사 메서드 호출 경고, 비검사 매개변수화 가변인수 타입 경고 등 다양하게 맞이할 것이다. 대부분의 비검사 경고는 쉽게 제거할 수 있다. 다음 예를 보자. 비검사 매개변수 경고 - HashSet 선언 Set exaltation = new HashSet(); 컴파일러에서 -Xlint:uncheck 옵션을 주면 어디가 잘못됐는지 알려준다. 여기서는 HashSet에 매개변수를 명시하지 않아서 경고가 나타나는 건데 연산자를 사용하면 경고를 해결할 수 있다. (컴파일러가 올바른 실제 타입 매개변수를 추론해준다.) 제거하기 어려운 경고 하지만 제네릭을 사용할 때 제거하기 어려운 경고도 있다. 하지만 경고를 제거하면 할수록 그 코..