제네릭 : 데이터의 타입을 일반화한다 <- 이런 의미를 가진다.
제네릭스를 활용하는 제네릭 클래스는 제네릭 타입 (T, E, K, V)을 활용해 하나의 클래스로 해당 제네릭 타입에 변화를 줘서 제네릭 클래스의 인스턴스를 다양한 타입의 인스턴스로 활용 가능하다.
MyGenericTest
public class MyGenericTest {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
제네릭 사용 이전의 클래스를 활용해서 나타내면
Application
package com.ohgiraffers.section01.generic;
public class Application {
public static void main(String[] args) {
MyGenericTest myGenericTest = new MyGenericTest();
myGenericTest.setValue("Hello World");
myGenericTest.setValue(1);
myGenericTest.setValue(3.14);
System.out.println(myGenericTest.getValue());
Object result = myGenericTest.getValue();
double primitiveResult = (Double)result;
// 여기서 런타임 에러가 발생한다. result는 double형으로 다운캐스팅해야하지만, String형으로 다운캐스팅도 컴파일 시점에 가능해지는데
// 이는 런타임 에러를 발생시킨다. -> Object를 활용하는 것은 자료형이 안전하지 않다.
}
}
Application 주석에서 적혀 있는 것처럼 이런 문제를 해결하기 위해 제네릭를 사용할 수 있다.
바로 활용해보자.
GenericTest
/* 다이아몬드 연산자에 들어갈 수 있는 4가지 타입
* 1. E - Element
* 2. T - Type
* 3. K - Key
* 4. V - Value
* */
public class GenericTest<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
T <- 제네릭 타입으로 여기에는 변화를 줄 수 있다. Integer, String 등등
Application
public class Application {
public static void main(String[] args) {
GenericTest<Integer> genericTest = new GenericTest<>();
}
}
위의 코드를 작성하면 이제 메소드를 불러올 때 자료형이 바뀐 것을 볼 수 있다.

이전에는 Object 형이어서 여러 가지 다운캐스팅이 가능해서 문제가 생겼지만, 제네릭을 사용하면 이제 다른 자료형을 사용하려고 하면 오류가 생긴다.

이제 컴파일 시점에서도 오류를 알려줘서 안전하게 사용할 수 있다.
활용을 해봤으니 사용 이유에 대해서도 알아보자.
와일드카드 : 제네릭 클래스의 인스턴스를 유연하게 활용하기 위한 문법
제네릭 클래스 타입의 객체를 메소드의 매개변수로 받을 때 타입 변수를 제한할 수 있다.
<?>: 모든 타입을 허용하는 와일드 카드
<? extends T>: T 타입 또는 T의 하위 타입을 허용하는 와일드 카드
<? super T>: T 타입 또는 T의 상위 타입을 허용하는 와일드 카드

RabbitFarm
public class RabbitFarm<T extends Rabbit> {
private T animal;
public RabbitFarm() {
}
public RabbitFarm(T animal) {
this.animal = animal;
}
public T getAnimal() {
return animal;
}
public void setAnimal(T animal) {
this.animal = animal;
}
}
Rabbit, Bunny, DrunkenBunny
public class Rabbit extends Mammal {
public void cry() {
System.out.println("끽끽 (토끼 울음소리)");
}
}
public class Bunny extends Rabbit {
@Override
public void cry() {
System.out.println("바니바니 X 2 당근당근");
}
}
public class DrunkenBunny extends Rabbit {
@Override
public void cry() {
System.out.println("바니 쨔스!");
}
}
WildCardFarm
// 제네릭 타입을 활용하는 기능을 포함한 클래스
public class WildCardFarm {
// 어떤 타입의 RabbitFarm이 와도 상관 없다.
// RabbitFarm은 애초에 Rabbit을 상속받아 객체를 만들 수 있는 범위가 정해져 있다.
public void anyType(RabbitFarm<?> farm) {
farm.getAnimal().cry();
}
// RabbitFarm 중에서도 Bunny 또는 하위 타입이 있는 RabbitFarm만 가능하다.
public void extendsType(RabbitFarm<? extends Bunny> farm) {
farm.getAnimal().cry();
}
// RabbitFarm 중에서도 Bunny 또는 상위 타입이 있는 RabbitFarm만 가능하다.
// 즉, Bunny, Rabbit만 가능하다.
public void superType(RabbitFarm<? super Bunny> farm) {
farm.getAnimal().cry();
}
}
anyType()도 RabbitFarm 에 있는 것들 중 하나를 가져오고
extends, super 부분을 보면 Bunny라고 되어 있다. 그래서 Application도 보면 주석처리 된 부분은 오류가 나는 것을 알 수 있을 것이다.
Mammal, Reptile, Snake 는 아직 X
Application
public class Application2 {
public static void main(String[] args) {
WildCardFarm wildCardFarm = new WildCardFarm();
wildCardFarm.anyType(new RabbitFarm<Rabbit>(new Rabbit()));
wildCardFarm.anyType(new RabbitFarm<Bunny>(new Bunny()));
wildCardFarm.anyType(new RabbitFarm<DrunkenBunny>(new DrunkenBunny()));
// wildCardFarm.extendsType(new RabbitFarm<Rabbit>(new Rabbit()));
wildCardFarm.extendsType(new RabbitFarm<Bunny>(new Bunny()));
// wildCardFarm.extendsType(new RabbitFarm<DrunkenBunny>(new DrunkenBunny()));
wildCardFarm.superType(new RabbitFarm<Rabbit>(new Rabbit()));
wildCardFarm.superType(new RabbitFarm<Bunny>(new Bunny()));
// wildCardFarm.superType(new RabbitFarm<DrunkenBunny>(new DrunkenBunny()));
}
}