제네릭

Jae-Baek Song·2023년 3월 10일
0

제네릭 클래스란

제네릭의 장점
1. 타입 안정성을 제공한다.
2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.

객체의 타입을 미리 명시해줌으로써 번거로운 형변환을 줄여준다.

기존에는 다양한 종류의 타입을 다루는 메서드의 매개변수나 리턴타입으로 Object타입의 참조변수를 많이 사용했고, 그로 인해 형변환이 불가피했지만, 이젠 Object 타입 대신 원하는 타입을 지정하기만 하면 되는 것이다.


제네릭의 제한

static멤버에 타입 변수 T를 사용할 수 없다.
T는 인스턴스변수로 간주되기 때문이다.
static멤버는 인스턴스 변수를 참조할 수 없다.

하지만제네릭 메소드는 static이 가능하다

제네릭 타입의 배열을 생성하는 것도 허용되지 않는다.

제네릭 배열을 생성해야할 필요가 있을 때는, newInstance()와 같이 동적으로 객체를 생성하는 메서드로 배열을 생성하거나, Object 배열을 생성해서 복사한 다음에 T[] 로 형변환하는 방법 등을 사용한다.

제네릭 클래스의 객체 생성과 사용

제네릭 객체를 생성할 때는 참조변수와 생성자에 대입된 타입이 일치해야한다.

Box<Apple> appleBox = new Box<Apple>(); //OK
Box<Apple> appleBox = new Box<Grape>(); //에러

두 타입이 상속 관계에 있어도 마찬가지이다. Apple이 Fruit의 자손이라고 가정하자.

Box<Fruit> appleBox = new Box<Apple>(); //에러 대입된 타입이 다르다
Box<Apple> appleBox = new FruitBox<Apple>(); //OK 다형성

타입 T가 Fruit인 경우 Fruit의 자손들은 이 메서드의 매개변수가 될 수 있다.

Box<Fruit> fruitBox = new Box<Fruit>();
fruitBox.add(new Fruit());
fruitBox.add(new Apple()); 

제한된 제네릭 클래스

다형성에서 조상타입의 참조변수로 자손타입의 객체를 가리킬 수 있는 것처럼, 매개변수화된 타입의 자손 타입도 가능한 것이다.

타입 매개변수 T에 Object를 대입하면, 모든 종류의 객체를 저장할 수 있게된다.

제한된 제네릭 타입은 클래스, 인터페이스, 제네릭 메소드에서만 사용할 수 있습니다.

와일드 카드

public static <T> void peekBox(Box<T> box){
    System.out.println(box);
}
  
public static void peekBox(Box<?> box){
    System.out.println(box);
}

'기능적인 측면에서' 와일드카드와 제네릭 메소드는 같다.
와일드 카드를 사용했을때 코드의 간결성의 장점을 가진다.

?만으로는 Object타입과 다를 게 없으므로, 다음과 같이 extends 와 super 로 상한과 하한을 제한할 수 있다.

<? extend T> //와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T> //와일드 카드의 하한 제한. T와 그 조상들만 가능
<?> //제한 없음 <? extends Object>와 동일

<? extend T & E> //제네릭 클래스와 달리 와일드 카드에서는 &를 사용할수없다

공변과 불공변

배열은 공변입니다

DogAnimal의 하위 타입이라면 배열 Dog[]는 배열 Animal[]의 하위 타입이 됩니다

// 런타임에 실패한다
Object[] objectArray = new Long[1];  // 컴파일이 되어 버린다..
objectArray[0] = "Hello";             // 런타임에 ArrayStoreException을 던진다..

제네릭은 불공변입니다

// 컴파일되지 않는다
List<Object> ol = new ArrayList<Long>();  // 컴파일 안된다. 호환되지 않는 타입이다
ol.add("Hello");

https://hwan33.tistory.com/m/24

배열은 런타임에 실체화, 제네릭 타입은 런타임에 소거

배열은 런타임에 실체화

Object[] objects = new String[1];

배열은 런타임에 타입이 실체화되기 때문에 Objects는 런타임에 String[]가 됩니다.

제네릭 타입은 런타임에 소거

자바에서 제네릭 타입을 굳이 런타임에 소거한 이유는 하위 호환성을 보장하기 위함입니다.
만약 제네릭 타입이 런타임에 실체화 된다면 제네릭이 생기기전에 사용했던 클래스들와 호환이 불가능하게 됩니다.

// 컴파일 타임(실제 작성한 코드)
ArrayList<String> stringList = new ArrayList<String>();
ArrayList<Integer> integerList = new ArrayList<Integer>();

// 런타임(제네릭 타입은 런타임에 소거되므로 구분이 불가능하다)
ArrayList stringList = new ArrayList();
ArrayList integerList = new ArrayList();

제네릭 타입은 런타임에 소거되므로 런타임에는 타입이 소거된 ArrayList만 남게됩니다.

https://pompitzz.github.io/blog/Java/whyCantCreateGenericsArray.html#%E1%84%92%E1%85%A7%E1%86%BC%E1%84%87%E1%85%A7%E1%86%AB%E1%84%92%E1%85%A1%E1%86%AB-%E1%84%8B%E1%85%B5%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%92%E1%85%A1%E1%84%80%E1%85%B5

0개의 댓글

관련 채용 정보