[JAVA]Generics

Ming·2022년 10월 3일
0

자바

목록 보기
11/14

Generics

Generics

컴파일시 타입을 체크해주는 기능으로 객체의 타입 안정성을 높이고, 형변환의 번거로움을 줄여준다.

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

tvList.add(new Tv());
tvList.add(new Audio()); //컴파일 에러

위의 예제와 같이 지정된 객체 외 다른 객체가 들어오면 컴파일러가 막아준다. 런타임에러(실행 시 발생하는 에러)를 컴파일 에러로 변경해 사전에 예방할 수 있다.

ArrayList tvList = new ArrayList();
tvList.add(new Tv());
Tv t = (Tv)tvList.get(0); //get()의 반환 타입이 Object이기 때문에 형변환 필요
ArrayList<Tv> tvList = new ArrayList<Tv>();
tvList.add(new Tv());
Tv t = tvList.get(0); //형변환 불필요

지네릭스를 사용하지 않은 경우 get()의 반환 타입은 Object이기 t의 타입은 Tv이기 때문에 형변환이 빌표한 반면, 지네릭스를 사용하는 경우는 반환타입도 Tv이기 때문에 형변환이 필요없다.

지네릭스의 장점

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

지네릭스 용어

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

class Box<T> { }

타입변수

Generics 클래스를 작성할 때, Object 타입 대신 타입 변수(E)를 선언해서 사용한다.

public class ArrayList<E> extends AbstractList<E> {
	private transient E[] elementData;
    public boolean add(E o) { ... }
    public E get(int index) { ... }
    ...
}

객체를 생성할 때, 타입 변수(E) 대신 실제 타입을 지정(대입)하면 된다.

ArrayList<Tv> tvList = new ArrayList<Tv>();

지네릭스 다형성

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

class Product { }
class Tv extends Product { }
ArrayList<Product> list = new ArrayList<Tv>(); //에러

위와 같이 Product 클래스가 Tv 클래스의 조상 클래스여도 에러가 발생한다. 참조 변수와 생성자의 대입된 타입은 일치해야한다.
단, 지네릭 클래스간의 다형성은 성립한다.

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

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

ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv()); //Ok

위와 같이 매개변수의 경우 Product의 자손들이 와도 성립한다.

제네릭 클래스

Iterator <E>

클래스를 작성할 때, Object 타입 대신 T와 같은 타입 변수 사용

//지네릭스 사용 전
public interface Iterator {
	boolean hasNext();
    Object next();
    void remove();
}
//지네릭스 사용 후 
public interface Iterator<E> {
	boolean hasNext();
    E next();
    void remove();
}
//지네릭스 사용 전
Iterator it = list.iterator();
while(it.hasNext()) {
	Student s = (Student)it.next();
    ...
}
//지네릭스 사용 후
Iterator<Student> it = list.iterator();
while(it.hasNext()) {
	Student s = it.next();
    ...
}

Oject 타입이 E로 바뀌었기 때문에 형변환을 해주지 않아도 된다.

HashMap<K, V>

여러 개의 타입 변수가 필요한 경우, 콤마(,)로 구분자 선언

public class HashMap<K, V> extends AbstractMap<K, V> {
	...
    public V get(Object key) { ... }
    public V put(K key, V value) { ... }
    public V remove(Object key) { ... }
    ...
}
  • 예시
HashMap<String, Student> map = new HashMap<String, Student>(); //생성
map.put("자바", new Student("자바", 1, 1, 100, 100, 100)); //데이터 저장
public class HashMap extends AbstractMap {
	...
    public Student get(Object key) { ... }
    public Student put(String key, Student value) { ... }
    public Student remove(Object key) { ... }
   	...
}

제한된 지네릭 클래스

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

class FruitBox<T extends Fruit> {
	ArrayList<T> list = new ArrayList<T>();
}

위의 예제는 Fruit의 자손만 T의 타입으로 지정가능하게 제한 것과 같다.

지네릭스의 제약

  • 타입 변수에 대입은 인스턴스 별로 다르게 가능하다. 그러므로 static 멤버(모든 인스턴스에 공통)에는 타입 변수를 사용할 수 없다.
class Box<T> {
	static T item; //error
    static int compare(T t1, T t2) { ... } //error
}
  • 타입 변수로 배열 선언은 가능하지만 배열 생성할 때 타입 변수를 사용할 수 없다. new 연산자는 뒤에 타입이 확정되어 있어야하는데 T는 어떤 타입이 올지 모르기 때문에 에러가 발생한다.
class Box<T> {
	T[] itemArr; // 선언할 때 ok
    T[] temArr = new T[10]; //생성할 때 error
}

와일드 카드< ? >

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

ArrayList<? extends Product> list = new ArrayList<Tv>();
ARrayList<? extends Product> list = new ArrayList<Audio>();

종류

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

  • 메서드의 매개변수에도 와일드 카드를 사용할 수 있다.
static Juice makeJuice(FruitBox<? extends Fruit> box) {
	String tmp = "";
	for(Fruit f : box.getList()) tmp += f + " ";
	return new Juice(tmp);
}
System.out.println(Juicer.makeJuice(new FruitBox<Apple>()));

지네릭 메서드

지네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)이다. 이때 클래스 타입 매개변수 <T>와 메서드의 타입 매개변수 <T>는 별개이다. 지네릭 메서드 내에서는 지네릭 메서드의 타입 변수가 우선이 된다.

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

메서드를 호출하라 때마다 타입을 대입해야하지만 대부분 생략 가능하다. 메서드를 호출할 때 타입을 생략하지 않을 때는 클래스 이름 생략이 불가능하다.

static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
	...
}
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.<Apple>makeJuice(appleBox));
System.out.println(<Fruit>makeJuice(fruitBox)); //error

0개의 댓글