제네릭에 대해서 설명해주세요

한상우·2022년 10월 5일
0

java

목록 보기
7/16

Q. 제네릭에 대해서 설명해주세요

Generic은 데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 하는 방법이다. -> 다형성

객체의 타입을 컴파일 시에 체크해줌으로써 의도하지 않은 타입의 객체가 저장되는 것을 막는다.
-> 객체의 타입 안정성을 높여준다

간단히 ArrayList 클래스를 살펴보면

public class ArrayList<E> extends AbstractList<E> ...

이렇게 되어있다. < > 괄호 안에 들어가는 타입이 제네릭 타입이다. 이를 통해 우리는 여러 데이터 타입의 ArrayList 객체를 생성할 수 있다. 만약 제네릭 타입이 없다면 각 타입에 맞게 클래스를 하나하나 만들어야 할 것이다..

ArrayList<String> stringArr = new ArrayList<>();
ArrayList<Integer> integerArr = new ArrayList<>();
ArrayList<Double> doubleArr = new ArrayList<>();

제네릭 타입은 클래스와 메서드에 선언할 수 있다.


제네릭 클래스

  • <T>를 통해 객체를 생성할 때 외부에서 타입을 지정한다.

암묵적 규칙

  • <T> : 타입
  • <E> : 요소
  • <K, V> : 키, 값
public class Box<T> {

    T item; // 타입 변수

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}
public static void main(String[] args) {
        Box<String> box = new Box<String>(); // 스트링만 받겠다

        box.setItem("Box");
        box.getItem();

        box.setItem(123); // 컴파일 에러

    }
  • 타입 파라미터로 명시할 수 있는 것은 참조 타입 (Reference Type)만 올 수 있다. primitive type은 올 수 없다.
    -> 참조 타입이 올 수 있다는 것은 사용자가 정의한 클래스도 타입으로 올 수 있다는 것.

제한

  • 모든 객체에 동일하게 동작해야 하는 static 멤버에 타입변수 T를 사용할 수 없다.

이를 이해하려면 인스턴스 변수를 알아야 하는데, 인스턴스 변수는 실체화된 객체가 쓰는 변수를 말한다. 타입 변수는 new 를 통해 객체를 만들 때 지정하는 것이므로, static에 끼워 넣는건 말이 안되겠지??

public class Box<T> {
    static T test; // 안된다
}
  • 제네릭 배열을 생성하는 것은 불가능.
  • 제네릭 배열 타입의 참조변수를 선언하는 것은 가능

new 연산자는 컴파일 시점에 타입 T가 뭔지 알아야 하기 때문에

T[] lst;  // 제네릭 배열 타입 선언은 가능
lst = new T[10];  // 불가능!!!

타입

  • Box<T> 의 객체를 생성할 때 참조변수와 생성자에 대입된 타입이 일치해야 한다. 아니면 에러 발생
Box<String> box = new Box<String>();   // Good
Box<String> box = new Box<Integer>();   // 타입 불일치

때문에 JDK 1.7 부터는 참조 변수의 타입만 쓰고 생성자에 타입을 지정하지 않아도 된다.
추정이 가능할 경우 타입을 생략할 수 있음

  • 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다 (extends)
public class Box<T extends Collection> // Collection 타입의 자손들만 대입할 수 있음


Box<ArrayList> box1 = new Box<>();  // ArrayList는 Collection을 상속
Box<String> box2 = new Box<>(); // error

제네릭 메소드

메소드의 선언 부에 적은 제네릭으로 리턴 타입, 파라미터 타입이 정해진다.
static <T> void 함수이름 ( T 매개변수 )

여기서 <T> 는 반환값이 아니다. 그냥 제네릭 메소드라는 것을 명시해주는 것

메소드에 선언된 제네릭 타입 매개변수는 결국 메소드 내에서만 사용될 것이여서 static이여도 된다! (아직은 잘 이해가 안가지만 그냥 제네릭 변수는 static으로 쓸 수 없지만, 제네릭 메소드는 쓸 수 있다 라고 알아둔다)

static <T> void test(T item) {
        System.out.println(item);
    }

와일드 카드

List<String> arrStr = new ArrayList<>();
List<Object> arrObj = arrStr;

위 경우 될거 같지만, 컴파일 에러가 난다.
왜냐!!! 위에가 정상 작동한다면 arrStr에 Integer 타입 값을 넣는게 가능해진다...
⚠️ arrObj.add(123)

어떤 컬렉션이든 받아서 출력하는 메소드를 만들려면 와일드 카드를 쓰면 된다.
아래와 같이 쓰면 컴파일 에러 사라짐

List<String> arrStr = new ArrayList<>();
List<?> arrObj = arrStr;
  • 와일드 카드 종류
List<?> arrObj = arrStr; // 제한 없음, 모든 타입 가능
List<? extends String> arrObj2 = arrStr; // T(String)와 그 자손들만 가능
List<? super String> arrObj3 = arrStr; // T(String)와 그 조살들만 가능

형 변환

Box<Object> objBox = (Box<Object>)new Box<String>(); // ERROR. 형변환 불가능
Box<? extends Object> wBox = (Box<? extends Object>)new Box<String>(); // OK
Box<? extends Object> wBox = new Box<String>(); // 위 문장과 동일

추가

컴파일러는 제네릭 타입을 이용해서 소스 파일을 체크하고, 필요한 곳에 형변환을 넣어준다. 그리고 제네릭 타입을 제거한다

즉, 컴파일된 파일에는 제네릭 타입에 대한 정보가 없다
→ 제네릭이 도입되기 이전의 소스 코드와의 호환성을 위해 (JDK 1.5부터 도입됨)

profile
안녕하세요 ^^

0개의 댓글