
컴파일 시 타입을 체크해 주는 기능
객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어듬
-> 의도치 않은 타입의 객체가 저장되는 것을 막음
// Tv객체만 저장할 수 있는 ArrayList를 생성
ArrayList<Tv> tvList = new ArrayList<Tv>();
tvList.add(new Tv()); // OK
tvList.add(new Audio()); // 컴파일 에러, TV 외 저장 불가
지네릭스 적용 전
ArrayList tvList = new ArrayList();
tvList.add(new Tv());
Tv t = (TV)tvlist.get(0);
적용 후
Arraylist<Tv> tvList = new ArrayList<Tv>();
tvList.add(new TV());
Tv t = tvList.get(0);
클래스 선언 시 붙이는 < E > 안에 E를 타입 변수라 부름
public class ArrayList<E> extends AbstractList<E>
{
private transient E[] elementData;
public boolean add(E o) {}
public E get(int index){}
...
}
기존 ArrayList 코드
public class ArrayList extends AbstractList
{
private transient Object[] elementData;
public boolean add(Object o){};
public Object get(int index){};
}
타입변수가 여러개인 경우 < K, V > 와 같이 작성
사용 예시
// 타입 변수 E 대신에 실제 타입 Tv를 대입
ArrayList<Tv> tvList = new ArrayList<Tv>();
public class ArrayList extends AbstractList
{
private transient Tv[] elementData;
public boolean add(Tv o){};
public Tv get(int index){}
}
get이 Object가 아닌 Tv를 반환하기에 형 변환이 필요 없는것!
class Box<T> {}
대입된 타입Box< Integer > 과 Box< String > 은 별개의 클래스 X
-> 서로 다른 타입을 대입해서 호출 하는 것!
컴파일 후에는 둘 다 원시 타입인 Box로 바뀜
지네릭 클래스 객체 생성 시, 참조변수에 지정해준 지네릭 타입과 생성자에 지정해준 지네릭 타입은 일치해야 함
ArrayList<Tv> list = new ArrayList<Tv>(); //OK
ArrayList<Product> list = new ArrayList<Tv>(); // 에러
class Product(){ }
class Tv extends Product{ }
class audio extends Product{ }
자손 객체 저장
ArrayList<Product> list = newArrayList<Product>();
list.add(new Product());
list.add(new Tv()); //OK
list.add(new Audio()); //OK
/*
꺼낼떄 형변환 필요
Product p - list.get(0);
Tv t = (Tv)list.get(1);
*/
클래스 타입 간 다형성 적용은 가능(지네릭 타입은 일치)
List<Tv> list = new ArrayList<Tv>();
List<Tv> list = new LinkedList<Tv>();
package ch12;
import java.util.*;
class Product{}
class Tv extends Product{}
class Audio extends Product{}
public class Ex12_1 {
public static void main(String[] args) {
ArrayList<Product> productList = new ArrayList<Product>();
ArrayList<Tv> tvList = new ArrayList<Tv>();
// ArrayList<Product> tvList = new ArrayList<Tv>(); //에러
// List<Tv> tvList = new ArrayList<Tv>();
productList.add(new Tv());
productList.add(new Audio());
tvList.add(new Tv());
tvList.add(new Tv());
printAll(productList);
//printAll(tvList) // 컴파일 에러 발생
}
public static void printAll(ArrayList<Product> list)
{
for(Product p: list)
System.out.println(p);
}
}
ch12.Tv@ea30797
ch12.Audio@58d25a40
Iterator에도 제네릭스 적용 가능
public interface Iterator<E>
{
boolean hasNext();
E next();
void remove();
}
package ch12;
import java.util.*;
public class Ex12_2 {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("자바왕", 1, 1));
list.add(new Student("자바왕", 1, 2));
list.add(new Student("홍길동", 2, 1));
Iterator<Student> it = list.iterator();
while(it.hasNext()) {
Student s = it.next(); // 제네릭스를 사용하였으므로 형변환 필요 X
System.out.println(s.name);
}
}
}
class Student{
String name = "";
int ban;
int no;
Student(String name, int ban, int no)
{
this.name = name;
this.ban = ban;
this.no = no;
}
}
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));
/*
Student s1 = (Student)map.get("1-1");
이 경우 형변환 필요 X
*/
타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한하는 방법
예시
// 만약 인터페이스를 구현해야 한다는 제약이 필요할 때도, extends 를 사용
// implements X
class FruitBox<T extends Fruit>
{
//Fruit의 자손만 타입으로 지정가능
ArrayList<T> list = new ArrayList<T>();
....
}
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러
// 이런 식으로도 가능
class FruitBox<T extends Fruit & Eatable> {...}
package ch12;
import java.util.ArrayList;
class Fruit implements Eatable{
public String toString() { return "Fruit";}
}
class Apple extends Fruit { public String toString() {return "Apple";}}
class Grape extends Fruit { public String toString() {return "Grape";}}
class Toy { public String toString() {return "Toy";}}
interface Eatable {}
public class Ex12_3 {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Grape> grapeBox = new FruitBox<Grape>();
/*
FruitBox<Grape> grapeBox = new FruitBox<Apple>(); 타입 불일치 에러
FruitBox<Toy> toyBox = new FruitBox<Toy>(); 에러
*/
fruitBox.add(new Fruit());
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
// appleBox.add(new Grape()); // 에러 Grape는 애플의 자손이 아님
grapeBox.add(new Grape());
System.out.println("fruitBox-" +fruitBox);
System.out.println("appleBox-" +appleBox);
System.out.println("grapeBox-" + grapeBox);
}
}
class FruitBox<T extends Fruit & Eatable> extends Box<T>{}
class Box<T>{
ArrayList<T> list = new ArrayList<T>();
void add(T item) { list.add(item);}
T get (int i) { return list.get(i);}
int size() { return list.size();}
public String toString() { return list.toString();}
}
fruitBox-[Fruit, Apple, Grape]
appleBox-[Apple]
grapeBox-[Grape]
객체별로 다른 타입을 지정하는 것이 적절Box<Apple> appleBox = new Box<Apple>();
Box<Grape> appleBox = new Box<Grape>();
T는 인스턴스 변수로 간주되기에 static 멤버에 타입 변수 T 사용 불가
즉 Box< Apple >.item과 Box< Grape >.item이 다른 것이면 안된다.
class Box<T>
{
static T item; // 에러
static int compare(T t1, T t2){ ... } // 에러
}
지네릭 타입의 참조변수를 선언하는 건 가능하지만, 배열을 생성하는 것은 안된다(new)
new와 instanceof는 컴파일 시점에 타입이 뭔지 정확히 알아야 하는데, 컴파일 하는 시점에서는 T가 어떤 타입이 될지 알 수 없다
class Box<T>
{
T[] imterArr; // Ok. T타입의 배열을 위한 참조변수
...
T[] toArray(){
T[] tmpArr = new T[itemArr.length]; // 에러, 지네릭 배열 생성 불가
...
return tmpArr;
}
...
}
지네릭 클래스 생성 시, 참조변수에 지정된 지네릭 타입과 생성자에 지정된 지네릭 타입은 일치해야 한다
// Ok. 지네릭 타입 일치
ArrayList<Tv> list = new ArrayList<Tv>();
// Ok. 다형성. 지네릭 타입 일치
List<Tv> list = new ArrayList<Tv>();
// 에러. 지네릭 타입 불일치
ArrayList<Product> list = new ArrayList<Tv>();
결론적으로 지네릭 타입에 다형성이 적용되있지 않음
-> 이를 구현하려면 와일드 카드 사용
<? extends T>와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T>와일드 카드 하한 제한. T와 그 조상들만 가능
< ? >제한 없음(모든 타입 가능) <? extends Object>와 동일
package ch12;
import java.util.ArrayList;
class Fruit2 { public String toString() { return "Fruit";}}
class Apple2 extends Fruit2 { public String toString() { return "Apple";}}
class Grape2 extends Fruit2 { public String toString() { return "Grape";}}
class Juice
{
String name;
Juice(String name) { this.name = name + "Juice"; }
public String toString() { return name;}
}
class Juicer
{
// 메서드 매개변수에도 와일드 카드 사용 가능
// 즉 Juicer.makeJuice(new FruitBox2< Apple or Grape>()))가능
static Juice makeJuice(FruitBox2<? extends Fruit2> box)
{
String tmp = "";
for(Fruit2 f : box.getList())
tmp += f + " ";
return new Juice(tmp);
}
}
public class Ex12_4 {
public static void main(String[] args) {
FruitBox2<Fruit2> fruitBox = new FruitBox2<Fruit2>();
// 밑에서 만약 와일드 카드 사용했다면
// appleBox = new FruitBox2<Fruit2 or Apple2 or Grape2> 가 가능
FruitBox2<Apple2> appleBox = new FruitBox2<Apple2>();
fruitBox.add(new Apple2());
fruitBox.add(new Grape2());
appleBox.add(new Apple2());
appleBox.add(new Apple2());
// 위에 메서드의 매개변수에 와일드 카드를 사용하였기에 밑에 두문장 실행 가능!
System.out.println(Juicer.makeJuice(fruitBox));
System.out.println(Juicer.makeJuice(appleBox));
}
}
class FruitBox2<T extends Fruit2> extends Box2<T> {}
class Box2<T>
{
ArrayList<T> list = new ArrayList<T>();
void add(T item) { list.add(item);}
T get(int i) { return list.get(i);}
ArrayList<T> getList() { return list;}
int size() { return list.size();}
public String toString() { return list.toString();}
}