Dev Book Review/Effective Java

[Effective Java] item33. 타입 안전 이종 컨테이너를 고려하라

1. 타입 안전 이종 컨테이너 패턴 매개변수화 되는 대상은 원소가 아닌 컨테이너 자신이다 Set가 있을 때 매개변수화 되는 것은 Integer가 아니라 List이다. 하나의 컨테이너에서 매개변수화 할 수 있는 타입의 수가 제한된다. 이보다 유연한 수단 : 타입 안전 이종 컨테이너 패턴 타입 안전 이종 컨테이너 패턴 (type safe heterogeneous container pattern) = 컨테이너 대신 키를 매개변수화 한 다음, 컨테이너에 값을 넣거나 뺄대 매개변수화 한 키를 함께 제공한다. 각 타입의 Class 객체를 매개변수화한 키 역할로 사용한다 : 이때 class 리터럴의 타입은 Class이다. public class Favorites{ // 타입 이종 컨테이너 추상화 public void..

[Effective Java] item33. 타입 안전 이종 컨테이너를 고려하라

728x90

1. 타입 안전 이종 컨테이너 패턴

매개변수화 되는 대상은 원소가 아닌 컨테이너 자신이다
Set<Integer>가 있을 때 매개변수화 되는 것은 Integer가 아니라 List<Integer>이다.

하나의 컨테이너에서 매개변수화 할 수 있는 타입의 수가 제한된다.
이보다 유연한 수단 : 타입 안전 이종 컨테이너 패턴

타입 안전 이종 컨테이너 패턴 (type safe heterogeneous container pattern)
= 컨테이너 대신 키를 매개변수화 한 다음, 컨테이너에 값을 넣거나 뺄대 매개변수화 한 키를 함께 제공한다.
각 타입의 Class 객체를 매개변수화한 키 역할로 사용한다 : 이때 class 리터럴의 타입은 Class<T>이다.

public class Favorites{ // 타입 이종 컨테이너 추상화
  public <T> void putFavorite(Class<T> type, T instance);
  public <T> T getFavorite(Class<T> type)
}

타입토큰 : 컴파일 타임 정보와 런타임 타입 정보를 알아내기 위해 메서드들이 주고받는 class 리터럴

public class Favorites { // 타입 이종 컨테이너 구현
  private Map<Class<?>, Object> favorites = new HashMap<>();
  public <T> void putFavorite(Class<T> type, T instance) {
    favorites.put(Objects.requireNonNull(type), type.cast(instance));
  }
  public <T> T getFavorite(Class<T> type) {
    return type.cast(favorites.get(type));
  }
}

여기서 와일드 카드 타입으로 put 할수 없다고 생각할 수 있지만, 이때 키가 와일드 카드 타입이기 때문에 넣을 수 있다.

Map의 값이 Object를이기 대문에 Class의 cast 메서드를 사용해 동적 형변환한다.
이때 cast 메서드에서 제네릭의 이점을 완벽히 사용한다 : 비검사 형변환 없이도 Favorites를 타입 안전하게 한다.

public class Class<T>{
  T cast(Object obj);
}

 

2. 타입 안전 이종 컨테이너의 제약

a. 악의적인 클라이언트가 Class 객체를 로타입으로 넘기면 Favorites 인스턴스의 타입 안정성이 쉽게 깨진다.

f.putFavorite((Class) Integer.class, "Integer의 인스턴스가 아니다.");
int favoriteInteger = f.getFavorite(Integer.class)

따라서 위에 구현한대로, put을 해줄 당시에 type.cast(instacne)와 같은 동적 형변환을 넣어주자

b. 실체화 불가 타입에는 사용할 수 없다.

  • List<String> 용 Class 객체를 얻을 수 없기 때문이다.
  • List.class 를 사용해야하지만 이렇게 했을 때 List<String>.class, List<Integer>.class 모두를 허용하여 객체 참조를 한다면 오류가 많아질 것이다.

 

3. 한정적 타입 토큰

한정적 타입 매개변수나 한정적 와일드카드를 사용하여 표현가능한 타입을 제한하는 타입토큰

애너테이션 API는 한정적 타입 토큰을 적극적으로 사용한다.

public <T extends Annotation> T getAnnotation(Class<T> annotationType)

annotationType : 애너테이션 타입을 뜻하는 한정적 타입 토큰
대상 요소에 달려있는 애너테이션을 런타임에 읽어오는 기능을 한다.
이 메서드는 토큰으로 명시한 타입의 애너테이션이 대상 요소에 달려있으면 그 애너테이션을 반환하고, 없다면 null을 반환

즉, 애너테이션된 요소는 그 키가 애너테이션 타입인 타입 안전 이종 컨테이너인 것이다.

Class<?> 타입의 객체를 한정적 타입 토큰을 받는 메서드에 넘기고 싶을 때

asSubclass 메서드 : 호출된 인스턴스 자신의 Class 객체를 인수가 명시한 클래스로 형변환 한다.

if 성공 : 인수로 받은 클래스 객체를 반환
else : ClassCastException

static Annotation getAnnotation(AnnotationElement 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))
}

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item32. 제네릭과 가변인수를 함께 쓸 때는 신중하라

1. 가변인수와 제네릭을 함께 사용할 때의 헛점 가변인수 메서드를 호출하면 가변인수를 담기위한 배열이 자동으로 하나 만들어진다. 내부로 감춰야했을 배열을 클라이언트에 노출해서 문제가 생겼다. 제네릭타입의 가변인수 메서드를 호출하면, 제네릭 타입의 배열이 생성되며, 제네릭 타입의 배열은 item28에서 말한 것 처럼, 타입을 런타임에 체크하기 때문에 클래스 캐시팅 에러가 날 가능성이 있다. 메서드 선언시, 실체화 불가 타입으로 varargs 매개변수를 선언하면 컴파일러가 경고를 보낸다. 힙오염이 가능하기 때문이다. 제네릭과 varages를 혼용하면 타입 안정성이 깨진다. 따라서 제네릭 varargs 배열 매개변수에 값을 저장하는 것은 안전하지 않다. static void dangerous(List...st..

[Effective Java] item32. 제네릭과 가변인수를 함께 쓸 때는 신중하라

728x90

1. 가변인수와 제네릭을 함께 사용할 때의 헛점

가변인수 메서드를 호출하면 가변인수를 담기위한 배열이 자동으로 하나 만들어진다.
내부로 감춰야했을 배열을 클라이언트에 노출해서 문제가 생겼다.

제네릭타입의 가변인수 메서드를 호출하면, 제네릭 타입의 배열이 생성되며, 제네릭 타입의 배열은 item28에서 말한 것 처럼, 타입을 런타임에 체크하기 때문에 클래스 캐시팅 에러가 날 가능성이 있다.

메서드 선언시, 실체화 불가 타입으로 varargs 매개변수를 선언하면 컴파일러가 경고를 보낸다.

image

힙오염이 가능하기 때문이다.

제네릭과 varages를 혼용하면 타입 안정성이 깨진다. 따라서 제네릭 varargs 배열 매개변수에 값을 저장하는 것은 안전하지 않다.

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
}

이런 위험에도 varargs 매개변수를 받으면 메서드가 실무에서 매우 유용하다.

Arrays.asList(T... a), Collections.addAll(Collection<? superT> c, T... elements),EnumSet.of(E first, E... rest)

2. @SafeVarargs 애너테이션

a. @SuppressWarnings("unchecked")

호출하는 곳 마다 이 애너테이션을 달아 경고를 숨겨야했다.

지루하고, 가독성을 떨어드리고, 때로는 진짜 문제를 알려주는 경고마저 숨긴다.

b. @SafeVarargs

메서드 작성자가 그 메서드가 타입 안전함을 보장하는 장치

컴파일러가 이 약속을 믿고 이 메서드가 안전하지 않을 수 있다는 경고를 더이상 하지 않는다.

3. 메서드가 안전한지 확신 할 수 있을 때

  • 메서드가 varargs 매개변수를 담는 배열에 아무것도 저장하지 않을 때
  • varargs 배열의 참조가 밖으로 노출 되지 않을 때

즉, 순수하게 인수들을 전달하는 일만 할 때 메서드가 안전하다.

4. 제네릭 varargs 매개변수 배열에 다른 메서드가 접근하도록 허용하지 말자

자신의 제네릭 매개변수 배열의 참조를 노출하므로 안전하지 않다.

  • 힙 오염을 메서드를 호출한 쪽의 콜스택으로까지 전이하는 결과를 낳는다.
static <T> T[] toArray(T... args){
  return args; // 참조가 밖으로 
}

예외사항

a. @SafeVarargs로 제대로 애노테이트 된 또다른 varargs 메서드에 넘기는건 안전하다.

b. 이 배열 내용의 일부 함수를 호출만 하는(varargs를 받지 않는) 일반 메서드에 넘기는 것도 안전하다.

5. varargs 매개변수를 List 매개변수로 바꿀 때

static <T> List<T> flatten(List<List<? extends T>> lists){
  List<T> restul = new ArrayList<>();
  for(List<? extends T> list : lists)
    result.addAll(list);
  return result;
}
  • 컴파일러가 이 메서드의 타입 안전성을 검증할 수 있다.
  • @SafeVarargs 애너테이션을 달지 않아도 된다.
  • 실수로 안전하다고 판단할 걱정도 없다.
  • 기존의 varargs는 배열로 메서드의 타입 안정성 검증이 불가능 했었다.

 

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item31. 한정적 와일드 카드를 사용해 API 유연성을 높여라

1. 매개변수화 타입의 불공변 매개변수화 타입은 불공변이다(invariant) : 서로 다른 타입 Type1,Type2가 있을 때 List 은 List 의 하위타입도 아니고 상위타입도 아니다. (리스코프 치환 원칙에 어긋난다.) 이때 불공변 방식보다 유연한 방식 : 한정적 와일드카드 타입 2. 한정적 와일드 카드 타입을 이용한 확장 유연성 극대화를 위해 원소의 생산자나 소비자용 입력 매개변수에 왈일드카드 타입을 사용하라. 입력 매개변수가 생산자와 소비자 역할을 동시에 한다면 와일드 카드 타입을 써도 좋을 게 없다. public void pushAll(Iterable list, int i, int j); 와일드 카드 타입을 사용하면, set, add와 같은 추가하는 메서드를 작성하지 못한다 따라서 실제 타..

[Effective Java] item31. 한정적 와일드 카드를 사용해 API 유연성을 높여라

728x90

1. 매개변수화 타입의 불공변

매개변수화 타입은 불공변이다(invariant) : 서로 다른 타입 Type1,Type2가 있을 때 List<Type1>List<Type2> 의 하위타입도 아니고 상위타입도 아니다. (리스코프 치환 원칙에 어긋난다.)

이때 불공변 방식보다 유연한 방식 : 한정적 와일드카드 타입

 

2. 한정적 와일드 카드 타입을 이용한 확장

유연성 극대화를 위해 원소의 생산자나 소비자용 입력 매개변수에 왈일드카드 타입을 사용하라.

입력 매개변수가 생산자와 소비자 역할을 동시에 한다면 와일드 카드 타입을 써도 좋을 게 없다.

public void pushAll(Iterable<? extends E> src) { // 생산자
    for (E e : src)
    push (e);
}
public void popAll(Collection<? super E> dst) { // 소비자
  dst.addAll(list);
  list.clear();
}

PECS : producer-extends, consumer-super (= Get and Put Princlple)

매개변수화 타입 T가 생산자면 <? extends T>를 사용하고 소비자라면 <? super T>를 사용하라

 

3. 반환타입에서의 한정적 와일드 카드 타입

반환타입에는 한정적 와일드 카드 타입을 사용하면 안된다
유연성을 높여주지 않고 클라이언트 코드에서도 와일드 카드 타입을 사용하게 하기 때문이다.

public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2)

클래스 사용자가 와일드 카드 타입을 신경써야 한다면 그 API에 문제가 있을 가능성이 크다

 

4. 매개 변수와 인수

  • 매개변수(parameter) : 메서드 선언에 정의한 변수 void add(int value)
  • 인수(argument) : 메서드 호출 시 넘기는 '실제 값' add(10)
  • 타입 매개변수(type parameter) : Class Set<T> {...}
  • 타입 인수(type argument) : Set<Integer> = ...

 

5. 예시

public static <E extends Comparable<? super E>> E max(List<? extends E> list)

이렇 게 구현했을 때만 아래 리스트를 max로 처리하는 것이 가능하다.

List<ScheduledFuture<?>> scheduledFulter = ... ;

 

6. 타입매개 변수가 하나이면 와일드카드로 대체하기

메서드 선언에 타입매개변수가 한번만 나오면 와일드 카드로 대체하라

비한정적 타입 매개변수 -> 비한정적 와일드 카드
한정적 타입 매개변수 -> 한정적 와일드 카드

public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i, int j);

와일드 카드 타입을 사용하면, set, add와 같은 추가하는 메서드를 작성하지 못한다 따라서 실제 타입으로 변환해주기 위해 제네릭의 타입 매개변수를 사용해야하는데 이때

와일드 카드 타입을 실제 타입으로 바꿔주는 private 도우미 메서드를 따로 작성한다.
실제 타입을 알아내려면 이 도우미 메서드는 제네릭 메서드여야하기 때문이다.

public class Swap {
    public static void swap(List<?> list, int i, int j) {
        swapHelper(list, i, j);
    }

    // 와일드카드 타입을 실제 타입으로 바꿔주는 private 도우미 메서드
    private static <E> void swapHelper(List<E> list, int i, int j) {
        list.set(i, list.set(j, list.get(i)));
    }
}

외부에서는 와일드 타입 기반의 멋진 선언을 유지할 수 있다.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item30. 이왕이면 제네릭 메서드로 만들라

1. 제네릭 메서드 만들기 메서드도 제네릭으로 만들 수 있다. ex ) Collections의 '알고리즘' 메서드 메서드 선언에서 원소타입을 타입 매개변수로 지정한다. 메서드 안에서 이 타입 매개변수를 사용하게 수정한다. 타입 매개변수 목록은 메서드의 제한자와 반환타입 사이에 온다. 한정적 와일드 카드 타입을 사용하면, 반환타입 입력타입 등을 좀더 유연하게 개선할 수 있다. public static Set union(Set s1, Set s2){ Set result = new HashSet(s1); result.addAll(s2); return result; } 2. 제네릭 : 불변 객체를 여러 타입으로 활용할 수 있게 만드는 방법 불변객체가 제네릭 타입일 때 여러 타입으로 활용이 가능하다. 요청 타입 ..

[Effective Java] item30. 이왕이면 제네릭 메서드로 만들라

728x90

1. 제네릭 메서드 만들기

메서드도 제네릭으로 만들 수 있다. ex ) Collections의 '알고리즘' 메서드

  • 메서드 선언에서 원소타입을 타입 매개변수로 지정한다.
  • 메서드 안에서 이 타입 매개변수를 사용하게 수정한다.
  • 타입 매개변수 목록은 메서드의 제한자와 반환타입 사이에 온다.
  • 한정적 와일드 카드 타입을 사용하면, 반환타입 입력타입 등을 좀더 유연하게 개선할 수 있다.
public static <E> Set<E> union(Set<E> s1, Set<E> s2){
  Set<E> result = new HashSet<>(s1);
  result.addAll(s2);
  return result;
}

 

2. 제네릭 : 불변 객체를 여러 타입으로 활용할 수 있게 만드는 방법

불변객체가 제네릭 타입일 때 여러 타입으로 활용이 가능하다. 요청 타입 변수에 맞게 객체의 타입을 바꾸어주는 "제네릭 싱글턴 팩터리"가 필요하다.

 

a. 함수객체 : 함수 내부에 들어가는 객체

List<String> str = Arrays.asList("a","b","c");
str.sort(Collectioins.reverseOrder());

제네릭 싱글턴 팩터리 패턴을 띄고있다.

reverseComparator를 형변환하는 코드로 비검사 형변환 경고가 발생하여, @SuppressWarning 애너테이션을 추가하여 경고 없이 컴파일되도록 숨겼다.

 

b. 재귀적 타입 한정 (recursive type bound)

자기 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용범위를 한정한다.

public static <E extends Comparable<E>> E max(Collection<E> c);

모든 타입 E는 자기 자신과 같은 타입인 원소 모두와 비교 가능하다

cf) 시뮬레이트한 셀프타입 관용구 - [아이템 2]

- 관련 자바봄 이슈) 

재귀적 타입 한정을 이용하는 제네릭타입이다.

추상메서드 self 를 지원하여 하위클래스에서 형변환 하지 않고도 메서드 연쇄를 지원할 수 있으며. self 타입이 없는 자바를 위한 우회방법을 이야기 한다.

public abstract class PayCard {
    public enum Benefit {
        POINT("포인트"), SALE("할인"), SUPPORT("연회비지원");
        Benefit(String benefit) {
        }
    }

    final Set<Benefit> benefits;

    abstract static class Builder<T extends Builder<T>> { // 재귀적 타입한정
        EnumSet<Benefit> benefits = EnumSet.noneOf(Benefit.class);

        public T addBenefit(Benefit benefit) {
            this.benefits.add(benefit);
            return self();
        }

        abstract PayCard build();

        protected abstract T self();
    }

    PayCard(Builder<?> builder) {
        benefits = builder.benefits.clone();
    }
}
public static class Builder extends PayCard.Builder<Builder>{
        private final Sale sale;

        public Builder(Sale sale) {
            this.sale = sale;
        }

        @Override
        NaverPayCard build() {
            return new NaverPayCard(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }

재귀적 타입 한정을 사용함으로 인해서, PayCard의 하위타입인 NaverPayCard에서 상위 클래스의 메서드를 호출 할 수 있게 된다.

https://github.com/Java-Bom/ReadingRecord/issues/75

 

 

[아이템 30] 시뮬레이트한 셀프 관용구 · Issue #75 · Java-Bom/ReadingRecord

180p 시뮬레이트한 셀프관용구에 대한 설명이 나오는데, 이게 2장의 Builder패턴 pizza를 상속받은 nypizza 빌더패턴에 대한 이야기야, 이거 한번 구현해보고 실제로 왜 재귀적 타입한정을 써야 이런 ��

github.com

 

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item29. 이왕이면 제네릭 타입으로 만들라

1. 제네릭클래스로 만드는 방법 a. 클래스 선언에 타입매개변수를 추가한다. 보통 E를 많이 사용한다. 제네릭 필드를 쓴다는 것을 명시하는 것이다. b. 실체화 불가 타입으로는 배열을 만들 수 없으니 해결한다. E[] element = new E[DEFAULT_INITAL_CAPACITY] b-1. 제네릭 배열을 금지하는 제약을 대놓고 우회한다. E[] elemet = (E[]) new Object[DEFAULT_INITAL_CAPACITY] 비검사 형변환이 프로그램의 타입 안정성을 해치지 않는지를 확인한다. > 이후 @SuppressWarnings 애너테이션으로 해당 경고를 숨긴다. 배열이 private 필드에 저장된다. 클라이언트로 반환되거나 다른 메서드에 전달되는 일이 없다. 배열에 저장되는 원소의..

[Effective Java] item29. 이왕이면 제네릭 타입으로 만들라

728x90

1. 제네릭클래스로 만드는 방법

a. 클래스 선언에 타입매개변수를 추가한다.

  • 보통 E를 많이 사용한다.
  • 제네릭 필드를 쓴다는 것을 명시하는 것이다.

b. 실체화 불가 타입으로는 배열을 만들 수 없으니 해결한다.

E[] element = new E[DEFAULT_INITAL_CAPACITY]

b-1. 제네릭 배열을 금지하는 제약을 대놓고 우회한다.

E[] elemet = (E[]) new Object[DEFAULT_INITAL_CAPACITY]

비검사 형변환이 프로그램의 타입 안정성을 해치지 않는지를 확인한다. > 이후 @SuppressWarnings 애너테이션으로 해당 경고를 숨긴다.

  • 배열이 private 필드에 저장된다.
  • 클라이언트로 반환되거나 다른 메서드에 전달되는 일이 없다.
  • 배열에 저장되는 원소의 타입은 항상 E 이다.

특징

  • 가독성이 더 좋다. : E 타입만을 받는다는 점을 어필한다.
  • 형변환을 배열 생성시 단한번만 해주면된다.
  • 배열의 런타임 타입이 컴파일타임 타입과 달라 힙오염이 일어날 수 있다.

b-2. 배열 필드의 타입을 Object[]로 바꾼다.

E result = (E) elements[--size];

E가 실체화 불가 타입이므로 컴파일러는 런타임에 이뤄지는 형변환이 안전한지 증명할 방법이 없다. > 이후 비검사 형변환을 수행하는 할당문에서만 경고를 숨긴다.

특징

  • 배열에서 원소를 읽을 떄마다 형변환 해주어야 한다.
  • 힙오염이 해가되지 않는다.

 

2. 제네릭 배열을 사용하는 이유

  • 자바가 리스트를 사용하는게 항상 가능하지도, 꼭 더 좋은 것도아니다. 리스트도 결국은 기본타입인 배열을 사용해 구현해야하기 때문이다.
  • 성능향상의 이슈로 배열을 사용할 수 있다.
  • 제네릭타입의 타입매개변수에는 primitive 타입을 사용할 수 없다. 박싱된 기본타입으로 우회할 수 있긴하다.

 

3. 한정적 타입 매개변수

class DelayQueue<E extends Delayed> implements BlockingQueue<E>

Delayed.class의 하위타입만 받는다는 의미이다. (모든 타입은 자기 자신의 하위 타입)

ClassCastException을 걱정할 필요가 없다.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item28. 배열보다는 리스트를 사용하라

1. 배열과 제네릭의 차이 배열 공변 (convariant) - Sub가 Super의 하위타입이라면 배열 Sub[]는 배열 Super[]의 하위타입이다. (함께 변한다) 배열에서는 실수를 런타임에 타입 오류를 알 수 있다 Object[] objectArray = new Long[1]; objectArray[0] = "타입이 달라 넣을 수 없다" // ArrayStoreException 실체화(reify)된다 : 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다. 제네릭 불공변 (invariant) - 서로 다른 타입 Type1,Type2가 있을 때 List 은 List 의 하위타입도 아니고 상위타입도 아니다 리스트에서는 컴파일할 때 타입 오류를 바로 알 수 있다. List ol = new Ar..

[Effective Java] item28. 배열보다는 리스트를 사용하라

728x90

1. 배열과 제네릭의 차이

배열

  • 공변 (convariant) - SubSuper의 하위타입이라면 배열 Sub[]는 배열 Super[]의 하위타입이다. (함께 변한다)
  • 배열에서는 실수를 런타임에 타입 오류를 알 수 있다
  Object[] objectArray = new Long[1];
  objectArray[0] = "타입이 달라 넣을 수 없다" // ArrayStoreException

실체화(reify)된다 : 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다.

제네릭

  • 불공변 (invariant) - 서로 다른 타입 Type1,Type2가 있을 때 List<Type1>List<Type2> 의 하위타입도 아니고 상위타입도 아니다
  • 리스트에서는 컴파일할 때 타입 오류를 바로 알 수 있다.
  List<Object> ol = new ArrayList<Long>();    // 호환되지 않는 타입이다.
  ol.add("타입이 달라 넣을 수 없다.")

소거(erasure)된다 : 원소 타입을 컴파일 타임에만 검사하며 런타임에는 알 수 없다.
제네릭 지원 전과 제네릭 타입을 함게 사용할 수 있게 해주는 메커니즘이다.

 

2. 제네릭 배열은 사용불가하다.

배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다.
new List<E>[], new List<String>[], new E[] : 컴파일시 제네릭 생성오류를 일으킨다.

이유 : 타입안전하지 않기 때문이다
컴파일러가 자동 생성한 형변환 코드에서 런타임에 ClassCastException이 발생할 수 있다 : 제네릭 타입의 취지와 맞지 않다.

 

3. 실체화 불가 타입

E, List<E>, List<String> : 실체화 불가 타입

실체화 되지 않아 런타임에는 컴파일 타임보다 타입점보를 적게 가지는 타입

List<?>, Map<?,?> : 소거 메커니즘 때문에 매개변수화 타입 가운데 실체화 가능한 타입 - 비한정적 와일드카드 타입

 

4. 배열의 불편함

a. 제네릭 컬렉션에서는 자신의 원소 타입을 담은 배열을 반환하는게 불가능하다.

아이템 33의 타입 안전 이종 컨테이너를 이용하여 자신의 원소타입을 추론할 수있다 (우회)

b. 제네릭 타입과 가변인수 메서드를 함께 쓰면 해석하기 어려운 경고 메세지를 받게된다.

가변인수 메서드를 호출할 때마다. 가변인수를 담는 배열이 만들어지는데, 이때 그 배열의 원소가 실체화 불가 타입이면 경고가 뜬다. @SafeVarargs로 해결한다.

 

5. 배열대신 리스트를 사용하자

  • 장점 : 타입 안정성과 상호 운용성이 좋아진다.
  • 단점 : 코드가 조금 복잡해지고 성능이 살짝 나빠질 수도 있다.
public class Chooser<T>{
  private final List<T> choiceList;

  public Chooser(Collection<T> choices){
    choiceList = new ArrayList<>(choices);
  }

  public T choose(){
    Random rnd = ThreadLocalRandom.current();
    return choiceList.get(rnd.netInt(choiceList.size()));
  }
}

List를 사용하면 런타임에 ClassCastException을 만날 일은 없다.

public class Chooser<T>{
  private final T[] choiceArray;

  public Chooser(Collection<T> choices){
    choicesArra = (T[]) choices.toArray; 
  }

    public Object choose(){
    Random rnd = ThreadLocalRandom.current();
    return choiceArray[rnd.nextInt(choiceArray.length)];
  }
}

T[] 로의 타입캐스팅 과정에서 경고가 뜬다. 제네릭에서는 원소의 타입정보가 소거되어 런타임에는 타임 정보를 알 수 없기 때문이다. item 27 비검사 경고를 제거하라는 말에 따라 위험요소를 제거할 수 있는 최선의 방법은 List로 구현하는 것이었다.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item 27. 비검사 경고를 제거하라

1. 할수있는 한 모든 비검사 경고를 제거하자 제네릭을 사용하기 시작했을 때 볼 수 있는 수많은 컴파일러 경고 비검사 형변환 경고 비검사 메서드 호출 경고 비검사 매개변수화 가변인수 타입 경고 비검사 변환 경고 새로 작성한 코드가 한번에 깨끗하게 컴파일되리라 기대하지는 말자 Java7 부터 제네릭 타입추론이 가능해졌다. Set exaltation = new HashSet(); 할 수 있는 한 모든 비검사 경고를 제거하라 : 모두 제거하면 타입 안정성이 보장된다. 런타임에 ClassCastException이 발생할 일이 없고, 내가 의도한대로 잘 동작한다. 2. @SuppressWarning a. 경고를 제거할 수는 없지만 타입 안전하다고 확신할 수 잇을 때 @SuppressWarning으로 경고를 숨기자..

[Effective Java] item 27. 비검사 경고를 제거하라

728x90

1. 할수있는 한 모든 비검사 경고를 제거하자

제네릭을 사용하기 시작했을 때 볼 수 있는 수많은 컴파일러 경고

  • 비검사 형변환 경고
  • 비검사 메서드 호출 경고
  • 비검사 매개변수화 가변인수 타입 경고
  • 비검사 변환 경고

새로 작성한 코드가 한번에 깨끗하게 컴파일되리라 기대하지는 말자

Java7 부터 제네릭 타입추론이 가능해졌다.

Set<Lark> exaltation = new HashSet<>();

할 수 있는 한 모든 비검사 경고를 제거하라 : 모두 제거하면 타입 안정성이 보장된다.
런타임에 ClassCastException이 발생할 일이 없고, 내가 의도한대로 잘 동작한다.

 

2. @SuppressWarning

a. 경고를 제거할 수는 없지만 타입 안전하다고 확신할 수 잇을 때 @SuppressWarning으로 경고를 숨기자

  • 검증 없이 경고 숨길 때 : 경고없이 컴파일이 되어도 런타임에 여전히 ClassCastException을 던질 수 있다.
  • 안전한데 안숨길 때 : 진짜 문제를 알리는 새로운 경고가 나와도 눈치채지 못할 수 있다.

b. 가능한 한 좁은 범위에 적용하자 : 변수 선언, 짧은 메서드, 생성자
범위가 필요이상으로 넓어지면, 지역변수를 하나 선언하고 그 변수에 애너테이션을 달아준다.

c. @SuppressWarning("unchecked") 애너테이션을 사용할 때 그 경고를 무시해도 안전한 이유를 항상 주석으로 남겨야한다.
다른사람이 코드를 이해하는데 도움이 되고, 다른사람이 그 코드를 잘못 수정할 경우를 막는다.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item26. 로타입은 사용하지 말라

1. 용어정리 public class Example{ private T member; } 제네릭 클래스[인터페이스] : 클래스[인터페이스] 선언에 타입 매개변수(type parameter)가 쓰인다. Example.class 제네릭 타입(Generic Type) : 제네릭 클래스와 제네릭 인터페이스를 통틀어 이르는 말. Example 매개변수화 타입(parameterized Type) : 각각의 제네릭 타입은 parameterizedType을 선언함. Example 타입 매개 변수(Type parameter) : 제네릭 선언에 사용된 매개변수 formalType : Example actualType : Example 로타입(raw type) : 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않았을 때. ..

[Effective Java] item26. 로타입은 사용하지 말라

728x90

1. 용어정리

public class<T> Example{
     private T member; 
}
  • 제네릭 클래스[인터페이스] : 클래스[인터페이스] 선언에 타입 매개변수(type parameter)가 쓰인다. Example.class
  • 제네릭 타입(Generic Type) : 제네릭 클래스와 제네릭 인터페이스를 통틀어 이르는 말. Example<T>
  • 매개변수화 타입(parameterized Type) : 각각의 제네릭 타입은 parameterizedType을 선언함. Example<String>
  • 타입 매개 변수(Type parameter) : 제네릭 선언에 사용된 매개변수 <T>
  • formalType : Example<E>
  • actualType : Example<String>
  • 로타입(raw type) : 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않았을 때. Example
    로타입은 제네릭 전후 코드의 호환을 위한 것이며, 동작하지만 좋은 예는 아니다.

 

2. 제네릭의 타입 안정성

오류는 가능한 한 발생 즉시, 이상적으로는 컴파일 타임에 발견하는 것이 좋다.

a. 로타입의 단점 : 컴파일 타임에 타입 정보를 알지 못한다.

ClassCastException과 같은 런타임에야 알아챌 수 있는 에러를 만들기 쉽다.

실제로 아래 예제는, 로타입의 사용으로 인해 컴파일 타임에는 문제가 없으나, 실제 런타임에는 ClassCastException이 터진다.
Integer값을 String값으로 캐스팅하려 했기 때문이다.

public class Raw {
    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        unsafeAdd(strings, Integer.valueOf(42));
        String s = strings.get(0); // 컴파일러가 자동으로 형변환 코드를 넣어준다.
    }

    private static void unsafeAdd(List list, Object o) {
        list.add(o);
    }
}
 

b. 제네릭의 장점 : 컴파일 타임에 타입 선언이 녹아든다.

컴파일러가 타입선언에 대해 인지하고 있기 때문에, 아무런 경고 없이 컴파일되면 의도대로 동작할 것임을 보장한다.

public class Raw {
    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        unsafeAdd(strings, "42");   // 컴파일 타임에 에러를 체크해주어서 바꿀 수 있다.
        String s = strings.get(0);
    }

    private static <T> void unsafeAdd(List<T> list, T o) { // 제네릭 선언
        list.add(o);
    }
}

컴파일러가, 컬렉션에서 원소를 넣는 모든 곳에 보이지 않는 형변환을 추가하여 절대 실패하지 않음을 보장하였다.

++ 추가 내용

 

3. 로타입은 절대로 사용하지 말자.

제네릭이 안겨주는 안정성과 표현력을 모두 잃게된다.

a. 로타입이 만들어진 이유 - 호환성

기존 코드를 모두 수용하면서 제네릭을 사용하는 새로운 코드와 맞물려돌아가게 하기 위해서이다.
호환성 : 로타입 지원 + 제네릭 구현시 소거(erasure) 방식을 이용

List vs List<Object>

  • List : 로타입 - 제네릭타입에서 완전 발은 떼었다.
  • List<Object> : 임의 객체를 허용하는 매개변수화 타입 - 타입에 대한 정보를 컴파일러에 알려주었다.
  • 제네릭의 하위타입 규칙 : List<String>은 로타입인 List의 하위타입이지만, List<Object>의 하위타입은 아니다.

로타입을 사용하면 타입 안정성을 잃게된다.

 

4. 타입 안전 + 유연 : 비한정 와일드카드 타입

비한정 와일드카드 타입(unbounded wildcard type) : 제네릭 타입을 쓰고 싶지만, 실제 타입 매개변수가 무엇인지 신경쓰고 싶지 않을 때 사용한다. Set<E>의 비한정 와일드카드 타입은 Set<?>

static int numElementsInCommon(Set<?> s1, Set<?> s2){...}

와일드카드 타입은 안전하고 로타입은 안전하지 않다.

  • 로타입 : 아무 원소나 넣을 수 있어 타입 불변식을 훼손하지 쉽다.
  • 와일드카드 타입 : Collection<?>에는 어떤 원소도 넣을 수 없다. (null 외에는)

    컴파일 타임에 오류메세지를 볼 수 있다.
    컬렉션의 타입 불변식을 훼손하지 못하게 막고, 컬렉션에서 꺼낼 수 있는 객체의 타입도 알수 없게 한다.

 

5. 로타입의 규칙예외

a. class 리터럴에는 로타입을 사용한다

자바 명세 자체가 class 리터럴에 매개변수화 타입을 사용하지 못하게 하였다.

  • 허용 : List.class, String[].class, int.class
  • 불가 : List<String>.class, List<?>.class

b. Instanceof 연산자는 로타입을 사용한다.

instanceof 연산자는 비한정 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다.

로타입이든 비한정적 와일드카드 타입이든 instanceof는 똑같이 동작한다. : <?> 코드만 지저분해지므로 로타입

런타임에는 제네릭 타입정보가 지워진다.

if(o instanceof Set){            // 로타입
  Set<?> s = (Set<?>) o;    // 와일드카드 타입
}

 

6. 용어정리

한글 영문
매개변수화 타입 parameterized type List<String>
실제 타입 매개변수 actual type parameter String
제네릭 타입 generic type List<E>
정규 타입 매개변수 formal type parameter E
비한정적 와일드카드 타입 unbounded wildcard type List<?>
로 타입 raw type List
한정적 타입 매개변수 bounded type parameter <E extends Number>
재귀적 타입 한정 recursive type bound <T extends Comparable<T>>
한정적 와일드카드 타입 Bounded wildcard type <? extends Number>
제네릭 메서드 generic method static <E> List<E> asList(E[] a)
타입 토큰 type token String.class

댓글

Comments