제네릭에 대한 간단한 정리
Java

제네릭에 대한 간단한 정리

모든 소스 코드는 https://github.com/lkimilhol/tistoryblog에서 확인 가능합니다!

 

안녕하세요.

이번에는 제네릭을 살펴볼까 합니다. 많은 분들이 generic에 이해하고 계실텐데요. 간단한 예제 정도로 정리만 해보려고 해요. (사실 코딩할때 제네릭을 잘 써본적이 없었거든요)

 

1. 제네릭을 사용하는 이유?

제네릭을 사용하는 이유는 무엇일까요?

 

저는 제네릭을 사용하는 이유에 대해서 

  1. 타입 체크가 컴파일 타임에 이루어 진다.
  2. 리턴 타입을 제한 할 수 있다.

로 이해하였습니다. 예시를 들어보겠습니다.

 

1. 타입 체크가 컴파일 타임에 이루어 진다.

UseClass.java

public class UseClass {
    public void exceptionExample() {
        List test = new ArrayList<>();
        test.add("a");
        test.add("b");
        test.add("c");

        int a = (Integer) test.get(0) / 1;
    }
}

위의 예제는 에러는 컴파일 타임에 에러가 나지 않습니다.

test에 "a", "b", "c" 처럼 String 타입을 넣어주었는데요. String 타입을 숫자로 나누려고 하니 에러가 발생하는 것입니다.

 

이 코드는 컴파일 타임에선 에러로 처리 되지 않고 런타임에만 에러 나기 때문에 개발자 입장에선 실수를 하기 쉽습니다.

 

이런 부분을 해결 할 수 있는 것이 제네릭입니다. List에 제네릭, 즉 type 명시해보죠.

보시는 바와 같이 빨간 줄이 떠 있습니다. 즉 컴파일 타임에 에러를 발견한 것이죠. 이처럼 제네릭을 사용하여 개발자가 실수할 여지를 줄여 줄 수 있는 거이죠. 이펙티브 자바에서는 이처럼 제네릭을 사용하지 않는 raw type 사용을 지양 하라고 권고하고 있습니다. 

 

2. 리턴 타입을 제한할 수 있다.

리턴타입을 제한한 다는 것을 무엇일까요?

 

우리가 클래스 하나를 정의했다고 합시다. 제네릭을 사용한 클래스입니다.

 

GenericClass.java

public class GenericClass<T> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

이처럼 클래스에 제네릭을 넣었습니다. 그럼 이 클래스를 생성할 땐 어떻게 해야 할까요?

 

GenericClass.java

public String genericClassCreate() {
	GenericClass<String> genericClass = new GenericClass<>();
	genericClass.setData("data");
	return genericClass.getData();
}

보시는 바와 같이 String 타입을 명시했습니다. 이러면 GenericClass의 data는 String type로 제한되는 것입니다.

마치 List에 원하는 자료형을 넣는 List<String>과 같다고 생각하면 되겠네요.

 

이처럼 제네릭을 사용하면서 개발자의 실수를 줄이는, 컴파일 타임에 에러가 체크 되는 장점을 가지고 있다고 생각합니다.

 

 

2. 제네릭 인터페이스는 어떻게 사용할까?

간단하게 인터페이스를 구현해 보겠습니다. 

 

GenericInterface.java

public interface GenericInterface<T extends Number> {
    int getSquared(T t);
}

인터페이스를 구현하는 클래스가 하나 필요하겠네요. 작성해보겠습니다.

 

GenericImplements.java

public class GenericImplements<T extends Number> implements GenericInterface<T> {
    @Override
    public int getSquared(T t) {
        return t.intValue() * t.intValue();
    }
}

제곱을 구하는 함수를 작성했습니다. 테스트케이스를 작성해서 확인해보도록 하죠.

 

GenericImplementsTest.java

class GenericImplementsTest {

    @DisplayName("제네릭 인터페이스 생성 후 값 비교")
    @Test
    void create() {
        // given
        GenericImplements genericImplements = new GenericImplements();
        // when
        Number data = genericImplements.getSquared(5);
        // then
        assertThat(data).isEqualTo(25);
    }
}

테스트 케이스에 성공했네요. 그러면 이 인터페이스는 어떤 장점이 있을까요? 앞선 예제와 비슷하게 숫자를 넣지 않으면 어떻게 될까요?

컴파일 에러가 발생 된 것을 볼 수 있습니다.

 

3.  제네릭 메서드는 어떻게 사용할까?

마찬가지로 타입을 명시하는 것이 특징입니다.

간단한 예제를 보도록 할게요.

 

GenericClass.java

public static <E> Set<E> genericMethod(E e) {
	Set<E> set = new HashSet<>();
	set.add(e);
	return set;
}

테스트케이스를 작성하여 어떻게 사용되는지 보겠습니다.

 

GenericClassTest.java

@DisplayName("제네릭 메서드 테스트")
@Test
void method() {
    // given
    // when
    Set<Integer> set = GenericClass.genericMethod(100);
    set.add(100);
    // then
    assertThat(set.size()).isEqualTo(1);
}

genericMethod를 호출할 때 100이라는 값을 넣어주었고 리턴으로 Set<Integer> 타입으로 결과를 받아왔네요.

만약 genericMethod에 String 값을 넣었다면 Set<String>이 리턴 되어야 할 거에요.

 

Set set 처럼 사용된다면 raw type 사용이니 이 사용은 지양하는 것이 좋겠습니다.

 

4. 마치며

간단한 제네릭 사용을 해보았는데요. 애초에 코딩할 때 제네릭을 잘 사용하지 않았던 습관들이 있어, 그냥 두들겨보자는 식으로 정리해두었네요. 간단한 프로젝트에서도 사용해 보면서 어떤식으로 사용하면 더 좋을지 고민해봐야겠습니다.

 

긴 글 읽어주셔 감사합니다!

'Java' 카테고리의 다른 글

Java의 Call by value, Call by reference  (0) 2021.08.09
간단한 Spring AOP 개념과 적용  (0) 2021.07.30
간단하게 상속과 조합을 이해해보기  (0) 2021.07.26
JPA N+1 문제  (0) 2021.07.22