본문 바로가기
토비의봄

3.1. Generics

by 이석준석이 2021. 2. 24.

1. 타입파라미터와 타입 아규먼트

public class Generics {
    static class Hello<T> { // T-> 타입 파라미터
        T t;
        T method(T val) {return null;}
    }

    public static void main(String[] args) {
        new Hello<String>(); // 타입 아규먼트
    }
}

2. 제너릭 쓰는이유

 

1. 컴파일시점에서 컴파일러가 정확하게 타입체킹을 할 수 있다.

public static void main(String[] args) {
    List list = Arrays.asList(1, 2, 3);
    list.add("runTimeError");

    List<Integer> listWithGen = Arrays.asList(1, 2, 3);
    listWithGen.add("compileError");
}

 

2. 강제 캐스팅으로 인한 오류 방지

  • 제너릭을 사용한다면 컴파일러가 적절하게 알아서 타입캐스팅 해준다.
public static void main(String[] args) {
    List list = Arrays.asList("string");
    String s = (String)list.get(0);
}

3. Raw Type

 

로우타입은 제너릭이 사용안된 List, Set 등이라고 보면된다.

public static void main(String[] args) {
    List<Integer> ints = Arrays.asList(1, 2, 3);
    // 로우타입 (List rawInts)
    List rawInts = ints;
    List<Integer> ints2 = rawInts;
    List<String> strs = rawInts;
    
    // 여기서 런타임에러가 발생!
    String s = strs.get(0);
}

4. static method generics

 

불가능

  • 클래스가 생성될 때 제너릭에 대한 정보를 받아오기 때문에 static 메소드에 T 를 사용할 수는 없다. (뭐가올지 모르니까)
public class Generics<T> {
    static void print(T t) {
        System.out.println("t.toString() = " + t.toString());
    }
}

 

가능

  • static method 레벨에 따로 제너릭을 사용하여 사용하도록 한다.
public class Generics {
    static <T> void print(T t) {
        System.out.println("t.toString() = " + t.toString());
    }
}

번외 : 생성자에도 제너릭 사용 가능

public class Generics {
    public <S> Generics(S s) {
    
    }
}

5. Bounded Type Parameter

 

타입에 대한 제한을 두겠다.

 

예시)

public class Generics<T extends List>{

}

 

아래와같이 여러개의 조건을 묶을 수 있다. (intersect)

  • 클래스는 한개만 가능
  • &조건만 가능 (모두 포함돼야함)
public class Generics {
    static <T extends List & Serializable & Comparable & Closeable> void print(T t) {

    }
}

예제)

  • String 배열에서 더 큰 것의 개수를 리턴하는 함수를 만들어보자. (제너릭을 이용해서)
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();
    }

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

<T extends Comparable<T>>

  • 크기 비교를 위해서 compareTo 를 쓴다고 가정했을 때, 넘어오는 type이 Comparable을 구현헀는지 모르므로, 타입에 제한을 둠
  • Comparable 의 정보는 type erasure 가 없애지 않는다. (컴파일 이후에도 정보가 남아있다.)

// Comparable 을 서브타입으로 가지는 리스트이다.
// 타입이 정해지면 뭔지알고, 이후에 그 타입으로 사용된다.
static <T extends Comparable> void method(List<T> list) {

}

// 타입을 알 필요가 없다.
// 가져와서 Comparable 의 기능만 사용할 것이다 or List의 기능만 사용할 것이다.
static void method(List<? extends Comparable> list) {

}

public class Generics {
    static void printList(List<Object> list) {
        list.forEach(s -> System.out.println(s));
    }

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

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3);
        printList(list); // 컴파일에러
        printList2(list);
    }
}

 

printList(list); 를 하면 컴파일에러가 발생한다.

  • 이유
    • List<Integer> 은 List<Object> 의 서브타입이 아니기 때문
    • printList(List<Object> list) 는 명확하게 List<Object> 타입의 서브타입을 원하는 것이기 때문
public static void main(String[] args) {
    List<Integer> intList = new ArrayList<>();
    List<Number> numberList = intList; // 컴파일에러 (List<Integer> 은 List<Number> 의 서브타입이 아님, Integer 가 Number의 서브타입일 뿐
    
    ArrayList<Integer> arrayList = new ArrayList<>();
    List<Integer> iintList = arrayList; // 가능 (ArrayList<Integer> 은 List<Integer> 의 서브타입이기 때문)
}

'토비의봄' 카테고리의 다른 글

4.2. Reactive Streams  (0) 2021.02.28
4.1. Reactive Streams  (0) 2021.02.28
3.2. Generics  (0) 2021.02.25
2. 슈퍼 타입 토큰  (0) 2021.02.20
1. 재사용성과 다이나믹 디스패치, 더블 디스패치  (0) 2021.02.20