제네릭이란?

- 컴파일시 타입 체크를 강화 해주는 기능 ->실행 시 에러를 어떻게 컴파일러로 끌어올까? 에 대한 결과 중 하나가  제네릭

- 객체 타입 안정성을 높이고 형변환의 번거로움을 줄여준다.

// Tv객체만 저장할 수 있는 ArrayList를 생성 (ArrayList는 Object 타입)
ArrayList<Tv>  = new ArrayList<Tv>();

tvList,add(new tv());	 //OK
tvList.add(new Audio()); // 컴파일 에러(형변환 에러). tv 외의 다른 타입은 저장 불가


Tv t = tvList.get(0);  //제네릭 사용 시 형변환 불필요

//제네릭을 사용하지 않을 시
Tv t = (Tv)tvList.get(0);  //Object 타입 요소 꺼낼 시 형변환 필요

 

제네릭의 장점

1. 타입 안정성을 제공한다.

2. 타입체크와 형변환을 체크할 수 있으므로 코드가 간결해진다.

 

타입 변수

-  Object를 포함한 클래스를 작성할 때, Object 타입 대신 타입 변수(E)를 선언해서 자료형으로 사용.

- 타입 변수로는 아무 문자나 가능, 대문자 한 글자를 쓰는 게 보통 (T 또는 E)

- 객체 생성 시 타입 변수 대신 실제 타입을 지정(대입)

//JDK 1.5 이후부터 Object 타입을 타입 변수로 작성한다
public class ArrayList<e> extends AbstractList<E> { // 일부 생략
    private transient E[] elementData;
    public boolean add(E o) { 생략 }
    public E get(int index) { 생략 }
    
    ...
    }
//객체 생성 시
public class ArrayList extends AbstractList<E> { // 일부 생략
    private transient Tv[] elementData;
    public boolean add(Tv o) { 생략 }
    public Tv get(int index) { 생략 } //Object가 아닌 Tv를 반환
    
    ...
    }

 

 

제네릭 용어

Box<T> 제네릭 클래스. 'T의 Box' 또는 'T Box'라고 읽는다.
T 타입 변수 또는 타입 매개변수. (T는 타입문자)
Box 원시 타입(raw type)

 

제네렉 타입과 다형성

class Product {}
class tv extends Product {}
class Audio extends Product {}

- 참조 변수와 생성자의 대입된 타입은 일치해야 한다

ArrayList<Tv>   	list = new ArrayList<Tv>();		//OK. 일치
ArrayList<Product>	list = new ArrayList<Tv>();	//에러. 불일치

- 제네릭 클래스간의 다형성은 성립.(여전히 대입된 타입은 일치히야함)

List<Tv> list = new ArrayList<Tv>();		//OK. ArrayList가 List를 구현
List<Tv> list = new LinkedList<Tv>();	//Ok. LinkedList가 List를 구현

 

- 매개변수의 다형성도 성립.

ArrayList<Product>	list = new ArrayList<Tv>(Product);
list.add(new Product());
list.add(new Tv());			// Product의 자손 Ok
list.add(new Audio());

 

- JDK 1.7부터 타입 선언 시 타입 변수가 작성 되면 타입 추론이 가능하기 때문에 생성자 쪽의 타입을 생략하고 작성할 수 있다. 단, 빈 다이아몬드 연산자는 사용해야 한다.

ArrayList<Product>	list = new ArrayList<>();

 

제한된 제네릭 클래스

- extends로 대입할 수 있는 타입을 제한. 인터페이스인 경우에도 extends를 사용한다.

class FruitBox<T extends Fruit> { // Fruit의 자손만 타입으로 지정가능
	ArrayList<T> list = new ArrayList<T>();
    ...
 }
 
 FruitBox<Apple> applebox = new FruitBox<Apple>();	
 FruitBox<Toy> toybox = new FruitBox<Toy>();		//Toy는 Fruit의 자손이 아니므로 에러

 

와일드카드 <?>

<? extends T>   와일드카드의 상한 제한. T와 그 자손들만 가능   -> 가장 많이 쓰임
<? super T>       와일드카드의 하한 제한. T와 그 조상들만 가능
<?>                    제한 없음. 모든 타입이 가능. <? extends Object>와 동일 

- 하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능

ArrayList<? extends Product> list = new ArrayList<Tv>();
ArrayList<? extends Product> list = new ArrayList<Audio>();
ArrayList<Product> list = new ArrayList<Tv>();	// 에러. 대입된 타입 불일치

- 메서드의 매개변수에 와일드 카드를 사용

static Juice makeJuice(FruitBox<? extends Fruit> box) {
	
    ...
    apple, grape 등등 모두 사용 가능

 


제네릭의 제약

- 타입 변수 대입은 인스턴스 별로 다르게 가능

Box<Apple> applebox = new Box<Apple>();	
Box<Grape> grapebox = new Box<Grape>();

 

- static멤버에 타입 변수 사용 불가

  ->static 은 모든 인스턴스의 공통 

 

- 배열 생성할 때 타입변수 사용 불가. 타입 변수로 배열 선언은 가능

 -> new 연산자 다음에 사용할 수 없다. new는 타입이 확정된 다음에 사용 가능함.

class Box<T> {
	T[] itemArr; // OK, T타입의 배열을 위한 참조변수
    	...
    
    T[] toArray() {
    	T[] tmpArr = new T [itemarry length]; //에러, 제네릭 배열 생성 불가
        	...

 

제네릭 메소드

- 제네릭 타입이 선언된 메소드(타입 변수는 메소드 내에서만 유효)

static <T> void sort(List<T> list, Comparator<? super T> c)

 

- 클래스의 타입 매개변수<T>와 메서드의 타입 매개변수 <T>는 별개   => 타입 문자가 일치하지만 다른 타입변수이다.

class FruitBox<T> {
	...
    static <T> void sort(List<T> list, Comparator<? super T> c) {
    ...
    
    }
 }

 

- 제네릭 메소드는 메소드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 한 것. (지네릭 타입 대부분 생략 가능) 

 FruitBox<Fruit> applebox = new FruitBox<Fruit>();	
	...
Juicer.<Fruit>makeJuice<Fruit>();
Juicer.makeJuice<Fruit>(); // 생략 가능

 

제네릭 타입의 형변환

- 지네릭 타입과 원시 타입 간의 형변환은 바람직 하지 않다. (경고 발생)

Box<Object> objBox = null;
Box<String> strBox = null;

Box box = (Box)objBox;      //Ok, 제네릭 타입 -> 원시타입  경고 발생
objBox = (Box<Object>)box   //Ok,  원시 타입 -> 제네릭 타입 경고 발생

objBox = (Box<Object>)strBox;  //에러   Box<String> -> Box<Object>
strBox = (Box<Object>)objBox;  //에러   Box<Object> -> Box<String>

- 와일드 카드가 사용된 제네릭 타입으로는 형변환 가능

Box<Object> objBox = (Box<Object>)new Box<String>();  // 에러. 형변환 불가능

Box<? extends Object> wBox = (Box<? extends Object>)new Box<String>();  //OK
Box<? extends Object> wBox = new Box<String>();  //위 문장과 동일. (Box<? extends Object>) 생략 가능


// 매개변수로 FruitBox<Fruit>, FruitBox<Apple>, FruitBox<Grape> 등이 가능
static Juice makeJuice(FruitBox<? extends Fruit> box) { ...}

FruitBox<? extends Fruit> box = new FruitBox<Fruit>();
FruitBox<? extends Fruit> box = new FruitBox<Apple>();

 

제네릭 타입의 제거

- 컴파일러는 제네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.

     -> 하위호환성을 위해(제네릭 타입 도입 이전 버전과의)

    -> 우리에게 불필요해서 안 보일뿐이지 형변환은 필요하다.

1. 제네릭 타입의 경계(bound)를 제거.

     -> 컴파일하면 <T>가 다시 Object로 변환, 제한된 경우엔 Objectrk Fruit로 변환

class Box<T extends Fruit> {
	void add(T t) {
    	...
     }
 }
 
 
class Box {
	void add(Fruit t) {
    	...
    }
}

 

2. 제네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가

T get(int i) {
	return list.get(i)
 }
 
 
 
 Fruit get(int i) {
 	return (Fruit)list.get(i);
  }

 

3. 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가

 

 

 

 

 

 

 

 

 

남궁성의 자바의 정석 강의 정리

'LECTURE > JAVA' 카테고리의 다른 글

12.컬렉션  (0) 2023.01.06
08.상속  (0) 2023.01.06
07. 객체 배열  (0) 2023.01.06
06.클래스와 객체  (0) 2023.01.06
05.배열  (0) 2023.01.06

+ Recent posts