제네릭(Generic)
왜 제네릭(Generic)을 사용해야 하는가?
제네릭(Generic)이란
- 타입을 파라미터화해서 컴파일시 구체적인 타입이 결정되도록 하는 것
- 자바5부터 새로 추가된 기능이다.
- 컬렉션, 람다식(함수적 인터페이스), 스트림,NIO에서 널리 사용된다.
제네릭을 사용하므로서 얻는 이점
컴파일시 강한 타입 체크를 할 수 있다.
실행시 타입 에러가 나는 것보다는 컴파일시에 미리 타입을 강하게 체크해서 에러를 사전에 방지한다.
타입변환을 제거할 수 있다.
List list = new ArrayList(); list.add("hello"); //object으로 자동형변환 String str = (String) list.get(0); //String으로 강제타입변환 // 제네릭 이용하기 List<String> list = new ArrayList<String>(); list.add("hello"); //String으로 해야한다. String str = list.get(0);
2절 제네릭 타입
제네릭 타입이란
타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.
선언시 클래스 또는 인터페이스 이름 뒤에 "<>" 부호가 붙는다.
"<>" 사이에는 타입 파라미터가 위치한다.
public class 클래스명<T> {...} public interface 인터페이스명<T>{...}
타입 파라미터
- 일반적으로 대문자 알파벳 한문자로 표현한다.
- 개발 코드에서는 타입 파라미터 자리에 구체적인 타입을 지정해야한다.
제네릭
public class Box<T> { private T t; public T get(){ return t;} public void set(T t){this.t = t}; }
Box<String> box = new Box<String>(); box.set("hello"); String str = box.get();
3절. 멀티 타입 파라미터
두개 이상의 타입 파라미터를 사용해서 선언할 수 있다.
class<K,V...> {...}
interface<K,V..> {...}
public class Product<T,M> { private T kind; private M model; public T getKind() { return kind; } public M getModel() { return model; } public void setKind(T kind) { this.kind = kind; } public void setModel(M model) {this.model = model; } }
Product<Tv, String> product = new Product<>(); //< > 생략가능
4절. 제네릭 메소드
제네릭 메소드
매개변수 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드
제네릭 메소드 선언 방법
public <타입파라미터,...> 리턴타입 메소드명(매개변수,...){...}
public <T> Box<T> boxing(T t) { ... } //Box<T>객체가 리턴되어서 나옴
제네릭 메소드를 호출하는 방법
리턴타입 변수 = <구체적인타입> 메소드명(매개값); //명시적으로 구체적타입 지정
리턴타입 변수 = 메소드명(매개값); //더 자주 사용한다. 매개값을 보고 구체적 타입 추정
Box<Integer> box = <Integer>boxing(100); Box<Integer> box = boxing(100);
public class Util {
public static<K,V> boolean compare(Pair<K,V> p1, Pair<K,V> p2) {
boolean keyCompare = p1.getKey().equals(p2.getKey());
boolean valueCompare = p1.getValue().equals(p2.getValue());
return keyCompare&&valueCompare ;
}
}
package chap13.exam02_generic_method;
public class Pair<K,V> {
private K key;
private V value;
Pair(K key, V value){
this.key = key;
this.value = value; }
public void setKey(K key) { this.key = key; }
public void setValue(V value) { this.value = value; }
public K getKey() { return key; }
public V getValue() { return value; }
}
13.5 제한된 타입 파라미터
타입 파라미터에 구체적인 타입을 제한할 필요가 있을 경우
상속 및 구현 관계를 이용해서 타입을 제한
public < T extends 상위타입> 리턴타입 메소드(매개변수,...) {...}
상위 타입은 클래스 뿐만 아니라 인터페이스도 가능하다.
인터페이스라고해서 extends 대신 implements를 사용하지 않는다.
타입 파라미터를 대체할 구체적인 타입
- 상위타입이거나 하위 또는 구현 클래스만 지정할 수 있다.
주의할 점
메소드의 중괄호 {} 안에서 타입 파라미터 변수로 사용 가능한 것은 상위 타입의 멤버(필드, 메소드)로 제한된다.
하위 타입에만 있는 필드와 메소드는 사용할 수 없다.
public <T extends Number> int compare(T t1, T t2) { //Number이거나 하위클래스 double v1 = t1.doubleValue(); //Number의 doubleValue() 메소드 사용 double v2 = t2.doubleValue(); //특정 자식클래스에만있는 메소드사용(x) return Double.compare(v1,v2); }
6절. 와일드카드 타입
와일드카드<?> 타입
제네릭 타입을 매개변수나 리턴타입으로 사용할 때 타입 파라미터를 제한할 목적
public static void registerCourse(Course<?> course) public static void registerCourseStudent(Course<? extends Student> course) public static void registerCourse(Course<? super Worker> course)
- 와일드카드 타입의 세가지 형태
- 제네릭 타입<?> : Unbounded Wildcards (제한없음)
- 제네릭 타입 <? extends 상위 타입> : Upper Bounded Wildcards (상위 클래스 제한)
- 제네릭 타입 <? super 하위 타입> : Lower Bounded Wildcards (하위 클래스 제한)
🔼Course<?> :수강생은 모든 타입(Person,Worker,Student, HighStudent) 이 될 수 있다.
🔼Course<? extends Student> : 수강생은 Student와 HighStudent만 될 수 있다.
🔼Course<? super Worker> : 수강생은 Worker와 Person만 될 수 있다.
7절. 제네릭 타입의 상속과 구현
제네릭 타입을 부모 클래스로 사용할 경우
타입 파라미터는 자식 클래스에도 기술해야한다.
public class ChildProduct<T,M> extends Prouduct<T,M>{...}
추가적인 타입 파라미터를 가질 수 있다.
public class ChildProduct<T,M,C> extends Prouduct<T,M>{...}
제네릭 인터페이스를 구현할 경우
타입 파라미터는 구현 클래스에도 기술해야한다.
public class StorageImpl<T> implements Storage<T> {...}
public class StorageImpl<T> implements Storage<T> {
private T[] array;
public StorageImpl(int capacity) {
this.array = (T[])(new Object[capacity]);
//타입 파라미터로 배열을 생성하려면 new T[n]형태로 생성할 수 없고
//(T[])(new Object[]) 생성해야한다.
}
@Override
public void add(T item,int index){
array[index] = item;
}
@Override
public T get(int index){
return array[index];
}
}
Storage<Tv> storage = new StorageImpl<Tv>(100);
storage.add(new Tv(), 0);
Tv tv = storage.get(0);