본문 바로가기
Backend/JAVA

Generics

by Dev_Mook 2024. 10. 29.

개요

GenericsJava 5(JDK 1.5) 버전에서 추가된 기능이에요.

Generics가 추가되기 전과 후의 소스코드를 비교하면서 Generics에 대해 알아보도록 할게요.

JDK 5.0 Documentation를 참고하여 작성한 글입니다.

Generics가 없던 시절

Java의 Collection에서 Element를 가져올 때에는 저장된 데이터의 타입에 맞게 가져와야 해요.

그 동안 Java 개발자들은 Element를 가져오기 위해 직접 Type Casting을 해줬습니다.

/* 예시 1 */
1 static void printElement(Collection c) {
2     for(Iterator i = c.iterator(); i.hasNext();) {
3         // 개발자가 직접 Type Casting
4         String value = (Stirng) i.next();
5         System.out.println(value);
6     }
7 }


개발자들이 Collection에 어떤 데이터가 전달되는지 이미 알고있다면 예시 1과 같이 사용해도 됩니다.

하지만 신규 개발자가 Collection에 저장된 데이터의 타입을 모른다면?

아마 잘못된 Type Casting을 하겠죠!

신규 개발자가 예시 1의 4번 줄을 아래와 같이 변경했다고 가정해봅시다.

    int value = (int) i.next();


개발하는 과정에서 Java는 value의 타입(int)과 Element(i.next())의 타입(int)만 확인하기 때문에 에러가 발생하지 않아요.

하지만 Collection 안에 저장된 Element는 String 타입의 데이터를 저장하고 있죠?

printElement Method를 실행하면 분명 오류가 발생할겁니다.(Run-time Error)

아마도 NumberformatException이 발생하겠죠.

그럼 Collection 안에 저장된 Element의 데이터 타입을 개발자에게 미리 알려줄 수 있다면 에러를 미리 발견할 수 있지 않을까요?
(Compile-time Error)

이러한 역할을 하는 기능이 바로 Generics에요!

Generics가 추가된 이후

Generics는 Collection의 데이터 타입을 컴파일러에게 전달하고

컴파일러는 Collection의 Generics가 알려준 데이터 타입으로 Type Casting하여 Element를 가져와요.

그럼 더이상 개발자가 Type Casting을 고민할 필요가 없겠죠?

즉, 데이터 타입에 대해 명확하고 안전한 소스코드를 작성할 수 있게 됩니다.

Generics가 적용된 소스코드는 아래 예시 2와 같이 작성할 수 있어요.

/* 예시 2 */
1 // <String>을 통해 Collection의 c가 문자열 타입인 것을 알 수 있다.
2 static void printElement(Collection<String> c) {
3     for(Iterator i = c.iterator(); i.hasNext();) {
4         // Compiler가 Type Casting하여 값을 가져온다.
5         String value = i.next();
6         System.out.println(value);
7     }
8 }



예시 2에서 작성한 것과 같이 < > 안에 테이터 타입을 작성해주면 Compiler가 알아서 Type Casting을 해줘요.

  • 참고로 A<Type> B 코드는 A of Type B라고 읽는다고 합니다.

  • 예시 2의 2번줄에 작성한 Collection<String> c문자열 c의 Collection이라고 읽을 수 있네요.


Generics는 매개변수에만 사용할 수 있는 것은 아니에요.

아래 예시 3과 같이 Method의 반환 타입으로 사용하거나

예시 4와 같이 반환할 데이터 타입을 매개변수로 전달받아 사용할 수도 있어요.

/* 예시 3 */
1  static List<String> getUserNames(...) {
2      // 문자열 userNames의 List 생성
3      // userNames는 문자열로 된 사용자 이름의 목록을 저장하는 List Collection입니다.
4      List<String> userNames = new ArrayList<String>();
5  
6      // 사용자 이름 목록을 조회하는 기능 구현...
7  
8      // List<String> 타입의 데이터를 반환합니다.
9      return userNames;
10 }

/* 예시 4 */
1 // Documentation에는 아래 주석과 같이 작성 예시 소스코드를 보여주고 있습니다.
2 // <T extends Annotation> T getAnnotation(Class<T> annotationType);
3 static <T> List<T> getCustomTypeList(Class<T> c) {
4     List<T> list = new ArrayList<T>();
5 
6     // 기능 구현...
7 
8     return list;
9 }



Generics의 단점

지금까지 Generics가 무엇인지, Generics를 사용하면 어떤 점에서 좋은지 알아봤어요.

그럼 단점도 있겠죠?

Generics잘못 동작하는 레거시 코드와 같이 동작시킬 때 Type Casting이 실패할 수도 있어요.

예를 들어 정수를 삽입하는 문자열 s가 있다고 가정해볼게요.

정수의 값은 Generics에 의해 문자열로 Type Casting을 시도하는데 데이터 타입이 맞지 않기 때문에 실패해요.

이 때 java.util.Collections에서 제공하는 Wrapper Class를 활용하면 Run-time 시 안전성을 보장할 수 있답니다.

1  // 레거시 코드로 인해 Type Casting에 실패할 수 있어요
2 Set<String> s = new HashSet<String>();
3 
4 // 레거시 코드가 정수를 삽입하려고 시도하는 시점에서 CalssCastException을 발생시킵니다.
5 // 이 때 결과 스택 추적을 통해 문제를 진단하고 복구할 수 있습니다.
6 // 즉, Collections가 한 번 더 체크하기 때문에 에러에 대한 안전성을 확보할 수 있습니다.
7 Set<String> s = Collections.checkedSet(new HashSet<String>(), String.class);

출처 및 참고

'Backend > JAVA' 카테고리의 다른 글

Autoboxing과 Unboxing  (1) 2024.10.31
향상된 for문  (0) 2024.10.30