Icednut's Note

Java Generics

2016-12-15

Type Parameter와 Type Argument

1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
7
public class Generics {
static <T extends List & Serializable & Comparable & Closeable> void print(T t) {
}
public static void main(String[] args) {
}
}

Bounded Type Parameter

1
2
3
4
5
6
7
8
9
10
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을 만들고 싶을 땐?
더 나아가서 일반적으로 만들고 싶을 때는?

1
2
3
4
5
6
7
8
9
10
11
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"));
}
}

제네릭과 상속

1
2
3
4
5
6
7
8
9
10
11
12
13
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; // 잘 됨
}
}
1
2
3
4
5
6
7
8
9
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>();
// 둘 다 컴파일 오류 없이 잘 됨
}
}

타입 추론

컴파일러가 추론해주는 일

1
2
3
4
5
6
7
8
9
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 c = Collections.emptyList();

1
2
3
4
5
static <T extends Comparable> void method(List<T> t) {
}
static void method(List<? extends Comparable> t) { // ? is wildcards
}

Wild Card

List<? extends Object> list;

  • 오브젝트의 기능이 담긴 타입의 객체를 사용하겠다는 의미
  • 타입이 뭐가 있던지 간에 리스트의 기능에 집중하겠다는 의미
1
2
3
4
5
6
7
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 list = Arrays.asList(1,2,3);
printList1(list); // 이 때는 컴파일 오류가 발생함. List는 List의 서브타입이 아니기 때문에..
printList2(list); // 이거는 문제 없음

1
2
3
4
5
6
7
8
9
10
11
12
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); // 이거밖에 안됨
Tags: java generics