Java Generic

Generic

제네릭 프로그래밍(Generic programming)은 데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있는 기술에 중점을 두어 재사용성을 높일 수 있는 프로그래밍 방식이다.

제네릭 프로그래밍 - 위키백과, 우리 모두의 백과사전
final List list = new ArrayList();
list.add("box1");

final String str1 = (String) list.get(0);
System.out.println(str1);
final List list = new ArrayList();
list.add("box1");
list.add(new Integer(1));

final String str1 = (String) list.get(0);
final String str2 = (String) list.get(1);

System.out.println(str1);
System.out.println(str2);

Exception in thread "main" java.lang.ClassCastException:

java.lang.Integer cannot be cast to java.lang.String

at Main.main(Main.java:14)

final List<String> list = new ArrayList<>();
list.add("box1");
list.add(new Integer(1));

final String box1 = list.get(0);
final String box2 = list.get(1);

System.out.println(box1);
System.out.println(box2);

Error:(11, 13) java: no suitable method found for add(java.lang.Integer)
    method java.util.Collection.add(java.lang.String) is not applicable
      (argument mismatch; java.lang.Integer cannot be converted to java.lang.String)
    method java.util.List.add(java.lang.String) is not applicable
      (argument mismatch; java.lang.Integer cannot be converted to java.lang.String)

public class Box<T> {
    private T element;

    public void add(T element) { 
        this.element = element; 
    }

    public T get() { 
        return this.element; 
    }
}

final Box<String> home = new Box<>();
home.add("Cat!");
System.out.println(home.get());

Generic Class

public class Box<C, A, T> {
    private C element;

    public void add(C element) {
        this.element = element;
    }

    public C get() {
        return this.element;
    }
}

final Box<String, Integer, Double> home = new Box<>();
home.add("Cat!");
System.out.println(home.get());

Generic Class

public <T> void print(T element) {
    System.out.println(element);
}

print("Cat!");
print(new Integer(1));
print(new Double(2.0));

Generic Method

public void print1(List<? extends Integer> element) {
    System.out.println(element);
}

public void print2(List<? super Double> element) {
    System.out.println(element);
}

print1(Arrays.asList(new Integer(1)));
print1(Arrays.asList(new Double(1.0))); // error!

print2(Arrays.asList(new Double(1)));
print2(Arrays.asList(new Integer(1.0))); // error!

Bounded Type Parameter

final List<String> list = new ArrayList<>();
list.add("Cat1!");

final List<?> wList = list;
final String cat = (String) wList.get(0);
wList.add("Cat2!"); // compile error!
wList.remove(0);

Wildcard

Compatibility

final List list1 = new ArrayList();
list1.add("Cat1!");
final String cat1 = (String) list1.get(0);
final List<String> list2 = new ArrayList<>();
list2.add("Cat2!");
final String cat2 = list2.get(0);

~ J2SE 1.4

J2SE 1.5 ~

final List<String> list = new ArrayList<>();
list.add("Cat!");
final String cat = list.get(0);
final List list = new ArrayList();
list.add("Cat!");
final String cat = (String) list.get(0);

Compile!

  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 10 L0
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 11 L1
    ALOAD 1
    LDC "Cat!"
    INVOKEINTERFACE java/util/List.add (Ljava/lang/Object;)Z
    POP
   L2
    LINENUMBER 12 L2
    ALOAD 1
    ICONST_0
    INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object;
    CHECKCAST java/lang/String
    ASTORE 2

Java bytecode

Covariant

& Contravariant

공변 (共変, covariant)
반(공)변 (反変, contravariant)불변 (不変 , invariant)

Covariant

public class Animal {
    public Animal getAnimal() {
        System.out.println("Animal!");
        return new Animal();
    }
}

public class Cat extends Animal {
    @Override
    public Cat getAnimal() {
        System.out.println("Cat!");
        return new Cat();
    }
}

final Cat cat = new Cat();
cat.getAnimal(); // Cat!

final Animal animal = cat;
animal.getAnimal(); // Cat!

Covariant

final Number num1 = new Integer(1);
final Number num2 = new Double(1.0);

final Number[] numbers = new Number[2];
numbers[0] = new Integer(1);
numbers[1] = new Double(1.0);

Contravariant

public class Animal {
    public void put(Animal animal) {
        System.out.println("Animal!");
    }
}

public class Cat extends Animal {
    public void put(Object animal) {
        System.out.println("Cat!");
    }
}

final Cat cat = new Cat();
cat.put(new Cat()); // Animal!

Contravariant

final Cat cat1 = new Animal(); // compile error!
final Cat cat2 = (Cat) new Animal(); // runtime error!

final Cat[] cats = new Cat[2];
cats[0] = new Animal(); // compile error!
cats[1] = (Cat) new Animal(); // runtime error!
public void printNumber(final List<Number> numbers) {
    numbers.forEach(System.out::println);
}


final List<Number> num1 = new ArrayList<Integer>(); // compile error!

final List<Number> num2 = new ArrayList<>();
num2.add(1);
printNumber(num2);

final List<Integer> num3 = new ArrayList<>();
num3.add(1);
printNumber(num3);  // compile error!

Invariant

final Number[] numbers = new Number[3];
numbers[0] = new Integer(1);
numbers[1] = new Long(1);
numbers[2] = new Double(1.0);

System.out.println(numbers[0].longValue());
System.out.println(numbers[1].longValue());
System.out.println(numbers[2].longValue());

Array = Covariant

final Number[] numbers2 = new Integer[3];
numbers2[0] = new Double(1.0);
System.out.println(numbers2[0].longValue());

Array = Covariant

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Double

public void print1(List<? extends Integer> element) {
    System.out.println(element);
}

final List<? extends Number> numbers = new ArrayList<Integer>();

Bounded Type Parameter

covariant

public void print2(List<? super Double> element) {
    System.out.println(element);
}

final List<? super Integer> integers = new ArrayList<Number>();

contravariant

final List<? extends Number> numbers = new ArrayList<Integer>();

Number number1 = numbers.get(0);
Integer number2 = numbers.get(1); // compile error!
numbers.add(new Integer(1)); // compile error!
numbers.add(new Long(1)); // compile error!

Bounded Type Parameter

covariant

final List<? super Integer> integers = new ArrayList<Number>();

Number integer1 = integers.get(0); // compile error!
Integer integer2 = integers.get(1); // compile error!
Object integer3 = integers.get(2);

integers.add(new Integer(1));
integers.add(new Long(1)); // compile error!

Bounded Type Parameter

contravariant

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    int srcSize = src.size();
    if (srcSize > dest.size())
        throw new IndexOutOfBoundsException("Source does not fit in dest");

    if (srcSize < COPY_THRESHOLD ||
        (src instanceof RandomAccess && dest instanceof RandomAccess)) {
        for (int i=0; i<srcSize; i++)
            dest.set(i, src.get(i));
    } else {
        ListIterator<? super T> di=dest.listIterator();
        ListIterator<? extends T> si=src.listIterator();
        for (int i=0; i<srcSize; i++) {
            di.next();
            di.set(si.next());
        }
    }
}

PECS

Producer Extends, Consumer Super

Type Token

public <T> T newInstances(Class<T> clazz) throws Exception {
    final T instances = clazz.newInstance();
    // 적절한 초기화 코드
    return instances;
}


final String str = newInstances(String.class);
final List<String> list = newInstances(List<String>.class); // error!

Class<T>

public <T> List<T> newListInstances(Class<T> clazz) throws Exception {
    final ArrayList<T> list = new ArrayList<>();
    // 적절한 초기화 코드
    return list;
}

final List<String> list = newListInstances(String.class);

Map, Set ???

Class<T>

public class Box<T> {
    // 적절한 구현
}

public class LongBox extends Box<List<Long>> {
    // 적절한 구현
}

final Type type = LongBox.class.getGenericSuperclass();
final ParameterizedType pType = ParameterizedType.class.cast(type);
final Type[] actualTypeArguments = pType.getActualTypeArguments();

System.out.println(actualTypeArguments[0]);

java.util.List<java.lang.Long>

ParameterizedType

public static void main(String[] args) throws Exception {
    class LongBox extends Box<List<Long>> { }
    
    final Type type = LongBox.class.getGenericSuperclass();
    final ParameterizedType pType = ParameterizedType.class.cast(type);
    final Type[] actualTypeArguments = pType.getActualTypeArguments();

    System.out.println(actualTypeArguments[0]);
}

ParameterizedType

public static void main(String[] args) throws Exception {
    Box typeBox = new Box<List<Long>>() {};

    final Type type = typeBox.getClass().getGenericSuperclass();
    final ParameterizedType pType = ParameterizedType.class.cast(type);
    final Type[] actualTypeArguments = pType.getActualTypeArguments();

    System.out.println(actualTypeArguments[0]);
}

ParameterizedType

final ParameterizedTypeReference<List<String>> typeReference =
        new ParameterizedTypeReference<List<String>>() {};
final List<String> response  = 
        restTemplate.exchange(
            url, 
            HttpMethod.GET, 
            request, 
            typeReference).getBody();

Spring : RestTemplate

final TypeToken<List<String>> typeToken = 
    new TypeToken<List<String>>() {};

final List<String> list =
        new Gson().fromJson(jsonText, typeToken.getType());

Google : Gson

final TypeReference type = new TypeReference<List<String>>() {};
final List<String> data = mapper.readValue(jsonData, type);

Jackson : ObjectMapper

  • 새로 작성하는 코드에서는 원천(raw) 타입을 사용하지 말자
  • 컴파일 경고 메시지가 없게 하자
  • 배열보다는 List를 사용하자
  • 제네릭 타입을 애용하자
  • 제네릭 메소드를 애용하자
  • 바운드 와일드 카드를 사용해서 API의 유연성을 높이자
  • 타입 안전이 보장되는 혼성(heterogeneous) 컨테이너의 사용을 고려하자
Chapter 5. 제네릭(Generics) - Effective java 2/e

END

Java Generic

By serivires

Java Generic

  • 565