지네릭스(Generics) 란?
- 컴파일시 타입을 체크해 주는 기능 - JDK1.5
- 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌
- 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
<형변환 에러>
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args){
ArrayList list = new ArrayList();
list.add(10);
list.add(20);
list.add("30"); // String
Integer i = (Integer) list.get(2); // 컴파일 OK
System.out.println(list);
}
}
- 컴파일러에서는 에러를 찾지 못한다. 실행 했을때 에러가 발생한다.
<형변환 필요>
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args){
ArrayList list = new ArrayList();
list.add(10);
list.add(20);
list.add(30);
Integer i = (Integer) list.get(2); // 컴파일 OK
System.out.println(list);
}
}
<형변환 불필요>
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args){
ArrayList<Integer> list = new ArrayList();
list.add(10);
list.add(20);
list.add(30);
Integer i = list.get(2); // 컴파일 OK
System.out.println(list);
}
}
타입 변수
- 클래스를 작성할 때, Object 타입 대신 타입변수(E)를 선언해서 사용
public class ArrayList<E> extends AbstractList<E> {
private transient E[] elementData;
public boolean add(E o){ }
pulbic E get(int index){ }
}
- 객체 생성시 타입 변수(E) 대신 실제 타입(Tv)를 지정(대입)
ArrayList<Tv> tvList = new ArrayList<Tv>();
- 타입 변수 대신 실제 타입이 지정되면, 형변환 생략 가능
*위의 <형변환 불필요> 소스 참고
지네릭스 용어
Box<T> : 지네릭 클래스 'T의 Box' 또는 'T Box'라고 읽는다
T : 타입 변수 또는 매개변수.(T는 타입문자)
Box : 원시타입 (raw type)
지네릭 타입과 다형성
- 참조변수와 생성자의 대입된 타입은 일치해야 한다.
ArrayList<Tv> list = new ArrayList<Tv>(); // ok
ArrayList<Product> list = new ArrayList<Tv>() // 에러!
- 지네릭 클래스간의 다형성은 성립
List<Tv> list = new ArrayList<Tv>(); // ok
List<Tv> list = new LinkedList<Tv>(): // ok
- 매개변수의 다형성도 성립
class Product{}
class Tv extends Product{}
class Audio extends Product{}
....
void method(){
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv());
list.add(new Audio());
Product p = list.get(0);
// get 함수는 E get(int idx) >> Product get(int idx) 이므로
// 형변환 필요
Tv t = (Tv)list.get(1);
}
....
ArrayList 의 add 함수에 제네릭 적용
boolean add( E e){} >> boolean add(Product e){}
Itereator<E>
- 지네릭 클래스의 하나
- it.next() 에서 형변환 불필요
package gnerics;
import java.util.ArrayList;
import java.util.Iterator;
public class GenericTest {
public static void main(String[] args){
ArrayList<Student> list = new ArrayList();
list.add(new Student("홍길동", "A"));
list.add(new Student("김철수", "B"));
list.add(new Student("김영희", "C"));
// 제네릭 클래스
Iterator<Student> it = list.iterator();
while(it.hasNext()){
// 형변환 불필요
Student student = it.next();
System.out.println(student.getName() +" : "+student.getClassName());
}
}
}
class Student{
String name;
String className;
public Student(String name, String className) {
this.name = name;
this.className = className;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
HashMap<K,V>
- 여러 개의 타입 변수가 필요한 경우 콤마(,)를 구분자로 선언
package gnerics;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class GenericTest2 {
public static void main(String[] args){
Map<Integer, Student> map = new HashMap<>();
map.put(1, new Student("홍길동", "A"));
map.put(2, new Student("김철수", "B"));
map.put(3, new Student("김영희", "C"));
Iterator<Integer> iterator = map.keySet().iterator();
while (iterator.hasNext()){
// 형변환 불필요
Student student = map.get(iterator.next());
System.out.println(student.getName() +" : "+student.getClassName());
}
}
}
class Student{
String name;
String className;
public Student(String name, String className) {
this.name = name;
this.className = className;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
제한된 지네릭 클래스
- extends로 대입할 수 있는 타입을 제한
- 인터페이스의 경우도 extends 사용
class FruitBox<T extends Fruit>{ // Fruit의 자손만 티입으로 지정 가능
ArrayList<T> list = new ArrayList<T>();
...
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // ok
FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러 Toy는 Fruit의 자손이 아니다
- 타입 변수에 대입은 인스턴스 별로 다르게 가능
Box<Apple> appleBox = new Box<Apple>(); // ok
Box<Grape> grapeBox = new Box<Grape>(); // ok
- static 멤버에 타입 변수 사용 불가
class Box<T> {
static T item; // 에러
static int comparee(T t1, T t2){} // 에러
}
- 타입 변수로 배열 선언은 가능하나 생성할 때는 사용불가
class Box<T> {
T[] itemArr; // ok
...
T[] toArray(){
T[] tmpArr = new T[itemArr.length]; // 에러! 제네릭으로 배열 생성 불가
...
}
}
와일드 카드 <?>
- 하나의 참조변수로 대입된 타입이 다른 객체를 참조 가능
- 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭 객체를 다루기 위한 것
<? extends T> 와일드 카드 상한 제한. T와 그 자손들만 가능. T 밑으로 가능
<? super T> 와일드 카드 하한 제한. T와 그 조상들만 가능. T 위로 가능
<?> 제한 없음. 모든 타입이 가능 <? extends Object> 와 동일
ArrayList< ? extends Product> list = new ArrayList<Tv>(); // ok
ArrayList< ? extends Product> list = new ArrayList<Audio>(); // ok
ArrayList<Product> list = new ArrayList<Tv>(); // 에러! 대입된 타입 불일치
- 메서드의 매개변수에 와일드 카드를 사용
static Juice makeJuice(FruitBox<? extends Fruit> box){
String tmp ="";
for(Fruit f : box.getList())
tmp += f+ " ";
return new Juice(tmp);
}
...
makeJuice(new FruitBox<Fruit>());
makeJuice(new FruitBox<Apple>());
지네릭 메서드
- 지네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)
- 메서드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 하는 것.
static <T> void sort(List<T> list, Comparator<? super T> c)
- 클래스의 타입 매개변수<T>와 메서드의 타입 매개변수 <T>는 별개
class FruitBox<T> { // 1번
...
static <T> void sort(List<T> list, Comparator<? super T> c){ // 2번
....
}
}
*** 1번 라인의 T와 2번라인의 T와는 다른 T
1번은 클래스의 타입 매개변수
2번은 메서드의 타입 매개변수
지역 변수가 클래스 변수보다 우선 하는 거 처럼
메서드의 타입 매개변수가 클래스의 타입 매개변수보다 우선
- 메서드를 호출할 때마다 타입을 대입해야(대부분 생략 가능)
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
...
Juicer.<Fruit>makeJuice(fruitBox);
Juicer.<Apple>makeJuice(Apple);
...
static <T extends Fruit> Juice makeJuice(FruitBox<T> box){
...
}
** 메서드 makeJuice 에서는 선언부에 있는 <T extends Fruit>이 영향을 받아
메서드 인풋 타입의 제네릭에도 <T extends Fruit> 을 따라야 한다.
- 메서드를 호출할 때 타입을 생략하지 않을 때는 클래스 이름 생략 불가
<Fruit>makeJuice(fruitBox) // 에러 클래스 이름 생략 불가
this.<Fruit>makeJuice(fruitBox) // ok
Juicer.<Fruit>makeJuice(fruitBox) // ok
static <T extends Fruit> Juice makeJuice(FruitBox<T> box){
...
}
static Juice makeJuice(FruitBox<? extends Fruit> box){
...
}
지네릭 타입의 형변환
- 지네릭 타입과 원시 타입 간의 형변환은 바람직 하지 않다.(가능은 하지만 경고발생)
Box<Object> objBox = null;
Box box = (Box)objBox; // ok 지네릭 > 원시. 경고 발생
objBox = (Box<Object>)box; // ok 원시 > 지네릭. 경고 발생
Box<String> strBox = null;
objBox = (Box<Object>)strBox; // 에러 Box<Object> -x-> Box<String>
strBox = (Box<String>)objBox; // 에러 Box<String> -x-> Box<Object>
- 와일드 카드가 사용된 지네릭 타입으로는 형변환 가능
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(); // ok 위 문장과 동일
지네릭 타입의 제거
- 컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.
(1) 지네릭 타입의 경계(bound)를 제거
컴파일 후에는 모두 클래스로 바뀌어진다. 하위 호환성 때문에 ! JDK 1.5 이전 버전도 돌아가야 하기 때문에
// 소스
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) 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가
static Juice makeJuice(FruitBox<? extends Fruit> box){
String tmp ="";
for(Fruit f : box.getList())
tmp += f+ " ";
return new Juice(tmp);
}
static Juice makeJuice(FruitBox box){
String tmp ="";
Interator it = box.getList().iterator();
while(it.hasNext()){
tmp += (Fruit)it.next() +" ";
}
return new Juice(tmp);
}
'개발' 카테고리의 다른 글
Java - 애너테이션 (0) | 2021.06.27 |
---|---|
Java - 열거형(enum) (0) | 2021.06.27 |
Java - Collections의 유용한 static 메서드 (0) | 2021.06.06 |
Java - HashMap과 Hashtable (0) | 2021.06.06 |
Java - TreeSet (0) | 2021.06.06 |