지네릭스(Generics)란?
- 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일시 타입을 체크해 주는 기능(compile-time type check) – JDK1.5
- 다룰 객체의 타입을 미리 명시해줌으로써 번거로운 형변환을 줄여준다.
- 모든 클래스가 Generic클래스로 바뀐 것은 아니고, 클래스 안에 Object 타입이 있는 것만 Generic클래스로 바뀜!(Object는 다양한 타입의 객체들을 다룰 수 있기 때문에)
예) ArrayList(일반 클래스) -> ArrayList<E>(지네릭 클래스)
(ArrayList는 Object 배열을 가지고 있어서 모든 종류의 객체 저장 가능)
지네릭스의 장점
1) 타입 안정성을 제공한다.(다를 객체의 타입을 미리 명시해줌으로써)
2) 타입체크와 형변환을 생략할 수 있으므로 코드가 간결 해진다.
-> 지네릭스를 추가함으로써 ClassCastException 예외를 막아준다.
예제1) 컴파일은 가능하지만 Runtime시 발생에러
ArrayList list = new ArrayList();
list.add(10);
list.add(20);
list.add("30");
Integer i = (Integer)list.get(2); // 컴파일 OK
System.out.println(list); // ClassCastException "형변환에러" -> 실행시 발생에러
Exception: Runtime Error 실행 중 발생 에러
RuntimeException 프로그래머 실수로 발생 에러
실행 중에 발생하는 에러보다 컴파일 할 때 나는 에러(complie time 에러)가 더 나음(프로그램 실행 전에 수정 가능하기 때문이다!)
-> RuntimeException을 가능하면 compile time에러로 끌어오기!
1번 String str1 = null; | 2번 String str2 = “ “; 빈문자열 |
1번보다 2번이 더 좋음 ex) str1.length( ) -> NullPointerException 발생 / str2.length( ) -> 0반환 |
|
1번 Object [ ] obj = null; | 2번 Object[ ] obj = new Object[0]; |
1번보다 2번이 더 좋음 |
-> 실행 중에 NullPointerException이 덜 생기게 하기 위해서! 이렇게 코드를 작성!
예제2) 지네릭스 추가
// ArrayList list = new ArrayList(); // JDK1.5이전. 지네릭스 도입이전
ArrayList<Integer> list = new ArrayList<Integer>(); // JDK1.5이후
list.add(10); // list.add(new Integer(10));
list.add(20);
list.add(30);
// list.add("30"); 에러 Integer 형으로 선언해서 문자열 xx, 타입체크가 강화됨. 지네릭스 덕분에
Integer i = list.get(0); // 형변환 생략 가능, 앞에서 지네릭스로 Integer 선언해서!
System.out.println(i); // 10
System.out.println(list); // [10,20,30]
지네릭스 용어
Box<T> | 지네릭 클래스(타입 변수 T선언) ‘T의 Box’ 또는 ‘T의 Box’라고 읽는다. |
T | 타입 변수 또는 타입 매개변수(T는 타입 문자) 새로운 객체를 만들 때마다 새로운 타입을 줄 수 있음. |
Box | 원시 타입(raw type), 일반 클래스일 때의 타입 |
타입 변수
- 지네릭 클래스를 작성할 때, Object타입 대신 타입 변수(E)를 선언해서 사용
-> 타입변수 E(Element)를 선언하고 Object를 다 E로 변환!, T로 써도 가능!
타입 변수에 대입하기
- 객체 생성시. 타입 변수(E) 대신 실제 타입(Tv)을 지정(대입)
// 타입 변수 E 대신에 실제 타입 Tv를 대입
지네릭 타입과 다형성
- 참조 변수와 생성자의 대입된 타입은 일치해야 한다.
- 지네릭 클래스간의 다형성은 성립(여전히 대입된 타입은 일치해야)
- 매개변수의 다형성도 성립
-> Tv와 Audio는 Product의 자손이어서(자손의 객체도) 가능!
Product p = list.get(0); -> 반환타입 Product 타입이 일치해서 형변환 불필요
Tv t = (Tv)list.get(1); -> 반환타입이 Product 이기 때문에 Tv는 형변환 해야함
예제)
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>();
// List<Tv> tvlist = new ArryaList<Tv>(); // Ok. 다형성
// ArrayList<Product> tvlist = new ArrayList<Tv>(); // 에러 타입 불일치
productlist.add(new Tv()); // public boolean add(Product e) {
productlist.add(new Audio()); // Product와 그 자손도 ok
tvlist.add(new Tv()); // public boolean add(Tv e) {
// tvlist.add(new Audio()); // 에러 Tv, 또는 그 자손만 ok
printAll(productlist);
// printAll(tvlist); // 컴파일 에러 발생 printAll의 타입이 Product이기 때문
}
public static void printAll(ArrayList<Product> list) {
for(Product p:list)
System.out.println(p);
}
}
Iterator <E>
- 클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수를 사용
Iterator 일반 클래스 | Iterator 제네릭 클래스 |
public interface Iterator { Boolean hasNext( ); Object next( ); void remove( ); } |
public interface Iterator<E> { Boolean hasNext( ); E next( ); void remove( ); } |
Iterator it = list.iterator( ); while(it.hasNext( )) { Student s = (Student)it.next( ); } |
Iterator<Student> it = list.iterator( ); while(it.hasNext( )) { Student s = } |
예제1)
import java.util.*;
class Student {
String name = "";
int ban;
int no;
Student(String name, int ban, int no) {
this.name = name;
this.ban = ban;
this.no = no;
}
}
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 = (Student)it.next(); // 지네릭스를 사용하지 않으면 형변환 필요.
// Student s = it.next();
// System.out.println(s.name);
System.out.println(it.next().name); // 위에 두줄을 한 줄로 줄일 수 있다.
}
}
}
HashMap<K,V>
- 여러 개의 타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언
- K는 key V는 value
예제2) 예제1번을 약간 변환
import java.util.*;
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;
}
}
public class Ex12_2 {
public static void main(String[] args) {
HashMap<String, Student> map = new HashMap<>(); // JDK1.7부터 생성자에 타입지정 생략가능
map.put("자바왕", new Student("자바왕", 1, 1, 80, 100, 100));
// Student s = (Studnet)map.get("자바왕");
Student s = map.get("자바왕");
System.out.println(s.kor); // 80
}
}
제한된 지네릭 클래스
- extends로 대입할 수 있는 타입을 제한
- 인터페이스인 경우에도 extends를 사용
예제)
import java.util.ArrayList;
interface Eatable {}
class Fruit implements Eatable { // Eatable인터페이스를 구현한 Fruit 클래스
public String toString() { return "Fruit"; }
}
class Apple extends Fruit { public String toString() {return "Apple";}} // Fruit클래스의 자손
class Grape extends Fruit { public String toString() {return "Grape";}} // Fruit클래스의 자손
class Toy { public String toString() { return "Toy"; }}
//(1) Fruit의 자손 & Eatable을 구현한 클래스만, Box의 자손 클래스
class FruitBox<T extends Fruit & Eatable> extends Box<T> {}
class Box<T> {
ArrayList<T> list = new ArrayList<T>(); // item을 저장할 list
void add(T item) { list.add(item); } // 박스에 item을 추가
T get(int i) { return list.get(i); } // 박스에서 item을 꺼낼때
int size() { return list.size(); }
public String toString() { return list.toString(); }
}
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>(); // 에러 Fruit의 자손이 아님
Box<Toy> toyBox = new Box<Toy>(); // Box에서는 가능함(Box는 제약이 없음)
fruitBox.add(new Fruit());
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
grapeBox.add(new Grape());
toyBox.add(new Toy());
// appleBox.add(new Fruit()); // 에러. Fruit은 apple의 조상, xx
// appleBox.add(new Grape()); // 에러. Grape는 apple의 자손 xx
System.out.println(fruitBox); // [Fruit, Apple, Grape]
System.out.println(appleBox); // [Apple]
System.out.println(grapeBox); // [Grape]
System.out.println(toyBox); // [Toy]
}
}
(1)
Fruit이 자손이면서 Eatable 인터페이스를 구현한 클래스만 들어올 수 있음, 인터페이스 쓸 때는 &(사실 Fruit이 이미 Eatable을 구현하고 있어서 생략가능)
-> class FruitBox<T extends Fruit> extends Box<T> { }
지네릭스의 제약
- 타입 변수에 대입은 인스턴스 별로 다르게 가능
1) static 멤버에 타입 변수 사용 불가
-> 모든 객체에 대해 동일하게 동작해야하는 static 멤버에 타입 변수 T를 사용할 수 없다. T는 인스턴스변수로 간주되기 때문.(static멤버는 인스턴스변수를 참조할 수 없음)
-> static멤버는 타입 변수에 지정된 타입, 즉 대입된 타입의 종류에 관계없이 동일해야 하기 때문
2) 배열 생성할 때 타입 변수 사용불가. 타입 변수로 배열 선언은 가능
-> 객체 생성이나, 배열 생성할 때 new T( ), new T[ ] 이렇게 사용할 수 없음
-> new 연산자는 컴파일 시점에 타입 T를 정확히 알아야 하기 때문에 new(연산자) + T 는 안된다.
-> instanceof연산자도 new 연산자와 같은 이유로 T를 피연산자로 사용할 수 없다.
'멀티캠퍼스 풀스택 과정 > Java의 정석' 카테고리의 다른 글
자바의 정석9-3 열거형(enum) (0) | 2022.01.12 |
---|---|
자바의 정석9-2 와일드 카드, 지네릭 메서드 (1) | 2022.01.12 |
자바의 정석8-6 HashMap, Hashtable, Collections 요약 (0) | 2022.01.10 |
자바의 정석8-5 HashSet, TreeSet (0) | 2022.01.10 |
자바의 정석8-4 Arrays, Comparator와 Comparable (0) | 2022.01.10 |