JAVA

[Java] 제네릭 타입

lakelight 2022. 7. 20. 13:40
728x90
반응형

제네릭 타입

어느 특정 데이터 형식에 의존하지 않고, 하나의 값이 여러 데이터 타입들을 가질 수 있도록 하는 방법

ex) ArrayList => ArrayList<타입> 객체명 new ArrayList<타입>();

 

제네릭 타입은 불공변

제네릭 타입은 하위 객체상위 객체의 하위 타입이더라도 호환이 되지 않습니다.
아래와 같이 사용하면 컴파일 오류가 납니다. 이를 불공변 하다고 합니다.

ArrayList<Object> objectList = new ArrayList<String>();

제네릭 타입이 불공변하기 때문에 컴파일 타임에서 타입 안전성을 보장해줄 수 있습니다.

 

제네릭 타입을 사용했을 때 좋은 점

  1. 잘못된 타입이 들어왔을 때 컴파일 단계에서 오류를 확인 할 수 있습니다. 제일 좋은 오류는 컴파일 단계에서 알 수 있는 오류이기 때문에 제네릭 타입을 사용한다면 좋은 효과를 가져올 수 있습니다.
  2. 클래스 외부에서 타입을 지정해주기 때문에 타입 체크에 자유롭고 관리가 편합니다.
  3. 코드의 재사용성이 높아집니다. 예를 들어 여러 객체에 대한 List를 구성할 때 사용한다면 좋을 것입니다.

 

제네릭 타입을 사용했을 때 주의할 점

  1. 클래스와 인터페이스만 적용되기 때문에 자바 기본타입은 사용할 수 없습니다. 참조 타입만을 사용해야 합니다. 참조 타입은 Integer, Character, Long, String, 사용자 정의 클래스 etc 입니다.
  2. 제네릭 타입을 사용한 객체 생성은 불가능 합니다. 즉 제네릭 타입의 객체는 생성이 불가능합니다.
    예를 들어  T t = new T()  같은 형태는 불가능합니다.
  3. 배열에 대한 제한이 있습니다. 

 

배열과 제네릭의 차이점

<컴파일 시 차이점>

컴파일 시에 배열은 실체화가 되지만 제네릭 타입은 소거가 됩니다.

<배열>
Object[] objects = new String[10];

배열은 런타임에 실체화가 되어 objects는 String[] 으로 동작합니다.

<제네릭 타입>
ArrayList<String> stringList = new ArrayList<String>();
//컴파일 시 아래와 같이 변경
ArrayList stringList = new ArrayList();

제네릭 타입은 런타임 타입을 지정한 부분이 사라지므로 ArrayList의 타입 식별이 불가능합니다.

<배열 생성 시 실제 문제점>

ArrayList<String>[] stringLists = new ArrayList<String>[1];
//String ArrayList 배열 생성 -> 제네릭 타입 소거

ArrayList<Integer> intList = new ArrayList<Integer>;
//Integer 배열 생성 -> 제네릭 타입 소거
intList.add(1);
Object[] objects = stringLists;

objects[0] = intList;
//컴파일 단계에서 보면 제네릭 타입이 소거되어 둘다 ArrayList이므로 저장

String s = stringLists[0].get(0)
//여기서 문제! StringList[0].get(0)을 꺼내면 String이어야 하는데 Integer이기 때문에 문제!

위와 같은 상황에는 런타임 에러가 발생할 수 있다

 

제네릭 타입의 실체화를 불가능하게 만든 이유

  1. 만약 제네릭 타입이 런타임에 실체화 된다면 제네릭이 생기기전에 사용했던 클래스들과 호환이 불가능하게 됩니다. 이렇듯 하위 호환성을 보장하기 위해서 실체화를 불가능하게 하였습니다.
  2. 제네릭이 런타임에 자신의 타입을 소거하더라고 개발자가 선언한 제네릭의 타입 파라미터에 명시한 타입만 들어갈 수 있도록 타입 안전성을 보장해줘야 합니다. (개발자의 의무)
  3. 제네릭 타입의 목적은 타입 안정성을 보장하기 위함이므로 타입 안정성을 보장해줘야 합니다. 제네릭 배열이 가능하도록 하면 타입 안전성을 보장할 수 없기 때문에 제네릭 배열을 직접 생성할 수 없습니다.

와일드카드 타입이나 형변환을 이용하면 배열을 생성할 수 있지만 되도록 원칙을 지키도록 코딩해야합니다.

 

제네릭 타입 종류

표시 의미
<T> Type
<E> Element
<K> Key
<V> Value
<N> Number

 

제네릭 클래스

class GenericClass<T> {
    private T content;

    public void setContent(T content) {
        this.content = content;
    }
			
    public T getContent() {
        return content;
    }
}

제네릭 클래스에 대한 예제 파일을 제작해보았습니다. T는 타입이 들어가는 위치이고 set메서드 get메서드도 구성해보았습니다.

 

제네릭 메서드

class GenericClass<T> {
    private T content;
    
    GenericClass(T content){
        this.content = content;
    }

    public void setContent(T content) {
        this.content = content;
    }
			
    public T getContent() {
        return content;
    }
    
    //제네릭 메서드 정의 부분
    public static<T> boolean compare(GenericClass<T>o1, GenericClass<T>o2) {
        boolean contentCompare = o1.getContent().equals(o2.getContent());
        return contentCompare;
    }
}

제네릭 메서드를 사용할 때는 컴파일러에게 제네릭 메서드라는 것을 알려주기 위해 리턴 타입을 꼭 명시해주어야 합니다.

 

결론

면접 때 질문을 받았던 제네릭 타입에 대해서 명확한 개념이 잡혀있지 않아 이번 포스팅을 통해
제네릭 타입에 대해서 알아보았습니다. 항상 생각하지만 아는 것은 옆 사람에게 설명할 수 있을 정도가
되어야 한다고 생각합니다. 이번 포스팅을 통해 공부를 하면서 제네릭의 개념을 확립할 수 있었던 시간이었습니다.

[참고]

1. 제네릭 타입

2. 제네릭 타입 사용시 주의할 점

3. 제네릭과 배열의 차이점

728x90
반응형