[작성일: 2023. 02. 08]
지네릭스(Generics)
- 컴파일 시 타입을 체크해 주는 기능(compile-time type check) - JDK 1.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);
// 컴파일 에러는 없으나 실행 시 형변환 에러 발생. 컴파일러의 한계.
System.out.println(list);
}
}
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(10); // list.add(new Integer(10);
list.add(20); // 지네릭스 덕분에 타입 체크가 강화됨.
list.add("30"); // 지네릭스로 인해 컴파일 에러 발생
Integer i = (Integer)list.get(2);
Integer i = list.get(2); // 지네릭스로 인해 형변환 생략 가능
System.out.println(list);
}
}
지네릭스 용어
Box<T> 지네릭 클래스. 'T의 Box' 또는 'T Box'라고 읽음.
T 타입 변수 또는 타입 매개변수.(T는 타입 문자)
Box 원시 타입(raw type), 일반 클래스
class Box<T> {} // 지네릭 클래스 선언
원시타입<타입변수>
// 대입된 타입(매개변수화 된 타입)
Box<String> b = new Box<String>();
참조변수 생성자
지네릭 타입과 다형성
- 참조 변수와 생성자의 대입된 타입은 일치해야 함.
- 지네릭 클래스 간의 다형성은 성립하나 대입된 타입은 일치해야 함.
- 매개변수의 다형성도 성립함.(자손도 대입 가능)
ArrayList<Tv> list = new ArrayList<Tv>(); // ok. 일치
ArrayList<Audio> list = new ArrayList<Tv>(); // 에러. 불일치
List<Tv> list = new ArrayList<Tv>(); // ok. 다형성. ArrayList가 List 구현
List<Tv> list = new LinkedList<Tv>(); // ok. 다형성. LinkedList가 List 구현
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv()); // ok. 자손
list.add(new Audio()); // ok. 자손
타입변수
- 지네릭 클래스 작성 시, Object 타입 대신 타입 변수(E)를 선언해서 사용함.
- 객체 생성 시, 타입 변수(E) 대신 실제 타입(Tv)을 지정(대입) - 참조변수와 생성자에 넣어주며 타입은 일치해야 함.
- 타입 변수 대신 실제 타입이 지정되면, 형변환 생략 가능해짐.
import java.util.ArrayList;
class Tv{}
public class GenericTest2 {
public static void main(String[] args) {
ArrayList<Tv> list = new ArrayList<Tv>(); // Tv타입의 객체만 저장 가능.
list.add(new Tv());
Tv t = list.get(0); // list의 첫번째 요소를 꺼냄. 형변환 필요 없음.
}
}
Iterator<E>
- 클래스를 작성할 때 Object 타입 대신 T와 같은 타입 변수를 사용
import java.util.*;
class ex {
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()) {
System.out.println(it.next().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>
- 여러 개의 타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언함.
import java.util.*;
class ex {
public static void main(String[] args) {
HashMap<String, Student> map = new HashMap<String, Student>();
// jdk 1.7부터 생성자에 타입지정 생략 가능함.
map.put("ㅇㅇㅇ"new Student("ㅇㅇㅇ", 1, 1, 100, 100, 100));
Student s = map.get("ㅇㅇㅇ"); // 형변환 하지 않아도 됨.
System.out.println(map);
}
}
class Student {
String name = "";
int ban;
int no;
int kor;
int eng;
int math;
Student(String name, int ban, int no, int kor, int eng, int math) {
this.name=name;
this.ban=ban;
this.no=no;
this.kor=kor;
this.eng=eng;
this.math=math;
}
}
제한된 지네릭 클래스
- extends로 대입할 수 있는 타입을 제한
- 인터페이스인 경우에도 extends를 사용함.
class FruitBox<T extends Fruit> {
ArrayList<T> list = new ArrayList<T>();
...
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // ok
FruitBox<toy> toyBox = new FruitBox<toy>(); // 에러. Toy는 Fruit의 자손이 아님.
interface Eatable {}
class FruitBox<T extends Eatable> {...}
✏️ 지네릭스의 제약
- 타입 변수에 대입은 인스턴스 별로 다르게 가능함.
- static 멤버에는 타입 변수 사용할 수 없음.
Box<Apple> appleBox = new Box<Apple>(); // ok. Apple 객체만 저장 가능 Box<Grape> grapeBox = new Box<Grape>(); // ok. Grape 객체만 저장 가능 class Box<T> { static T item; // 에러 static int compare(T t1, T t2) { ... } // 에러 }
- 배열 생성 시 타입 변수 사용 불가능.
- 타입 변수로 배열 선언은 가능함.
- new 연산자 다음에 T 사용 불가능.class Box<T> { T[] itemArr; // ok. T타입의 배열을 위한 참조변수 ... } T[] toArray() { T[] tmpArr = new T[itemArr.lenth]; // 에러. 지네릭 배열 생성 불가능 ... }
와일드 카드 <?>
- 하나의 참조 변수로 대입된 타입이 다른 객체 참조 가능
- 와일드 카드 사용 시, 메서드의 매개변수에도 와일드 카드 사용할 수 있음.(와일드 카드 없이는 사용 불가능)
- 서로 다른 타입이 대입된 여러 지네릭 객체를 다루기 위한 것
<? extends T> 와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super 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>(); // 에러. 대입된 타입 불일치
지네릭 메서드
- 지네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효함.)
- 클래스의 타입 매개변수<T>와 메서드의 타입 매개변수는 <T>는 별개임.
- 메서드를 호출할 때마다 타입을 대입해야 함.(대부분 생략 가능)
- 메서드를 호출할 때 타입을 생략하지 않을 때는 클래스 이름 생략 불가능.(드문 상황)
- 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 하는 것
class FruitBox<T> { // 지네릭 클래스
...
staic <T> void sort(List<T> list, Comparator<? super T> c) {
...
} // 타입문자 일치하지만 다른 타입변수임.
}
System.out.println(<Fruit>makeJuice(fruitBox)); // 에러. 클래스 이름 생략 불가
System.out.println(this.<Fruit>makeJuice(fruitBox)); // ok
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); // ok
지네릭 형변환
- 지네릭 타입과 원시 타입 간의 형변환은 가능은 하나 바람직하지 않음.(경고 발생)
- 와일드 카드가 사용된 지네릭 타입으로는 형변환 가능함.
Box<Object> objBox = null;
Box box = (box)objBox; // ok. 지네릭 타입 ➡️ 원시타입. 경고 발생
ojbBox = (box<Object>)Box; // ok. 원시타입 ➡️ 지네릭 타입. 경고 발생
ojbBox = (box<Object>)strBox; // 에러. Box<String> ➡️ Box<Object>
strBox = (box<String>)objBox; // 에러. Box<Object> ➡️ Box<String>
Box<? extends Object> wBox = new Box<String>(); // ok
Box<? extends Object> wBox = (Box<? extends Object>new Box<String>(); // ok
지네릭 타입의 제거
- 컴파일러는 지네릭 타입을 제거하고 필요한 곳에 형변환을 넣음.(이전 버전과의 호환성 때문: 안정성)
- 지네릭 타입의 경계(bound)를 제거
- <T> ➡️ Object
- 제한된 경우에는 제한된 타입으로 변경해 줌.
- 지네릭 타입 제거 후에 타입이 불일치하면 형변환을 추가해 줌.
- 와일드 카드가 포함된 경우 적절한 타입으로 형변환을 추가해 줌.
🐣 해당 게시글은 자바의 정석(남궁성 님) 영상으로 함께 공부하며 요약/정리한 글입니다.
🐣 입문 개발자가 작성한 글이므로 틀린 내용이나 오타가 있을 수 있습니다.