[Effective Java] 아이템

리터럴 및 클래스 리터럴

문자 그대로(리터럴) 소스 코드에 직접 나타납니다. 데이터 값 자체수단.

예를 들어 정수 리터럴은 다음과 같은 숫자 값입니다. 23이고 문자열 리터럴은 따옴표로 묶인 문자열 값입니다. B. “안녕하세요.”

클래스 리터럴은 클래스의 이름을 참조하는 데 사용됩니다. 예를 들어 문자열.수업같은 클래스 이름 뒤에 .수업추가하여 사용합니다.

그리고 이러한 클래스 리터럴의 유형은 Class.


예를 들어 String.class의 유형은 Class입니다. Integer.class의 유형은 Class입니다..

유형 토큰

유형 토큰은 컴파일 타임에 유형 정보를 얻고 런타임에 유형 정보를 얻기 위해 메서드 간에 교환되는 클래스 리터럴입니다.

유형 토큰은 주로 제네릭 유형을 사용하는 메서드 또는 클래스에서 유형 정보를 전달하는 데 사용됩니다.

보다 일반적으로 유형 토큰은 일반적으로 유형 안정성필요한 곳에 사용되며 대표적으로 RestTemplate이나 ObjectMapper가 있다.

MyLittleTelevision mlt = objectMapper.readValue(jsonString, MyLittleTelevision.class);

제네릭 의약품의 일반적인 사용

제네릭은 종종 Set과 같은 컬렉션과 연결됩니다. 그리고 지도 뿐만 아니라 ThreadLocal과 같은 단일 항목 컨테이너 및 AtomicReference 사용된.

이러한 모든 용도에서 매개변수화된 객체는 컨테이너 자체입니다(예: ThreadLocal, ThreadLocal의 경우). 등. 컨테이너 자체매개변수화합니다.

그래서 컨테이너에 구성 가능한 유형의 수는 제한되어 있습니다.것이 가능하다.

예를 들어, 설정 요소의 유형을 나타내는 유형 매개변수와 Map 두 가지 유형 매개변수, 즉 키와 값이 필요합니다.

유형이 안전한 이기종 컨테이너 패턴

그러나 보다 유연한 수단이 필요할 때가 있습니다.

예를 들어, 데이터베이스의 행은 여러 개의 열을 가질 수 있으며 모두 유형이 안전할 수 있다면 좋을 것입니다.

이에 대한 간단한 해결책은 대신 컨테이너를 사용하는 것입니다. 키 매개변수화그런 다음 컨테이너에서 값을 더하거나 빼면서 매개변수화된 키를 지정합니다.

이러한 방식으로 제네릭 형식 시스템은 값의 형식이 키와 동일함을 보장합니다.

간단한 예로 다음과 같이 유형별로 즐겨찾기 인스턴스를 저장하고 검색하는 즐겨찾기 클래스를 작성했습니다.

import java.util.*;

public class Favorites {

    private Map<Class<?>, Object> favorites = new HashMap<>();

    public <T> void putFavorite(Class<T> type, T instance) {
        favorites.put(Objects.requireNonNull(type), instance);
    }
    public <T> T getFavorite(Class<T> type){
        return type.cast(favorites.get(type));
    }
}

각 타입의 클래스 객체를 파라미터화된 키로 사용하여 해당 타입에 맞는 인스턴스를 저장하도록 구현합니다.

사용 예는 다음과 같습니다.

Favorites f = new Favorites();

f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 22);
f.putFavorite(Class.class, Favorites.class);

final String favorite = f.getFavorite(String.class);  // java
final Integer favorite1 = f.getFavorite(Integer.class);  // 22
final Class<?> favorite2 = f.getFavorite(Class.class);  // Class<Favorites>

Type-Safe 이기종 컨테이너 패턴의 문제점

클래스 객체를 제네릭이 아닌 원시 유형으로 전달하면 유형 안전성이 손상됩니다.

예를 들어 다음과 같이 클라이언트 코드를 작성하는 경우:

f.putFavorite((Class)Integer.class, "Integer의 인스턴스가 아닙니다.");

int fInt = f.getFavorite(Integer.class);  // ClassCastException 발생

즐겨찾기가 유형 불변성을 위반하지 않도록 하려면 아래와 같이 putFavorite 메소드에서 인수로 지정된 인스턴스 유형이 type으로 지정된 유형과 동일한지 여부를 확인할 수 있습니다.

public <T> void putFavorite(Class<T> type, T instance) {
    favorites.put(Objects.requireNonNull(type), type.cast(instance));
}

이 메소드의 대표적인 예로는 checkedSet, checkedList, Collections의 checkedMap 등이 있다.


컬렉션(또는 카드)과 함께 클래스 아이템 1개(또는 2개)를 받게 됩니다.

이러한 모든 메서드는 Class 객체와 컬렉션이 동일한 컴파일 타임 유형임을 보장합니다.

구체화할 수 없는 유형에는 사용할 수 없습니다.

비필수형E, List와 같이 구체화되지 않은 유형입니다.목록 등이며 컴파일 시간보다 런타임에 유형 정보가 적습니다.

즉, 즐겨찾는 String 또는 String()을 저장할 수 있지만 즐겨찾는 목록저장할 수 없습니다.


위의 오류가 발생하는 이유는 목록에 대한 클래스 개체가 없기 때문입니다. 검색할 수 있습니다.

교활함을 위한 수업 얻을 수 없지만 List에 대한 클래스 객체는 얻을 수 있으므로 위 코드를 다음과 같이 수정할 수 있습니다.

List<String> favoriteStringList = new ArrayList<>();
favorites.putFavorite(List.class, favoriteStringList);

그러나 모든 List의 클래스 리터럴 이후 교활한 것처럼 그리고 교활한 수업 코드가 다음과 같이 작성되면 유형 안전성이 위반됩니다.

List<String> favoriteStringList = new ArrayList<>();
favoriteStringList.add("f1");

List<Integer> favoriteIntegerList = new ArrayList<>();
favoriteIntegerList.add(1);

favorites.putFavorite(List.class, favoriteStringList);
favorites.putFavorite(List.class, favoriteIntegerList);

final List<Integer> favorite1 = favorites.getFavorite(List.class);
System.out.println(favorite1.get(0));

final List<String> favorite2 = favorites.getFavorite(List.class);
System.out.println(favorite2.get(0));  // ClassCastException

이 두 번째 문제에 대해 완전히 만족스러운 해결 방법은 없지만 몇 가지 해결 방법이 있습니다.

빨리 슈퍼 타입 토큰(슈퍼 유형 토큰).

슈퍼타입 토큰

앞에서 보았듯이 List와 같이 구체화할 수 없는 유형에 대한 유형 정보를 유지할 방법이 없었습니다. 검색합니다.

하지만 가져올 수 있다면일반 클래스가 상위 유형으로 사용되는 경우입니다.

예를 들어 확인해 보겠습니다.

public class SuperTypeToken {

    static class Sup<T> {
        T value;
    }

    public static void main(String() args) throws NoSuchFieldException {
        Sup<String> s = new Sup<>();

        System.out.println(s.getClass().getDeclaredField("value").getType()); // class java.lang.Object
    }
}

위와 같이 리플렉션을 사용하더라도 Type Eraser에 의해 T가 제거되기 때문에 런타임에는 타입 정보를 알 수 없습니다.

다만, 아래와 같이 슈퍼 타입을 사용하면 List와 같은 타입 정보도 사용할 수 있다. 이전에 얻을 수 없었던 것을 얻습니다.

class Sup<T> {}

class Sub extends Sup<List<String>> {}

이제 List에 대한 정보를 알아봅시다. 클래스 검색 getGenericSuperclass() 방법.

첫 번째 적용 예:

class Sup<T> {}

class Sub extends Sup<List<String>> {}

public class SuperTypeToken {

    public static void main(String() args) {
        final Sub sub = new Sub();

        final Type t = sub.getClass().getGenericSuperclass(); // SuperTypeToken$Sup<java.util.List<java.lang.String>>
        ParameterizedType parameterizedType = (ParameterizedType) t;
        System.out.println(parameterizedType.getActualTypeArguments()(0));
    }
}


class.getGenericSuperclass()

위 절차에 대한 설명은 다음과 같습니다.

이 클래스 개체가 나타내는 엔터티(클래스, 인터페이스, 기본 형식 또는 void)의 직접 슈퍼 클래스를 나타내는 형식을 반환합니다. 슈퍼클래스가 매개변수화된 유형인 경우 반환된 Type 객체는 소스 코드에 사용된 실제 유형 인수를 정확하게 반영해야 합니다. 슈퍼클래스를 나타내는 매개변수화된 유형이 아직 생성되지 않은 경우 생성됩니다. 매개변수화된 유형 생성 프로세스의 의미 체계는 ParameterizedType 선언을 참조하십시오. 이 클래스 객체가 객체 클래스, 인터페이스, 기본 유형 또는 void를 나타내는 경우 null이 반환됩니다. 이 클래스 객체가 배열 클래스를 나타내는 경우 객체 클래스를 나타내는 클래스 객체가 반환됩니다. 반환값: 이 클래스 객체가 나타내는 클래스의 직계 슈퍼 클래스

요약하다:

이 클래스가 나타내는 엔터티(클래스, 인터페이스 등)의 직접 슈퍼 클래스 유형을 반환합니다.
바로 위 수퍼클래스는 매개변수화된 유형(ParameterizedType)입니다.경우 예, 실제 유형 매개변수를 반영하는 유형.반환해야

그러나 위의 예에서 본 것처럼 상위 유형 토큰을 사용하는 코드는 지나치게 복잡합니다.

좀 더 쉽게 하기 위해 익명 클래스를 사용하여 다음과 같은 코드를 작성할 수 있습니다.

class ParameterizedTypeReference<T> {

    Type type;

    protected ParameterizedTypeReference() {
        final Type stype = getClass().getGenericSuperclass();

        if (stype instanceof ParameterizedType pt) {
            this.type = pt.getActualTypeArguments()(0);
        }
        else {
            throw new IllegalArgumentException();
        }
    }

    public Type type() {
        return type;
    }
}

class Test {
    public static void main(String() args) {
        final Type type = new ParameterizedTypeReference<List<String>>() {}.type();
        System.out.println(type);  // java.util.List<java.lang.String>
    }
}

상위 유형의 토큰을 사용하기 위해 이제 클라이언트 코드에서 ParameterizedTypeReference를 상속하고 유형에 액세스하는 익명 클래스를 생성할 수 있습니다.

슈퍼 타입 토큰 사용

상위 유형 토큰을 통해 유형이 안전한 이기종 컨테이너를 구체화할 수 없는 유형에 사용할 수 없다는 제약 조건을 해결할 수 있습니다.

public class Favorites {

    private Map<ParameterizedTypeReference<?>, Object> favorites = new HashMap<>();

    public <T> void putFavorite(ParameterizedTypeReference<T> type, T instance) {
        favorites.put(Objects.requireNonNull(type), instance);
    }

    public <T> T getFavorite(ParameterizedTypeReference<T> tr) {
        if (tr.type instanceof  Class<?>)
            return ((Class<T>)tr.type).cast(favorites.get(tr));
        else
            return ((Class<T>)((ParameterizedType)tr.type).getRawType()).cast(favorites.get(tr));
    }
}

class ParameterizedTypeReference<T> {
    Type type;

    protected ParameterizedTypeReference() {
        final Type stype = getClass().getGenericSuperclass();

        if (stype instanceof ParameterizedType pt) {
            this.type = pt.getActualTypeArguments()(0);
        } else {
            throw new IllegalArgumentException();
        }
    }

    public Type type() {
        return type;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (!(o instanceof final ParameterizedTypeReference<?> that)) return false;
        return Objects.equals(type, that.type);
    }

    @Override
    public int hashCode() {
        return Objects.hash(type);
    }
}

이제 다음과 같이 사용할 수 있습니다.

final Favorites favorites = new Favorites();

favorites.putFavorite(new ParameterizedTypeReference<>(){}, "123");
final String favorite = favorites.getFavorite(new ParameterizedTypeReference<>(){});
System.out.println(favorite);

final List<String> strings = List.of("f1", "f2", "f3");
favorites.putFavorite(new ParameterizedTypeReference<List<String>>() {}, strings);

final List<Integer> integers = List.of(1, 2, 3);
favorites.putFavorite(new ParameterizedTypeReference<>(){}, integers);  // 생략도 가능

final List<Integer> favoriteInts = favorites.getFavorite(new ParameterizedTypeReference<List<Integer>>(){});
favoriteInts.forEach(System.out::println);

final List<String> favoriteStrings = favorites.getFavorite(new ParameterizedTypeReference<>(){});
favoriteStrings.forEach(System.out::println);