제네릭(Generics)
Java 5때 도입된 제네릭은 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다.
객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.
// Tv객체만 저장할 수 있는 ArrayList를 생성
ArrayList<Tv> tvList = new ArrayList<Tv>();
tvList.add(new Tv());// OK
tvList.add(new Audio());// 컴파일 에러. Tv 외에 다른 타입은 저장불가
이미 어떤 타입의 객체들이 저장되어 있는지 알고 있기 때문에 저장된 객체를 꺼낼 때는 형변환할 필요가 없어서 편리하다.
제네릭의 장점
- 잘못된 타입이 들어오는 것을 컴파일 단계에서 방지하므로 타입 안정성을 제공한다.
- 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
- 비슷한 기능을 지원할 때 코드의 재사용성이 높아진다.
타입변수
선언에서 클래스 이름 옆의 ‘<>’안에 있는 E를 ‘타입 변수(type variable)’라고 하며, 일반적으로는 ‘Type’의 첫 글자를 따서 T를 사용한다.
타입 변수로 반드시 T를 사용해야 하는 것은 아니며, T가 아닌 다른 것을 사용해도 된다.
public class ArrayList<E> extends AbstractList<E> {// 일부 생략private transient E[] elementData;
public boolean add(E o) {/* 내용생략 */ }
public E get(int index) {/* 내용생략 */ }
...
}
타입 변수가 여러 개인 경우에는 Map<K, V>와 같이 콤마’,’를 구분자로 나열하면 된다. K는 Key(키)를 의미하고, V는 Value(값)을 의미한다. 이들은 기호의 종류만 다를 뿐 ‘임의의 참조형 타입’을 의미한다.
제네릭이 도입되기 이전 코드의 문제점
- 제네릭을 사용하기 이전에는 Object를 사용하여 모든 객체를 받아들였기 때문에 값을 사용하기 위해서 형변환이 필요했다.
public class Product{
private Object obj;
public void set(Object obj) {this.obj = obj;}
public Ovject get() {return obj;}
}
public class Furniture{
}
public class Example{
public static void main(String[] args){
Product p = new Product();
p.set("TV"); // String -> Object 자동 타입 변환
String name = (String)p.get(); // Object -> String 강제 타입 변환
p.set(new Furniture()); // Furniture -> Object 강제 타입 변환
Furniture f = (Furniture)p.get(); // Object -> Furniture 강제 타입 변환'
}
}
- 프로그래머의 실수가 컴파일 과정에서 발견되지 않음.
Box aBox = new Box();
Box bBox = new Box();
// 사과와 오렌지 클래스가 아닌 '문자열'을 담음
aBox.set("Apple");
bBox.set("Orange");
System.out.println(aBox.get());
System.out.println(bBox.get());
제네릭이 도입되기 이전 ArrayList의 소스
public class ArrayList extends AbstractList {// 일부 생략private transient Object[] elementData;
public boolean add(Object o) {/* 내용 생략 */ }
public Object get(int index) {/* 내용 생략 */ }
}
기존에는 다양한 종류의 타입을 다루는 메서드의 매개변수나 리턴타입으로 Object타입의 참조변수를 많이 사용했고, 그로 인해 형변환이 불가피했지만, 이젠 Object타입 대신 원하는 타입을 지정하기만 하면 되는 것이다.
타입변수 대입
지네릭 클래스를 생성할 때는 다음과 같이 참조변수와 생성자에 타입 변수 E 대신에 Product와 같은 실제 타입을 지정해주어야 한다.
// 타입 변수 E 대신에 실제 타입 Product를 대입
ArrayList<Product> tvList = new ArrayList<Product>();
이때, 타입 변수 E 대신 지정된 타입 Product를 ‘대입된 타입(parameterized type)’이라고 한다.
public class ArrayList<E> extends AbstactList<e> {// 일부생략private transient E[] elementData;
public boolean add(E o) {/* 내용 생략 */ }
public E get(int index) {/* 내용 생략 */ }
...
}
타입이 대입되고 나면, ArrayList의 선언에 포함된 타입 변수 E가 아래와 같이 지정된 타입으로 바뀐다고 생각하면 된다.
public class ArrayList extends AbstractList {// 일부 생략private transient Tv[] elementData;
public boolean add(Tv o) {/* 내용 생략 */ }
public Tv get(int index) {/* 내용 생략 */ }// Object가 아닌 Tv를 반환
...
}
위 코드의 get()이 Object가 아닌 Product를 반환하게 되므로 형변환이 필요 없게 된다.
제네릭 용어
Box<T> 지네릭 클래스. ‘T의 Box’또는 ‘T Box’라고 읽는다.
T 타입 변수 또는 타입 매개변수.(T는 타입 문자)
Box 원시 타입(raw type)
타입 문자 T는 메서드의 매개변수와 유사한 면이 있기 때문에, 지네릭 클래스의 Box<T>의 타입 변수 또는 타입 매개변수라고 한다.
아래와 같이 타입 매개변수에 타입을 지정하는 것을 ‘지네릭 타입 호출’이라고 하고, 지정된 타입 ‘String’을 ‘매개변수화된 타입(parameterized type)’이라고 한다.
제네릭과 다형성
제네릭 클래스의 객체를 생성할 때, 참조변수에 지정해준 지네릭 타입과 생성자에 지정해준 지네릭 타입은 일치해야 한다.
ArrayList<Tv> list = new ArrayList<Tv>(); // OK. 일치
ArrayList<product> list = new ArrayList<Tv>(); // 에러. 불일치
...
class Product {}
class Tv extends Produce {}
class Audio extends Produce {}
제네릭 타입이 아닌 클래스의 타입 간에 다형성을 적용하는 것은 가능하다. 이 경우에도 제네릭 타입은 일치해야한다.
제네릭 클래스의 타입 인자 제한
// 인스턴스 생성 시 타입 인자로 Number 또는 이를 상속하는 클래스만 올 수 있음
class Box<T extends Number>{...}
class Box<T extends Number>{
private T ob;
public void set(T o){
ob = o;
}
public T get(){
return ob;
}
}
public static void main(String[] args){
Box<Integer> iBox = new Box<>();
iBox.set(24);
Box<Double> dBox = new Box<>();
dBox.set(5.97);
...
}
제네릭 메소드의 정의
클래스 전부가 아닌 메소드 하나에 대해 제네릭으로 정의
public <T> T genericMethod(T o) { // 제네릭 메소드
...
}
[접근 제어자] <제네릭타입> [반환타입] [메소드명]([제네릭타입] [파라미터]) {
// 텍스트
}
class BoxFactory {
public static <T> Box<T> makeBox(T o){
Box<T> box = new Box<T>(); // 상자 생성
box.set(o); // 전달된 인스턴스를 상자에 담음.
return box; // 상자를 반환
}
}
제네릭 메소드의 T는 메소드 호출 시점에 결정
Box<String> sBox = BoxFacotry.<String>makeBox("Sweet");
Box<Double> dBox = BoxFacotry.<Double>makeBox("7.59"); // 7.59에 대해 오토 박싱 진행
타입 인자 생략 가능
Box<String> sBox = BoxFactory.makeBox("Sweet");
Box<Double> dBox = BoxFacotry.makeBox("7.59"); // 7.59에 대해 오토 박싱 진행
참조
남궁성 저 - 자바의 정석 기초편
자바 [JAVA] - 제네릭(Generic)의 이해
정적언어(C, C++, C#, Java)을 다뤄보신 분이라면 제네릭(Generic)에 대해 잘 알지는 못하더라도 한 번쯤은 들어봤을 것이다. 특히 자료구조 같이 구조체를 직접 만들어 사용할 때 많이 쓰이기도 하고
st-lab.tistory.com
'회고록 > Archive' 카테고리의 다른 글
retrospect: 멋쟁이 사자처럼 백앤드 스쿨 2022.11.01 회고 (1) | 2022.12.02 |
---|---|
retrospect: 멋쟁이 사자처럼 백앤드 스쿨 2022.10.18 회고 (0) | 2022.12.02 |
docs: 멋쟁이 사자처럼 백앤드 스쿨 2기 수업 일지 READ ME(2022-11-13) (7) | 2022.11.13 |
[Java] 대용량 데이터 분석 - 4 (대한민국 인구 유동(전입, 전출) 데이터 분석 & 처리) (0) | 2022.10.11 |
[Java] 대용량 데이터 분석 - 3 (0) | 2022.10.11 |