* Generics(지네릭스)
- 컴파일 시 타입을 체크해주는 기능(compile-time type check) --> 런타임 에러(ClassCastException)를 컴파일 에러로 끌고 올 수 있음!
//Tv 객체만 자정할 수 있는ArrayList 생성
ArrayList<Tv> tvList = new ArrayList<Tv>();
tvList.add(new Tv()); // Ok
tvList.add(new Audio()); // 컴파일 에러.
- 형 변환의 번거로움을 줄여줌.
ArrayList list = new ArrayList;
list.add(1);
list.add(2);
Integer i = (Integer)list.get(1); // 꼭 형변환 필요, Object type을 반환하므로.
---------------------------------------------
ArrayList<Integer> list = new ArrayList<Integer>;
list.add(1);
list.add(2);
Integer i = list.get(1); // 형변환 필요 없음
** Generics 개념이 도입된 이후 (JDK 1.5 이후) Generics가 필요한 곳엔 (클래스에서 선언돼 있으면?) 반드시 적어줘야 함.
Object 객체를 넣는다고 해도 Object를 적어줘야 함 (그래서 ArrayList 선언할 때 밑줄이 있었구나!)
* 타입 변수
- Generics 클래스 작성 시, Object타입 대신 타입 변수(E) 선언해 사용
public class ArrayList extends AbstractList{ // 일반 클래스
private transient Object[] elementData;
public boolean add(Object o){...}
public Object get(int index){...}
...
}
---------------------------------------------- // 위 아래 코드 내용은 동일(Object를 타입변수 E로 선언)
public class ArrayList<E> extends AbstractList<E>{ // 지네릭 클래스
private transient E[] elementData;
public boolean add(E o){...}
public E get(int index){...}
...
}
- 이후 객체 생성 시, 타입 변수(E) 대신 실제 타입을 지정해야 함 (대입)
참조 변수와 생성자에 지정해야 하며 두 개가 같아야 함. (와일드카드 사용하면 다를 수 있음)
- 지네릭스 용어 : 지네릭 클래스, 원시 타입, 타입 변수

- 지네릭 타입 호출 : Box<String> b = new Box<String>();
String은 언제든지 변경 가능!
-지네릭 타입과 다형성
지네릭 클래스 간, 매개변수의 다형성은 성립
// 지네릭 클래스 간의 다형성
List<Tv> list = new ArrayList<Tv>(); //Ok
// 매개변수의 다형성
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv()); // Tv 가 Product의 자손이면 Ok
list.add(new Audio()); // Audio가 Product의 자손이면 Ok
// 대신 get할 때 Product형을 리턴하므로, 이걸 Tv로 받으려면 형변환 필요
Tv tv = list.get(1); // 오류! get으로 불러오는건 Product이므로.
Tv tv = (Tv)list.get(1); // Ok
* 지네릭 클래스 예시
- Iterator<E>
public interface Iterator{ // 일반 클래스
boolean hasNext();
Object next();
void remove();
}
Iterator it = list.iterator();
while(it.hasNext()){
Student s = (Student)it.next(); // 형변환 O
...
}
-----------------------------------
public interface Iterator<E>{ // 지네릭스 클래스
boolean hasNext();
E next();
void remove();
}
Iterator<Student> it = list.iterator();
while(it.hasNext()){
Student s = it.next(); // 형변환 X
...
}
- HashMap<K,V>
여러 개의 타입 변수가 필요한 경우 콤마를 구분자로 선언.
HashMap의 key, value의 타입 각각 지정 가능.
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,100));
* 제한된 지네릭 클래스
- extends로 타입 변수를 제한할 수 있음 (인터페이스의 경우에도 extends 사용! implements 사용 X)
+ 인터페이스를 extends 할 때 콤마(,)가 아니라 &로 작성 필요
class FruitBos<T extends Fruit>{
ArrayList<T> list = new ArrayList<T>();
...
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // Ok
FruitBox<Toy> toyBos = new FruitBox<Toy>(); // 컴파일 에러! Toy는 Fruit의 자손이 아님
* 지레닉스의 제약
- 타입 변수에 대입은 인스턴스 별로 다르게 가능.
1. static멤버에 타입 변수 사용 불가!
static 멤버는 모든 인스턴스에 공통인데, 인스턴스마다 다른 타입 변수 사용 불가.
2. 배열, 객체 생성할 때 타입 변수 사용불가.
class Box<T>{
T[] itemArr; // Ok T타입의 배열을 위한 참조변수
...
T[] toArray(){
T[] tmpArr = new T[itemArr.length]; // 에러! 타입 변수로 배열, 객체 생성은 불가.
}
}
* 와일드카드 <?>
- 하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능 --> 타입 변수가 일치하지 않아도 Ok!
ArrayList<? extends Product> list = new ArrayList<Tv>(); //Ok
ArrayList<? extends Product> list = new ArrayList<Audio>(); //Ok
ArrayList<Product> list = new ArratList<Tv>(); // 에러!
<? extends T> : 와일드카드의 상한 제한, T와 그 자손들만 가능
<? super T> : 와일드카드의 하한 제한, T와 그 조상들만 가능
<?> : 제한 없음. 모든 타입이 가능
- 메서드의 매개변수에 와일드카드 사용 가능
와일드카드 사용으로 매개변수에 data 타입이 T의 자손인 경우 다 들어올 수 있게 됨.
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<Fruit>()));
System.out.println(Juicer.makeJuice(new FruitBox<Apple>())); // 둘 다 가능!
* 지네릭 메서드
- 지네릭 타입이 선언된 메서드 (타입 변수는 메서드 내에서만 유효)
static <T> void sort(List<T> list, comparatoe<? super T> c)
클래스 타입 변수와 메서드 타입 변수의 이름이 같아도, 메서드에선 메서드 타입 변수를 사용함! 즉 둘이 다를 수 있음.
- 메서드를 호출할 때마다 타입을 대입해야. (대부분 생략 가능)
static <T extends Fruit> Juice makeJuice(FruitBox<T> box){
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
* 지네릭 타입의 형변환
- 지네릭 타입과 원시 타입 간의 형변환은 바람직 하지 않다.
Box<Object> objBox = null;
Box box = (Box)objBox; // 에러는 없으나, 경고 발생 (지네릭 타입 --> 원시 타입)
objBox = (Box<Object>)box; // 에러는 없으나, 경고 발생 (원시 타입 --> 지네릭 타입)
- 서로 다른 타입의 지네릭 타입은 형변환 X
obj Box = (Box<Object>)strBox; // 에러
- 와일드카드가 사용된 지네릭 타입은 형변환 가능!
Box<Object> objBox = (Box<Object>)new Box<String>(); // 불가능
Box<? extends Object> objBox = (Box<? extends Object>)new Box<String>(); // 가능
Box<? extemds Object> objBox = new Box<String>(); // 위 문장과 동일
* 지네릭 타입의 제거
- 컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환 넣는다. + 생략한 형변환도 추가 + 와일드카드 변경.
코드 작성할 때 Object --> <T>로 바꾸면, 컴파일러가 다시 <T> --> Object로 변경한다. (하위 호환성 때문!)
'코딩 > JAVA 공부' 카테고리의 다른 글
| [JAVA] 자바의 정석 _ 쓰레드 (0) | 2022.10.20 |
|---|---|
| [JAVA] 자바의 정석 _ 열거형(enum) / 애너테이션 (0) | 2022.10.18 |
| [JAVA] 자바의 정석 _ HashMap & Hashtable, Collections (0) | 2022.10.16 |
| [JAVA] 자바의 정석 _ HashSet, TreeSet (0) | 2022.10.16 |
| [JAVA] 자바의 정석 _ Arrays (0) | 2022.10.15 |