
타입이 여러 개 생기니 타입이 맞는기 검증하는 과정이 필요해진 것. → instanceof 와 같은 것
정해져있지 않은 타입이 있을 수 있음 → 다양하게 바뀔 수 있다면 그러한 내용들을 열어 놓고 선택할 수 있게 Generics 라는 이름을 지어서 사용한다.
제네릭의 사전적인 의미는 일반적인이라는 의미이다. 자바에서 제네릭이란 데이터의 타입을 일반화 한다는 의미를 가진다. 제네릭은 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 지정하는 방법을 말한다. 컴파일 시에 미리 타입 검사를 시행하게 되면 클래스나 메소드 내부에서 사용되는
타입 안정성을 높일 수 있으며(잘못된 타입인 경우 컴파일 에러를 발생시킴) 반환값에 대한 타입 변환 및 타입 검사에 들어가는 코드 생략이 가능해진다. (instanceof 비교 및 다운 캐스팅 작성 불필요)
어떤 타입이 들어올지 모르겠을 때 -> 런타임에 받으면 문제, 매번 체크도
귀찮고-> 그런부분에서 시작에서부터 지정한 다음 이 내용 말고는 못들어오도록
정해놓고 제한점을 정해둠으로 파일을 관리한다.
public class GenericTest<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
// 제네릭 클래스의 T(타입)에 해당하는 부분이 모두 Integer로 변환된다.
GenericTest<Integer> gt = new GenericTest<Integer>();
/*GenericTest 객체의 타입을 그때그때 다르게 변경할 수 있게 만든다.*/
genericTest1.setValue(10);
System.out.println(genericTest1.getValue());
System.out.println(genericTest1.getValue() instanceof Integer);
GenericTest<String> genericTest2 = new GenericTest<>();
genericTest2.setValue("Hello");
System.out.println(genericTest2.getValue());
System.out.println(genericTest2.getValue() instanceof String);
결과
10
true
Hello
true
객체 배열은 무엇이 들어가 있는지 몰라서 그때그때 타입을 확인해주어야 하지만, 한가지 타입에 대해서만 사용한다면 위 정해진 타입변수를 사용하여 한가지 타입에 대한 객체 배열(혹은 더 많은 타입변수를 사용)을 사용할 수 있도록 만들어둔 것이다.

public class RabbitFarm<T> {
//토끼 농장 타입을 만들어서 그때 그때 타입을 넣어서 사용할 것이라는 것
//field
private T animal;
//constructor
public RabbitFarm(){}
public RabbitFarm(T animal){
this.animal = animal;
}
//getter
public T getAnimal() {
return animal;
}
//setter
public void setAnimal(T animal) {
this.animal = animal;
}
}public static void main(String[] args) {
RabbitFarm<Rabbit> rabbitFarm = new RabbitFarm<>();
rabbitFarm.setAnimal(new Rabbit()); //Rabbit 객체 추가
rabbitFarm.getAnimal().cry();
rabbitFarm.setAnimal(new Bunny()); //Bunny 객체 추가
rabbitFarm.getAnimal().cry();
rabbitFarm.setAnimal(new DrunkBunny()); //DrunkBunny 객체 추가
rabbitFarm.getAnimal().cry();
// 동적바인딩이 일어나 각기 다른 타입의 객체들이 들어갈 수 있다.
}
결과
토끼가 울음소리를 냅니다. 🐰🐰
바니바니 바니바니 당근 당근🥕🥕
봐니~봐니~ 댕근댕근~~🍻🍻🍻
Extends 키워드를 이용하여 특정 타입만 사용하도록 제네릭 범위를 제한할 수 있다.
토끼농장 클래스에서 interface를 상속해보자. 인터페이스를 상속받을 때 implement 대신, extends를 사용한다.

public class RabbitFarm<T extends Animal> {
이렇게 정의하면 Rabbitfarm 안에 모든 Animal 타입을 받을 수 있다.
하지만 Animal까지는 필요없다. Generics 범위를 Animal이 아니라 토끼의 종 위로는 필요없다. Generics 범위를 Rabbit까지로 걸어보자. 토끼농장 RabbitFarm의 제네릭 접근 범위 상한선을 Rabbit으로 정해둔다.
public class RabbitFarm<T extends Rabbit> {
이렇게 제네릭 범위를 제한해주면, Application실행에서 토끼농장 제네릭으로 Animal을 가져올 수 없도록 막아둔 것이다.

RabbitFarm 클래스는 Rabbit 의 상위 클래스로는 접근할 수 없는 것이다.
RabbitFarm<T extends Rabbit> 으로 그 제한을 정한다.
만약 제한을 T extends Rabbit 에서 T extends Animal 로 바꾸면 어떻게 될까?
토끼 농장 클래스에서 잠깐 T extends Animal 로 바꿔보자.


이렇게 접근할 수 있게 된다. 하지만 이것은 원하는 흐름이 아니다.
다시 T extends Rabbit 으로 돌리고, 아래 실행 클래스처럼 확인할 수 있다.
RabbitFarm<Rabbit> farm4 = new RabbitFarm<>();
RabbitFarm<Bunny> farm5 = new RabbitFarm<>();
RabbitFarm<DrunkBunny> farm6 = new RabbitFarm<>();
farm4.setAnimal(new Rabbit());
((Rabbit) farm4.getAnimal()).cry();
farm4.getAnimal().cry();
farm5.setAnimal(new Bunny());
((Bunny) farm5.getAnimal()).cry();
farm5.getAnimal().cry();
farm6.setAnimal(new DrunkBunny());
((DrunkBunny) farm6.getAnimal()).cry();
farm6.getAnimal().cry();
결과
토끼가 울음소리를 냅니다. 🐰🐰
토끼가 울음소리를 냅니다. 🐰🐰
바니바니 바니바니 당근 당근🥕🥕
바니바니 바니바니 당근 당근🥕🥕
봐니~봐니~ 댕근댕근~~🍻🍻🍻
봐니~봐니~ 댕근댕근~~🍻🍻🍻
토끼농장 클래스로 돌아오자. 그리고 상위를 이렇게 바꿔보자.
public class RabbitFarm<T extends Rabbit & Animal> {
제네릭에서 이제 <T extends Rabbit & Animal> 로 바뀌었다. 클래스와 인터페이스 상속 시 & 를 사용할 때 아래의 룰이 있다.
이번엔 이렇게 바꿔보자
public class RabbitFarm<T extends Rabbit , Reptile> {
이렇게 되면, 토끼농장 클래스는 제네릭 타입을 두 가지로 받아오는 것이다. 물론 토끼농장에서 뱀을 들일 수 없겠지만, 이는 의미적으로 상속의 의미를 따로 따로 만들겠다는 것이다.
타입을 섞는게 아니라 각기 Rabbit과 Reptile 을 따로 가져와서 관리하겠다는 것이다.
이렇게 제네릭 타입변수를 여러개 가져오게 되면 아래와 같이 실행파일을 바꾸어 주어야 한다.


이렇게 바꾸면 에러가 사라진다. 제네릭 타입변수를 여러개 가져와주면 사용할 수 있게 되는 것이다.
매개변수로 전달받은 토끼농장을 구현할 때 사용한 타입 변수에 대해서 제한을 할 수 있다.
<?> : 모든 타입을 허용하는 와일드 카드. 누구든 들어올 수 있다. <? extends T> : T 타입 또는 T의 하위 타입을 허용하는 와일드 카드. T로 제네릭 타입의 상한선을 정해준다. <? super T> : T 타입 또는 T의 상위 타입을 허용하는 와일드 카드. 상속 연결고리가 닿는 곳까지만 허용하는 것. T=Rabbit 일 때, Rabbit→Mammal→Animal 까지만 받을 수 있음이 토끼 농장 클래스를 이용해서 와일드 카드를 이해해보자.
와일드카드 농장 클래스를 추가해보자.
<WildCardFarm> 클래스
<?>
public void anyType(RabbitFarm<?> farm) {
farm.getAnimal().cry();
}
여기에는 토끼 농장에 있는 토끼나, 바니나 술취한 바니 모두 상관없이 들어올 수 있다.
<? extends Type>
public void extendsType(RabbitFarm<? extends Bunny> farm) {
farm.getAnimal().cry();
}
여기는 토끼 농장의 바니이거나 술취한 바니(후손타입)로 만들어진
토끼 농장만 매개변수로 사용할 수 있다.
<? super Type>
public void superType(RabbitFarm<? super Bunny> farm) {
farm.getAnimal().cry();
}
-> 토끼 농장의 토끼는 바니이거나 그 부모 타입(Rabbit)으로 만들어진
토끼농장만 매개변수로 사용할 수 있다.
이렇게 되면, 실행은 어떻게 될지 보아야 한다.
아래는 와일드 카드 팜 클래스를 실행할 실행문에서 가져온 것이다.
ildCardFarm.anyType(new RabbitFarm<Mammal>(new Mammal()));
wildCardFarm.anyType(new RabbitFarm<Reptile>(new Reptile()));
//이 것들은 실행이 안된다 농장 생성 자체가 불가능한 것은 매개변수로 사용할 수 없다.
// <방법 1>
RabbitFarm<Rabbit> rabbitFarm1 = new RabbitFarm<>();
rabbitFarm1.setAnimal(new Rabbit());
wildCardFarm.anyType(rabbitFarm1);
이는 와일드 카드 클래스의 anyType() 메소드 실행문이다.
// rabbitFarm을 생성하고 넣어줌
// <방법 2>
wildCardFarm.anyType(new RabbitFarm<Bunny>(new Bunny()));
//생성할 때 매개변수 생성자로 객체생성해줌
// 방법 1과 2는 동일하다.
wildCardFarm.anyType(rabbitFarm1);
wildCardFarm.anyType(new RabbitFarm<Bunny>(new Bunny()));
wildCardFarm.anyType(new RabbitFarm<DrunkBunny>(new DrunkBunny()));
아래는 extendsType() 메소드이다. 상속 상위 제한을 걸어준다.
// Extends type -> Bunny 제한자.
// extendsType 메소드에는 Rabbit으로 생성할 수 없다.
wildCardFarm.extendsType(new RabbitFarm<Rabbit>(new Rabbit())); ❌
// 따라서 Bunny와 DrunkBuny가 상한선이다.
wildCardFarm.extendsType((new RabbitFarm<Bunny>(new Bunny()))); ⭕
wildCardFarm.extendsType((new RabbitFarm<DrunkBunny>(new DrunkBunny()))); ⭕
마지막으로 아래는 superType() 메소드이다. 상속 하한선을 정해준다.
// 아래는 superType 메소드. <? super Bunny> 로 들어갔다. 따라서, Bunny 하한선으로 접근제한한다.
wildCardFarm.superType(new RabbitFarm<Rabbit>(new Rabbit())); ⭕
wildCardFarm.superType((new RabbitFarm<Bunny>(new Bunny()))); ⭕
// Bunny 하한선이므로 DrunkBunny는 사용하지 못한다. //오류발생
wildCardFarm.superType((new RabbitFarm<DrunkBunny>(new DrunkBunny()))); ❌
