제네릭스(Generics)
: 제네릭스란 컴파일시 타입을 체크해주는 기능이다. -> 타입의 안정성을 높이고 형변환의 번거로움을 줄여준다.
예를들어 아래와 같이 ArrayList를 생성할 때, 저장할 객체의 타입을 저장해주면 지정한 타입 외에 다른 타입의 객체가 저장되면 에러가 발생한다.
//Tv객체만 저장할 수 있는 ArrayList를 생성.
ArrayList<Tv> tvList= new ArrayList<Tv>();
tvList.add(new Tv()); //Ok.
tvList.add(new Audio()); //컴파일 에러. Tv외에 다른 타입은 저장 불가.
→ 그리고 저장된 객체를 꺼낼 때는 형변환할 필요가 없어서 편리하다. 이미 어떤 타입의 객체들이 저장되어 있는지 알고 있기 때문이다.
- 타입 변수
: 클래스를 작성할 때, Object타입 대신 타입변수<E>를 선언해 사용한다.
ArrayList클래스의 선언에서 클래스 이름 옆의 '<>'안에 있는 E를 '타입 변수(type variable)'라고 하며, 일반적으로는 'Type'의 첫 글자를 따서 T를 사용한다. 타입변수로 반드시 T를 사용해야 하는 것은 아니며, T가 아닌 다른 것을 사용해도 된다. ArrayList<E>의 경우, 'Elemant(요소)'의 첫 글자를 따서 타입 변수의 이름으로 E를 사용한다.
public class ArrayList<E> extends AbstractList<E> {
private transient E[] elementData;
public boolean add(E o) {}
publci E get(int index) {}
...
}
타입 변수가 여러개인 경우에는 Map<K, V>와 같이 콤마','를 구분자로 나열하면 된다. K는 Key(키)를 의미하고, V는 Valeu(값)을 의미한다.
- 타입 변수에 대입하기
: ArrayList와 같은 제네릭 클래스를 생성할 때는 다음과 같이 참조변수와 생성자에 타입 변수 E대신에 Tv와 같은 실제 타입을 지정해줘야 한다.
ArrayList<Tv> tvList=new ArrayList<Tv>(); //타입 변수 E 대신 실제 타입 Tv를 대입
→ 이때, 타입 변수 E 대신 지정된 타입 Tv를 '대입된 타입'이라고 한다.
- 제네릭스 용어
- 지네릭 타입과 다형성
: 지네릭 클래스의 객체를 생성할 때, 참조변수에 지정해준 지네릭 타입과 생성자에 지정해준 지네릭 타입은 일치해야 한다.
import java.util.*;
class Product{}
class Tv extends Product{}
class Audio extends Product{}
class JavaJungsuk_Generics {
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>(); //OK. 다형성
productList.add(new Tv());
productList.add(new Audio());
tvList.add(new Tv());
tvList.add(new Tv());
printAll(productList);
}
public static void printAll(ArrayList<Product> list) {
for(Product p:list)
System.out.println(p);
}
}
결과
Tv@15db9742
Audio@6d06d69c
→ 클래스 Tv와 Product가 서로 상속관계에 있어도 일치해야 하기 때문에
ArrayList<Product> list = new ArrayList<Tv>();는 에러가 난다.
- Iterator<E> 예제
import java.util.*;
class JavaJungsuk_Generics_Iterator {
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();
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;
}
}
결과
자바왕
자바짱
홍길동
→ Iterator에 제네릭스를 적용하는 예제이다. 지네릭스를 이용해 선언해주기만 하면 된다.
- HashMap<K, V>
: HashMap처럼 데이터를 키(key)와 값(value)의 형태로 저장하는 컬렉션 클래스는 지정해줘야 할 타입이 두개이다.
키의 타입이 String이고 저장할 값의 타입이 Student인 HashMap을 생성하려면 다음과 같이 한다.
HashMap<String, Student> map=new HashMap<String, Student>(); //생성
map.put("자바왕", new Student("자바왕", 1, 1, 100, 100, 100)); //데이터 저장
- 제한된 지네릭 클래스
: 지네릭 타입에 'extends'를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.
import java.util.ArrayList;
class Fruit implements Eatable {
public String toString() {return "Fruit";}
}
class Apple extends Fruit {
public String toString() {
return "Apple";
}
}
class Grape exnteds Fruit {
public String toString() {
return "Grape";
}
}
class Toy {
public String toString() {
return "Toy";
}
}
interface Eatable {}
class JavaJungsuk_Generics_limitedGenercis {
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());
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]
→ 한 종류의 타입만 담을 수 있지만, Fruit클래스의 자손들만 담을 수 있다는 제한이 더 추가된 것이다.
→ 클래스가 아니라 인터페이스를 구현해야 한다는 제약이 필요하다면 이때도 'implements'가 아닌 'extends'를 사용해야 한다.
'프로그래밍 > Java' 카테고리의 다른 글
Java_열거형(enum)_열거형의 정의와 사용, 열거형의 조상, 열거형에 멤버 추가 (0) | 2023.01.23 |
---|---|
Java_제네릭스·지네릭스(2)_제네릭스의 제약, 와일드 카드, 제네릭 메서드, 제네릭 타입의 형변환 (0) | 2023.01.19 |
Java_컬렉션프레임웍(7)_Collections의 메서드(동기화, 변경불가, 싱글톤, 단일 컬렉션), 컬렉션 클래스 정리 (0) | 2023.01.11 |
Java_컬렉션프레임웍(6)_HashMap과 Hashtable (0) | 2023.01.08 |
Java_컬렉션프레임웍(5)_TreeSet (0) | 2023.01.04 |