제네릭
작성일
제네릭이란?
클래스나 메소드에서 사용할 내부 데이터 타입을 외부에서 지정하는 기법
제네릭 클래스
클래스 선언에 타입 매개변수가 쓰이면, 이를 제네릭 클래스라 한다.
class FruitBox<T> {
List<T> fruits = new ArrayList<>();
public void add(T fruit) {
fruits.add(fruit);
}
}
클래스 옆에 꺽쇠괄호와 타임 매개변수를 적어주면 된다.(여기서 T가 타입 매개변수이다.)
제네릭의 사용이유
1.타입 안정성
클래스
class Apple {}
class Banana {}
class FruitBox {
private Object fruit;
public FruitBox (Object fruit) {
this.fruit = fruit;
}
pbulic Object getFruit() {
return fruit;
}
}
정상코드
public static void main(String[] args) {
FruitBox appleBox = new FruitBox(new Apple());
FruitBox bananaBox = new FruitBox(new Banana());
Apple apple = (Apple) appleBox.getFruit();
Banana banana = (Banana) bananaBox.getFruit();
}
에러코드
public static void main(String[] args) {
FruitBox appleBox = new FruitBox(new Apple());
FruitBox bananaBox = new FruitBox(new Banana());
Apple apple = (Apple) appleBox.getFruit();
Banana banana = (Banana) appleBox.getFruit();
}
실행하기 전엔 모른다.
즉 제네릭을 사용하지 않았을 때엔 appleBox에서 가져온 과일을 바나나에 대입해버렸는지, 아니였는지 모른다.
위를 제네릭 클래스로 작성했을 시,
제네릭클래스
class Apple {}
class Banana {}
class FruitBox<T> {
private T fruit;
public FruitBox (T fruit) {
this.fruit = fruit;
}
pbulic T getFruit() {
return fruit;
}
}
제네릭을 사용하고 아까와 같은 에러코드
public static void main(String[] args) {
FruitBox appleBox = new FruitBox(new Apple());
FruitBox bananaBox = new FruitBox(new Banana());
Apple apple = (Apple) appleBox.getFruit();
Banana banana = (Banana) appleBox.getFruit();
}
이런 코드 작성 실수를 하면, 바로 IntelliJ가 인식을 하여서 컴파일 에러를 발생시켜 준다,
2.캐스팅 삭제
비제네릭 클래스
public static void main(Stirng[] aggs) {
Fruit appleBox = new FruitBox(new Apple());
Fruit bananaBox = new FruitBox(new Banana());
Apple apple = (Apple) appleBox.getFruit();
Banana banana = (Banana) bananaBox.getFruit();
}
제네릭 클래스
public static void main(String[] args) {
FruitBox
제네릭을 사용하는 이유
- 컴파일 타임에 자료형의 오류에 대한 검증을 수행하여 런타임에 자료형에 안전한 코드를 실행한다.
- 반환값에 대한 타입 변환 및 타임 검사에 들어가는 노력을 줄일수 있고, 형변환이 없어지므로 가독성이 좋아진다.
제네릭 메소드의 형태
- <> 안의 타입으로 매개변수의 데이터 타입을 지정
- 타입 파라미터의 범위는 메소드 블록 이내로 한정
public <T> void add(T t) {
// ...
}
제네릭 메소드는 제네릭 클래스가 아니더라도 정의할 수 있다
class Name {
public <T> void printClassname(T t) {
System.out.println(t.getClass().getName());
}
}
public static void main(Stirng[] args) {
Name name= new Name();
name.printClassName(1);
name.printClassName(3.14);
}
실행 결과값
java.lang.Integer
java.lang.Double
타입 매개변수의 제한
상한 경계
T extends Fruit
- 타입 매개변수의 클래스는 Fruit 클래스이거나 Fruit의 하위 클래스이어야 한다.
class FruitBox<T extends Fruit> {
private List<Fruit> fruits = new ArrayList<>();
pbulic void add(T fruit) {
fruits.add(fruit);
}
}
하한 경계
T super Fruit
- 타입 매개변수의 클래스는 Fruit 클래스이거나 Fruit의 상위 클래스이어야 한다.
비경계 와일드카드
- ?의 형태로 사용, 예를 들어, List<?>
- 모든 타입이 인자가 될 수 있다.
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem + " ");
}
System.out.println();
}
List <String> strings =new ArrayList<>();
printList(strings);
비경계 와일드 카드의 특징
List<?>에서 Get한 원소는 Object 타입이다
1.비경계 와일드카드의 원소는 어떤 타입도 될 수 있다.
- 어떤 타입이 와도 읽을 수 있도록, 모든 타입의 공통 조상인 Object로 받는다
2.List<?>에는 null만 삽입할 수 있다.
- 비경계 와일드카드의 원소가 어떤 타입인지 알 수 없다. 그러므로 타입 안정성을 지키기 위해 null만 삽입할 수 있다.
상한 경계 와일드카드
- ? extends T의 형태로 사용. 예를 들어, List<? extends T>
- T 혹은 T의 하위 클래스만 인자로 올 수 있다.
상한 경계 와일드카드의 특징
- 상한 경계 와일드카드의 원소는 T 혹은 T의 하위 클래스이다.
- 원소들의 최고 공통 조상인 T로 읽으면, 어떤 타입이 오든 T로 읽을 수 있다.
- List<? extends T>에는 null만 삽입할 수 있다.
하한 경계 와일드카드의 특징
- ? super T의 형태로 사용. 예를 들어, List<? super T>
- T 혹은 T의 상위 클래스만 인자로 올 수 있다.
- T 하한 경계 와일드카드의 원소는 T의 상위클래스 중 어떤 타입도 될 수 있다. 어떤 타입이 와도 읽을 수 있도록,
T들의 공통 조상인 Object를 받는다.