본문 바로가기

개발/웹 개발

[JAVA/자바] 제네릭(Generic)

제네릭(Geceric)의 정의

 

 

제네릭(generic)은 데이터 타입을 일반화하는 것. 다시 말해, 특수한 형태의 변수로 존재하다가 변수가 할당될 때 그 타입이 정해지는 것입니다.

 

조금 더 쉽게 말하자면, 클래스나 메서드 내부에서 사용할 변수의 타입을 외부에서 지정하는 것입니다.

 

개념만 들어보면 처음 듣는 개념인 것 같지만 사실 다들 한번씩 써본 형태입니다.

 

List<String> list = new ArrayList<>();

 

위처럼 List 타입의 변수를 선언할 때 각 요소마다 특정 값을 저장하기 위해서 홑화살괄호(<,>) 안에 String이라고 명시한 부분이 바로 제네릭입니다.

 

실제로 List 클래스를 들어가 보면 다음과 같은 형태로 되어있는 걸 볼 수 있습니다.

 

public interface List<E> extends Collection<E>

 

 

List<E> 의 형태로 여기서 나온 E는 List 배열의 각 요소를 뜻합니다.

 

 

다른 예시를 하나 더 들어보자면 Map을 선언하는 형태를 보겠습니다.

 

Map<String, Object> map = new HashMap<>();

 

마찬가지로 Map<String, Object>의 형태로 선언되어 있는데 key값으로는 String을 value 값으로는 Object를 받아오는 것으로 선언하였습니다.

 

실제로 Map 클래스를 살펴보면 다음과 같습니다.

 

public interface Map<K, V>

 

설명한 내용과 똑같이 K, V 값을 받고 있습니다.

 

 

그래도 명확하게 풀리지 않는 의문이 있는데, 그래서 E, K, V 가 뭔데? (제네릭이 뭔데?)

 

 

 

제네릭 식별자

 

제네릭 식별자는 정해진 바 없으며 본인이 사용하고 싶은 대로 사용해도 상관없습니다.

 

다만, 다음과 같은 사회적 약속은 존재합니다.

 

기호 설명
<E> 배열의 각 요소 (Element)
<T> 타입 (Type)
<K> 키 (Key)
<V> 값 (Value)
<N> 숫자 (Number)

 

 

 

 

제네릭 사용하기

 

제네릭을 실제로 사용하기 위한 테스트 코드를 작성해봅시다.

 

class Test<T>{
    private T val;

    public T getVal(){
        return val;
    }
    public T setVal(T val){
        this.val = val
    }
}

 

 

Test라는 이름의 클래스가 있고 내부에 getter와 setter가 정의되어 있습니다.

 

이 클래스에 실제로 값을 할당해주겠습니다.

 

Test<String> test = new Test<>();

 

 

위와 같이 제네릭 타입에 String 타입을 선언하는 순간 Test클래스 내부적으로는 T 타입이 String 타입으로 바인딩됩니다.

 

그럼 Test 클래스는 아래와 같은 형태를 띠게 되는 겁니다. 다르게 Integer 형태로 선언하게 되면 T 타입이 모두 Integer로 변하게 되는 것이고요.

 

class Test<String>{
    private String val;

    public String getVal(){
        return val;
    }
    public String setVal(String val){
        this.val = val
    }
}

 

 

여기서 주의할 점이 있습니다. 이미 한번 특정 타입으로 바인딩된 변수는 다른 타입으로 접근하려 하면 에러가 발생합니다.

 

List<String> list = new ArrayList<>();

// String 타입의 요소 추가
list.add("test");
// int 타입의 요소 추가 : 에러 발생!!!!
list.add(10)

 

 

 

 

 

제네릭 사용하기 : 인터페이스

 

 

제네릭을 인터페이스에도 사용 가능합니다. 다만, 실제로 구현부 클래스 및 메서드에도 인터페이스의 제네릭 타입과 맞추어서 사용합니다.

 

interface TestInterface<T>{
    public void setValue(T t);
    public T getValue();
}

class TestClass<T> TestClass implements TestInterface<T>{
    private T value;

    @Override
    public void setValue(T t){
        value = t;
    }

    @Override
    public T getValue(){
        return value;
    }
}

 

 

이러한 인터페이스를 아주 잘 활용하고 있는 부분이 바로 람다식(lambda)인데 이 내용은 추후 알아보도록 하겠습니다.

 

 

잘못되었거나, 이해가 되지 않는 부분은 댓글 남겨주세요. 감사합니다.