핵심 정리

• 제네릭 가변인수 배열에 값을 저장하는 것은 안전하지 않다.

**// Anti Pattern** 
// 제네릭 varargs 배열 매개변수에 값을 저장하는 것은 안전하지 않다. (191-192쪽)
public class Dangerous {
    // 코드 32-1 제네릭과 varargs를 혼용하면 타입 안전성이 깨진다! (191-192쪽)
    static void dangerous(List<String>... stringLists) {
        List<Integer> 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!"));
    }
}

• 힙 오염이 발생할 수 있다. (컴파일 경고 발생)

• 자바7에 추가된 @SafeVarargs 애노테이션을 사용할 수 있다.

// 코드 32-3 제네릭 varargs 매개변수를 안전하게 사용하는 메서드 (195쪽)
**// generic 가변 인자 
// Good Pattern** 
public class FlattenWithVarargs {

    **@SafeVarargs**
    static <T> List<T> flatten(List<? extends T>... lists) {
        List<T> result = new ArrayList<>();
        for (List<? extends T> list : lists)
            result.addAll(list);
        return result;
    }

    public static void main(String[] args) {
        List<Integer> flatList = flatten(
                List.of(1, 2), List.of(3, 4, 5), List.of(6,7));
        System.out.println(flatList);
    }
}

• 제네릭 가변인수 배열의 참조를 밖으로 노출하면 힙 오염을 전달할 수 있다.

예외적으로, @SafeVarargs를 사용한 메서드에 넘기는 것은 안전하다

// 코드 32-3 제네릭 varargs 매개변수를 안전하게 사용하는 메서드 (195쪽)
public class FlattenWithVarargs {

    @SafeVarargs
    static <T> List<T> flatten(List<? extends T>... lists) {
        List<T> result = new ArrayList<>();
        for (List<? extends T> list : lists)
            result.addAll(list);
        return result;
    }

    public static void main(String[] args) {
        List<Integer> flatList = flatten(
                List.of(1, 2), List.of(3, 4, 5), List.of(6,7));
        System.out.println(flatList);
    }
}

• 예외적으로, 배열의 내용의 일부 함수를 호출하는 일반 메서드로 넘기는 것은 안전하다.

• 아이템 28의 조언에 따라 가변인수를 List로 바꾼다면

• 배열없이 제네릭만 사용하므로 컴파일러가 타입 안정성을 보장할 수 있다.

• @SafeVarargs 애너테이션을 사용할 필요가 없다.

• 실수로 안전하다고 판단할 걱정도 없다

// 배열 대신 List를 이용해 안전하게 바꿘 PickTwo (196쪽)
public class SafePickTwo {
    **static <T> List<T> 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<String> attributes = pickTwo("좋은", "빠른", "저렴한");
        System.out.println(attributes);
    }
}

===

**// 코드 32-4 제네릭 varargs 매개변수를 List로 대체한 예** - 타입 안전하다. (195-196쪽)
public class FlattenWithList {
    **static <T> List<T> flatten(List<List<? extends T>> lists) {**
        List<T> result = new ArrayList<>();
        for (List<? extends T> list : lists)
            result.addAll(list);
        return result;
    }

    public static void main(String[] args) {
        List<Integer> flatList = flatten(List.of(
                List.of(1, 2), List.of(3, 4, 5), List.of(6,7)));
        System.out.println(flatList);
    }
}

완벽 공략 45. ThreadLocal

쓰레드 지역 변수

• 모든 멤버 변수는 기본적으로 여러 쓰레드에서 공유해서 쓰일 수 있다. 이때 쓰레드 안 전성과 관련된 여러 문제가 발생할 수 있다.

• 경합 또는 경쟁조건 (Race-Condition)

• 교착상태 (deadlock)

• Livelock

• 쓰레드 지역 변수를 사용하면 동기화를 하지 않아도 한 쓰레드에서만 접근 가능한 값이 기 때문에 안전하게 사용할 수 있다.

• 한 쓰레드 내에서 공유하는 데이터로, 메서드 매개변수에 매번 전달하지 않고 전역 변 수처럼 사용할 수 있다

public class ThreadLocalExample implements Runnable {

    // SimpleDateFormat is not thread-safe, so give one to each thread
    private static final ThreadLocal<SimpleDateFormat> 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

스레드 지역 랜덤값 생성기

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;
		
		// Atomic Class Internal main method 
    public synchronized int compareAndSwap(int expectedValue, int newValue)
    {
        int readValue = value;
        if (readValue == expectedValue)
            value = newValue;
        return readValue;
    }
}