Java Generics

Wan Geun Lee / December 15, 2016

Type Parameter와 Type Argument

Java
0
1
2
3
4
5
6
7
8
9
10
11
public class Generics {
    static class Hello<T> { // 여기서 T를 Type Variable 이라고도 한다. T is type parameter
    }

    static void print(String value) {
        System.out.println(value);
    }

    public static void main(String[] args) {
        new Hello<String>(); // type argument
    }
}

 

Intersection Type

Java
0
1
2
3
4
5
6
public class Generics {
  static <T extends List & Serializable & Comparable & Closeable> void print(T t) {
  }

  public static void main(String[] args) {
  }
}

 

Bounded Type Parameter

Java
0
1
2
3
4
5
6
7
8
9
public class Generics {
    static long countGreaterThan(Integer[] arr, Integer elem) {
        return Arrays.stream(arr).filter(s -> s > elem).count();
    }

    public static void main(String[] args) {
        Integer[] arr = new Integer[] {1,2,3,4,5,6,7};
        System.out.println(countGreaterThan(arr, 4));
    }
}

스트링 용의 countGreaterThan을 만들고 싶을 땐? 더 나아가서 일반적으로 만들고 싶을 때는?

Java
0
1
2
3
4
5
6
7
8
9
10
public class Generics {
    static <T extends Comparable<T>> long countGreaterThan(T[] arr, T elem) {
        return Arrays.stream(arr).filter(s -> s.compareTo(elem) < 0).count();
        // s 에서는 erase type이라고 해서 정보를 다 날린다.
    }

    public static void main(String[] args) {
        String[] arr = new String[] {"a", "b", "c", "d", "e"};
        System.out.println(countGreaterThan(arr, "c"));
    }
}

 

제네릭과 상속

Java
0
1
2
3
4
5
6
7
8
9
10
11
12
public class Generics {
    public static void main(String[] args) {
        Integer i = 10;
        Number n = i; // 가능

        List<Integer> ints = new ArrayList<>();
        List<Number> numbers = ints; // 1. compile error, 2. runtime error, 3. ...
        // Type parameter 사이에는 상속 관계를 컴파일 타임에 체크할 수 없다. 제네릭에는 상속관계에 영향을 줄 수 없다.

        ArrayList<Integer> arrs = new ArrayLIst<>();
        Lister<Integer> ints2 = arrs; // 잘 됨
    }
}
Java
0
1
2
3
4
5
6
7
8
public class Generics {
    static class MyList<E, P> implements List<E> {...}

    public static void main(String[] args) {
        List<String> s1 = new MyList<String, Integer>();
        List<String> s2 = new MyList<String, String>();
        // 둘 다 컴파일 오류 없이 잘 됨
    }
}

 

타입 추론

컴파일러가 추론해주는 일

Java
0
1
2
3
4
5
6
7
8
public class Generics {
    static <T> void method(T t, List<T> list) {...}

    public static void main(String[] args) {
        method(1, Arrays.asList(1,2,3)); // 타입을 명시하지 않아도 메소드의 파라미터로 넘겨주는 로직이 컴파일 타임에 Integer로 추론되서 컴파일 된다.

        Generics.<Integer>method(1, Arrays.asList(1,2,3));
    }
}

빈 컬랙션이 타입 추론 List<String> c = Collections.<String>emptyList();

Java
0
1
2
3
4
static <T extends Comparable> void method(List<T> t) {
}

static void method(List<? extends Comparable> t) { // ? is wildcards
}

 

Wild Card

List<? extends Object> list;

  • 오브젝트의 기능이 담긴 타입의 객체를 사용하겠다는 의미
  • 타입이 뭐가 있던지 간에 리스트의 기능에 집중하겠다는 의미
Java
0
1
2
3
4
5
6
static void printList1(List<Object> list) {
    list.forEach(s -> System.out.println(s));
}

static void printList2(List<?> list) {
    list.forEach(s -> System.out.println(s));
}

 

위 두 메소드의 차이점은 뭘까? printList1(Arrays.asList(1,2,3)); printList2(Arrays.asList(1,2,3)); 둘 다 오류는 안남

그런데… List<Integer> list = Arrays.asList(1,2,3); printList1(list); // 이 때는 컴파일 오류가 발생함.

List<Integer>는 List<Object>의 서브타입이 아니기 때문에.. printList2(list); // 이거는 문제 없음

Java
0
1
2
3
4
5
6
7
8
9
10
11
class A {}
class B extends A {}

List<B> lb = new ArrayList<B>();
List<A> la = lb; // 이거는 컴파일 오류가 발생함, List<B>는 List<A>의 서브타입이 아니기 떄문
List<? extends A> la = lb; // 이거는 가능
List<? super A> lc = lb; // 이거도 가능
List<? super B> ld = lb; // 이거는 불가

la.add(new A()); // 이거도 컴파일 오류
la.add(new B()); // 이거도 컴파일 오류
la.add(null); // 이거밖에 안됨