? super

Jang Seok Woo·2021년 7월 8일
0

실무

목록 보기
31/136

코드분석 도중

Function2<? super OAuthToken, ? super Throwable, Unit> loginCallback = (OAuthToken oAuthToken, Throwable throwable) -> {
        //TODO
        return null;
    };

위와 같은 코드를 보게 되었다.

우선 제네릭(Generic)을 이해해보자

JDK1.5 에 처음 도입되었다.

Generics add stability to your code by making more of your bugs detectable at compile time. – Oracle Javadoc
제네릭(Generic)은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미한다. – 생활코딩
지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크를 해주는 기능이다. – 자바의 정석

데이터 타입을 먼저 지정하기보다 int가 될수도, String이 될 수도 있다면 지정하지 않고 T라고 하고 넘어가면 컴파일러가 알아서 형변환을 해준다.

이해가 잘 안가니까 예시를 들어보자.

public class SimpleArrayList {
    private int size;
    private Object[] elementData = new Object[5];

    public void add(Object value) {
        elementData[size++] = value;
    }

    public Object get(int idx) {
        return elementData[idx];
    }
}

위 ArrayList가 있는데.. 얘는 배열의 타입이 Object라서 모든 타입을 add, get함수로 받을 수 있다.

public class SimpleArrayListTest {
    public static void main(String[] args) {
        SimpleArrayList list = new SimpleArrayList();

        list.add(50);
        list.add(100);

        Integer value1 = (Integer) list.get(0);
        Integer value2 = (Integer) list.get(1);

        System.out.println(value1 + value2);
    }
}

여기서 보면 add로 50이라는 int를 넣어도 알아서 들어간다.

public class SimpleArrayListTest {
    public static void main(String[] args) {
        SimpleArrayList list = new SimpleArrayList();

        list.add("50");  // 달라진부분
        list.add("100"); // 달라진부분

        Integer value1 = (Integer) list.get(0);
        Integer value2 = (Integer) list.get(1);

        System.out.println(value1 + value2);
    }
}

여기서는 "50"을 넣음으로써 String형태로 넣었다.
그래도 컴파일할 때 오류가 생기지 않는다. Object로 받으니까 int가 되었든, String이 되었든 오류가 발생하지 않는다.

하지만 실행하면 런타임에 오류가 발생한다.

Exception in thread "main" java.lang.ClassCastException:
    java.lang.String cannot be cast to java.lang.Integer
    at com.example.java.generics.basic.SimpleArrayListTest.main(SimpleArrayListTest.java:11)

잘못된 타입캐스팅이 이루어졌다는 오류메시지이다. String 을 넣어놓고서 Integer 로 형변환했기 때문이다.

문제를 해결하기 위해 아래와 같이 하면 된다.

GenericArrayList.java

public class GenericArrayList<T> {

    private Object[] elementData = new Object[5];
    private int size;

    public void add(T value) {
        elementData[size++] = value;
    }

    public T get(int idx) {
        return (T) elementData[idx];
    }
}

이렇게 하면 컴파일러가 알아서 T라는 타입에 형변환을 해서 집어넣는다.
List<스트링>을 RAW한 형태로 사용한다고 한다.

class Test {
    public static void main(String[] args) {
        GenericArrayList<Integer> intList = new GenericArrayList<>();
        intList.add(1);
        intList.add(2);

        int intValue1 = intList.get(0); // 형변환이 필요없다
        int intValue2 = intList.get(1); // 형변환이 필요없다

        // String strValue = intList.get(0); // 컴파일에러
    }
}

그래서 위와 같은 상황에서는 형변환이 필요없고,
아래와 같이 Integer로 하면

class Test {
    Test() {
    }

    public static void main(String[] var0) {
        GenericArrayList var1 = new GenericArrayList(); // 제네릭이 사라졌다
        var1.add(1);
        var1.add(2);
        int var2 = (Integer)var1.get(0); // 형변환이 추가되었다
        int var3 = (Integer)var1.get(1); // 형변환이 추가되었다
    }
}

이렇게 형변환을 한다.

한정적 타입 매개변수 (Bounded Type Parameter)

제네릭으로 사용될 타입 파라미터의 범위를 제한할 수 있는 방법이 있다.

위에서 만든 GenericArrayList 가 Number 의 서브클래스만 타입으로 가지도록 하고 싶은 경우 아래와 같이 제네릭의 타입을 제한할 수 있다. (인터페이스나 클래스나 추상클래스나 모두 extends 를 사용한다)

public class GenericArrayList<T extends Number>

위와 같이 정의했다면 GenericArrayList 에는 String 을 담을 수 없다.

Number 의 상위클래스만 타입으로 가지도록 하고 싶은 경우 (적절한 예시는 아니지만) 아래와 같이 제네릭의 타입을 제한할 수 있다.

public class GenericArrayList<T super Number>

바운디드 타입 파라미터가 사용되는 가장 흔한 예시는 Comparable 을 적용하는 경우다.

T extends Comparable<T>

와 같이 정의하면 Comparable 인터페이스의 서브클래스들만 타입으로 사용하겠다는 것이다. Comparable 인터페이스를 구현하기 위해서는 compareTo() 메소드를 반드시 정의해야하기 때문에 Comparable 인터페이스를 구현한 클래스들은 비교가 가능한 타입이 된다.

비교하는 로직이 들어간 클래스에는 비교가 가능한 타입들을 다루는 것이 맞을 것이다. 이를 강제하도록 할 수 있는게 바운디드 타입 파라미터이다.

자 여기서 보면 상위클래스만 타입으로 가지도록 한정하는 방법인 것이다. 무엇이?

맨 위에 내가 궁금했던 부분이

Function2<? super OAuthToken, ? super Throwable, Unit> loginCallback = (OAuthToken oAuthToken, Throwable throwable) -> {
        //TODO
        return null;
    };

그런데 여긴 조금 다르게 생겼다.

앞에 물음표가 있다. super는 알겠고 뒤에 타입도 알겠고 근데 앞에 물음표는 무엇인가?

https://butgrin.tistory.com/64?category=960811

상속관계에서 자식역할인 OAuthToken 타입의 부모되는 타입은 받겠다. 물론 OAuthToken타입도 받고. 이 얘기다.

참조 : https://yaboong.github.io/java/2019/01/19/java-generics-1/

profile
https://github.com/jsw4215

0개의 댓글