Java와 C#의 Generic

KIYOUNG KWON·2021년 8월 30일
0

개요

Generic은 DataType을 일반화 하는 기능이다. 간단하게 말하면 클래스나 함수에서 사용 할 DataType을 미리 지정하지 않고 정의 된 클래스나 함수를 사용할 때(객체화 할 때) 지정하는 것을 의미한다.

Java와 C#에서 Generic 활용

해당 포스트에선 Java와 C#의 Generic의 구현방식을 비교해 보며 Generic에 대해 알아보도록 하자.

java

import java.util.ArrayList;

public class GenericTest {
    public static void main(String[] args) throws Exception {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(3);
    }
}

C#

using System;
using System.Collections.Generic;

public class GenericTest {
    static void main(string[] args) {
        List<int> list = new List<int>();
        list.Add(3);
    }
}

위의 2개의 코드는 Java와 C#에서 list의 generic 기능을 사용하여 int요소를 하나씩 추가한 코드이다. 겉으로 보기엔 매우 비슷한 이 2개의 코드가 내부적으론 완전 다르게 동작한다.

Erasure Generic vs Reification Generic

2언어의 generic의 구현방식은 다음과 같이 2가지로 구분된다.

Erasure Generic(Java)Reification Generic(C#)
Type 구분 시점컴파일 시점런타임 시점
런타임 Type 구분불가능가능

2방식의 가장 큰 차이점은 런타임에 Type구분이 가능하냐 이다. 예제를 보면 감이 올 것이다.

public class GenericTest {
    public static void main(String[] args) throws Exception {
        List<Integer> list = new ArrayList<>();
        if(list instanceof List<?>)
        {
            System.out.println ("Type Erased...");
        }
    }
}

실행하면 Type Erased... 가 출력될 것이다. 여기서 <?>는 Java에서 <? extends Object>를 의미하는데 이는 Object를 상속받은 모든 Type을 받을 수 있다는 의미이다. 그렇다면 Integer를 요소로 갖는 List라는 것은 확인할 수 없을까?

public class GenericTest {
    public static void main(String[] args) throws Exception {
        List<Integer> list = new ArrayList<>();
        if(list instanceof List<Integer>) //error Cannot perform instanceof check against parameterized type List<Integer>. Use the form List<?> instead since further generic type information will be erased at runtime
        {
            System.out.println ("Type Not Erased...");
        }
    }
}

컴파일러가 아주 친절하게 에러로 답을 알려준다. runtime에는 type이 지워지니 check하려면 List<?>와 비교하라고 한다. 그렇다면 Java의 다음 코드는 runtime 시점에선 동일하다고 볼 수 있다. 이는 결국 Object를 상속받은 형태 즉 reference Type만 사용이 가능하다는 의미이다. 그래서 int, long, double과 같은 primitive type을 java의 generic에선 사용할 수 없던 부분도 이해가 된다.

List<Integer> list = new ArrayList<>();
List<Object> list = new ArrayList<>(); 

즉 java는 컴파일 타임에 Type을 체크하고 runtime엔 진행하지 않는다는 의미이다. 이러한 구조는 runtime에 type을 check할 수단이 없다보니 예측할 수 없는 문제를 발생시킬 수 있다고 하는데 이를 확인해볼만한 적당한 예제가 없어 어떤 case가 있는지 찾아 보다 보니 링크 처럼 캐싱에서 예기치 못한 문제가 생기는 경우가 있다고 한다. 어쨌든 Java의 generic은 컴파일 타임에 check되지 않는 부분에서 예기치 못한 현상이 발생할 소지가 있다는 의미가 된다.

그러면 모두 예상하던 대로 C#은 이와 다르게 동작할 것이다.

using System;
using System.Collections.Generic;

public class GenericTest {
    static void main(string[] args) {
        List<int> list = new List<int>();
        Console.WriteLine(list.GetType());
    }
}

이를 출력했을 때 다음과 같이 출력된다.

System.Collections.Generic.List`1[System.Int32]

다음과 같이 runtime에 해당 리스트의 요소의 datatype을 알수있다. 즉 C#의 경우 Reification(구체화) 되어 있는 것이다. 이는 C#의 VM인 CLR에서 지원하기에 가능한 기능으로

List<int> list = new List<int>();

다음 부분을 il code(c#의 바이트 코드)에선 다음 과 같이 표현된다.

IL_0001: newobj instance void class[System.Private.CoreLib]System.Collections.Generic.List`1<int32>::.ctor()

결론

Java의 generic은 VM을 변경하지 않고 구현하려고 해 Jvm에 generic을 위한 별도의 기능을 추가하지 않았다고 한다. 하위호환성을 생각한다면 꼭 틀리다고 하긴 애매하지만 그를 위해 런타임 동안 추가적인 logic(특정 부분에선 object를 generic에서 지정한 형태로 형변환이 필요함)이 발생한다거나 런타임에 type이 safe하지 않다거나 하는 부가적인 문제가 발생한 것은 개발자에게 추가적으로 신경써야 하는 부분을 제공한 샘이다. 개인적으론 C#의 generic의 방식이 가장 그 개념을 충실하게 구현하지 않았나 생각한다.

0개의 댓글