이 글은 유튜브 '자바의 정석 - 기초편'을 보고 정리한 글입니다.
📂content
1. 제네릭(Generics)란?
- 컴파일시 타입을 체크해 주는 기능(compile-time type check) - JDK1.5
//Tv객체만 저장할 수 있는 ArrayList를 생성
ArrayList<Tv> tvList = new ArrayList<Tv>();
tvList.add(new Tv()); //OK
tvList.add(new Audio()); //컴파일 에러. Tv 외에 다른 타입은 저장 불가
- 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌 (하나의 컬렉션에는 대부분 한 종류의 객체만 저장)
제네릭의 장점
1. 타입 안정성을 제공한다.
2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
Exception(예외) : RuntimeError(실행 중에 발생하는 에러)
RuntimeException : 프로그래머 실수로 발생하는 에러
ClassCastException : 형변환 에러
NullPointerException : 참조변수 null
IndexOutOfBoundsException : 배열범위를 벗어나는 에러
RuntimeError보다는 CompileTimeError가 낫다. 프로그램이 실행되기 전에 수정할 수 있기 때문이다.
여기서 ClassCastException을 CompileTime에 Type정보를 주어서 컴파일러가 체크할 수 있게 만들어준 것이 제네릭이다. 마찬가지로 String str = null; 보다는 Strint str = "";이 좋다. Object[] objArr = null;보다는 Object[] objArr = new Object[0]; 또는 Object[] objArr = {};가 더 좋은 코드이다.
⍟실습
package etc;
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);
}
}
컴파일할 때는 괜찮지만, 실제로 실행을 하면 에러가 난다. list에서 꺼낼 때, Object로 꺼내기 때문에 허용을 했지만, 실제로는 String값이 들어가 있다.
package etc;
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
//만약에 여러개 저장하고 싶다면, Object를 사용한다. 하지만 꺼낼 때 형변환을 해야한다.
// ArrayList<Object> list = new ArrayList<Object>();
list.add(10); //list.add(new Integer(10));
list.add(20);
list.add(30); //타입 체크가 강화됨. 제네릭 덕분에
Integer i = list.get(2); //컴파일 OK. 형변환 생략 가능
//하지만 실행하면 에러가 난다.
System.out.println(list);
}
}
제네릭 타입을 써주어야 하는 클래스가 있다.
ArrayList(일반 클래스) -> ArrayList<E> (제네릭 클래스)
2. 타입 변수
- 클래스를 작성할 때, Object타입 대신 타입 변수(E)를 선언해서 사용.
public class ArrayList extends AbstractList { //일부 생략
private transient Object[] elementData;
public boolean add(Object o) {/* 내용생략 */}
public Object get(int index) {/* 내용생략 */}
...
}
위 코드가 아래와 같이 바뀌었다. Object대신 E로 바뀌었다. JDK1.5부터 일반클래스가 제네릭클래스로 바뀌었다.
public class ArrayList<E> extends AbstractList<E> { //일부 생략
private transient E[] elementData;
public boolean add(E o) {/* 내용생략 */}
public E get(int index) {/* 내용생략 */}
...
}
E는 Element(배열의 요소)의 약자로 쓰인다.
3. 타입 변수에 대입하기
- 객체를 생성 시, 타입 변수(E) 대신 실제 타입(Tv)을 지정(대입)
//타입 변수 E 대신에 실제 타입 Tv를 대입
ArrayList<Tv> tvList = new ArrayList<Tv>();
- 타입 변수 대신 실제 타입이 지정되면, 형변환 생략가능
전자는 tvList.get(0)하면 반환값이 Object이기 때문에 형변환을 해야 했는데,
후자는 반환값이 tv라서 형변환이 불필요하다.
4. 지네릭스 용어
Box<T> 지네릭스 클래스. 'T의 Box' 또는 'T Box'라고 읽는다.
T 타입 변수 또는 타입 매개변수. (T는 타입 문자)
Box 원시 타입(raw type)
5. 지네릭 타입과 다형성
- 참조 변수와 생성자의 대입된 타입은 일치해야 한다.
class Product {}
class Tv extends Product {}
class Audio extends Product {}
ArrayList<Tv> list = new ArrayList<Tv>(); //OK. 일치
ArrayList<Product> list = new ArrayList<Tv>(); //에러. 불일치
Product와 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.
타입변수로 Product가 지정이 되면, Product의 자손이 저장이 될 수 있다.
boolean add(E e) { ... } -> boolean add(Product e) { ... } 가 되면서 Product와 그 자손 객체가 다형성에 의해 저장이 되기 때문이다.
E get(int index) { ... } -> Product get(int index) { ... }가 된다.
Product p = list.get(0);
Tv t = (Tv)list.get(1);
그래서 형변환이 필요가 없다.
⍟실습
Ex12_1
package etc;
import java.util.*;
class Product {}
class Tv extends Product {}
class Audio extends Product {}
class Ex12_1 {
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()); //public boolean add(Product e) {} => Product와 그 자손 OK
productList.add(new Audio());
tvList.add(new Tv()); //public boolean add(Tv e) {}
tvList.add(new Tv());
// tvList.add(new Audio()); //에러
printAll(productList);
// printAll(tvList); // 컴파일 에러가 발생한다.
}
public static void printAll(ArrayList<Product> list) {
for (Product p : list)
System.out.println(p);
}
}
6. 지네릭 클래스의 예
1. Iterator<E>
- 클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수를 사용
Iterator가 일반 클래스일 때, Object로 되어있었다. 근데 지네릭클래스로 바뀌면서 Object가 타입변수로 E로 변함.
옛날에는 형변환이 필요했다. 그런데 제네릭클래스로 바뀌고 난 후, E에 Student가 대입이 되면서 Student를 반환하게 되면서 형변환을 할 필요가 사라졌다.
⍟실습
Ex12_2
import java.util.*;
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);
}
} // main
}
class Student {
String name = "";
int ban;
int no;
Student(String name, int ban, int no) {
this.name = name;
this.ban = ban;
this.no = no;
}
}
2. HashMap<K,V>
- 여러 개의 타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언
역시 마찬가지로 형변환을 할 필요가 없어졌다.
⍟실습
package etc;
import java.util.*;
class Ex12_2 {
public static void main(String[] args) {
HashMap<String, Student> map = new HashMap<>(); //jdk 1.7부터 생성자에 타입지정 생략가능
map.put("자바왕", new Student("자바왕", 1,1,100,100,100));
//public Student get(Object key) {}
Student s = map.get("자바왕");
System.out.println(map);
} // main
}
class Student {
String name = "";
int ban; //반
int no; //번호
int kor;
int eng;
int math;
public 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;
}
}
7. 제한된 지네릭 클래스
- 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의 자손이 아님.
- 인터페이스인 경우에도 extends를 사용
interface Eatable {}
class FruitBox<T extends Eatavle> { ... }
implements를 쓸 것 같지만 extends를 사용한다.
⍟실습
Ex12_3
package etc;
import java.util.ArrayList;
class Fruit implements Eatable {
public String toString() { return "Fruit";}
}
class Apple extends Fruit { public String toString() { return "Apple";}}
class Grape extends Fruit { public String toString() { return "Grape";}}
class Toy { public String toString() { return "Toy" ;}}
interface Eatable {}
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>(); // 에러.
fruitBox.add(new Fruit());
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
// appleBox.add(new Grape()); // 에러. Grape는 Apple의 자손이 아님
grapeBox.add(new Grape());
System.out.println("fruitBox-"+fruitBox);
System.out.println("appleBox-"+appleBox);
System.out.println("grapeBox-"+grapeBox);
} // main
}
class FruitBox<T extends Fruit & Eatable> extends Box<T> {}
//사실 Fruit이 Eatable 인터페이스 상속을 받아서 Eatable을 받을 필요가 없다.
//2개를 상속받을 때 ','를 쓰지 않고 '&'을 쓴다.
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();}
}
- 헷갈리면 그림으로 그려보기. 위 코드의 클래스 관계는 아래와 같다.
8. 지네릭스의 제약
- 타입 변수에 대입은 인스턴스 별로 다르게 가능
Box<Apple> appleBox = new Box<Apple>(); //OK. Apple객체만 저장가능
Box<Grape> grapeBox = new Box<Grape>(); //OK. Grape객체만 저장가능
- static멤버에 타입 변수 사용 불가
class Box<T> {
static T item; //에러
static int compare(T t1, T t2) { ... } //에러
...
static은 모든 인스턴스의 공통이기 때문이다.
- 배열 생성할 때 타입 변수 사용불가. 타입 변수로 배열 선언은 가능
class Box<T> {
T[] itemArr; //OK. T타입의 배열을 위한 참조변수
...
T[] toArray() {
T[] tmpArr = new T[itemArr.length]; //에러. 지네릭 배열 생성불가
...
출처
'🎥Back > 자바의 정석' 카테고리의 다른 글
[JAVA의 정석]제네릭 형변환 (0) | 2024.01.19 |
---|---|
[JAVA의 정석]와일드카드, 제네릭 메서드 (0) | 2024.01.19 |
[JAVA의 정석]Collections 클래스, 컬렉션 클래스 요약 (2) | 2024.01.17 |
[JAVA의 정석]HashMap (0) | 2024.01.17 |
[JAVA의 정석]TreeSet (0) | 2024.01.17 |