제네릭(Generic)

Joy🌱·2023년 1월 4일
0

☕ Java

목록 보기
29/40
post-thumbnail

💁‍♀️ 제네릭(Generic)이란,
데이터의 타입을 일반화 한다는 의미.
제네릭은 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 지정하는 방법.


🙋‍ 잠깐 ! 제네릭의 장점은 뭔가요?

  • 컴파일 시에 미리 타입 검사를 시행하면 클래스나 메소드 내부에서 사용되는 객체의 타입 안정성을 높일 수 있음 (잘못 된 타입인 경우 컴파일 에러를 발생시킴)
  • 반환 값에 대한 타입 변환 및 타입 검사에 들어가는 코드 생략이 가능해짐
    (instanceof 비교 및 다운 캐스팅 작성이 불필요)

👀 제네릭 테스트

📌 Ref.

* 제네릭 설정은 클래스 선언부 마지막 부분에 '다이아몬드 연산자를 이용'하여 작성
* 내부에 작성하는 영문자는 관례상 '대문자'로 작성
* 다이아몬드 연산자 내부에 작성한 T'타입 변수'라고 부르며 타입 변수를 자료형 대신 사용
* '가상으로 존재하는 타입'으로 T가 아닌 영문자를 사용해도 무방
* 사용하는 쪽에서 작성한 제네릭 클래스를 이용할 시 실제 사용할 타입을 타입 변수 자리에 맞추어 넣어주면 
  '컴파일 시점에 타입이 결정'

◼ GenericTest<T> Class

public class GenericTest<T> {
	private T value; >>> 현재 해당 필드는 타입이 결정되지 않은 상태
	
	>>> getter, setter 작성 시에도 구체적인 타입 대신 T를 이용
    // getter, setter
	public void setValue(T value) { >>> 결정되어있지않음
		this.value = value;
	}
	public T getValue() {
		return value;
	}
}

◼ Application Class

[1] 타입을 Integer로 인스턴스를 생성하는 경우

GenericTest<Integer> gt1 = new GenericTest<Integer>();
>>> 따라서 GenericTest 클래스의 필드, 생성자 안에 있는 TInteger
		
gt1.setValue(10);
System.out.println(gt1.getValue()); // 10 출력
>>> 메소드의 인자 및 반환 값 모두 Integer 타입

[2] 타입을 String으로 인스턴스를 생성하는 경우

GenericTest<String> gt2 = new GenericTest<String>();
gt2.setValue("치즈"); 
System.out.println(gt2.getValue()); // 치즈 출력
>>> 위에서 <T>Integer였지만 여기서는 String으로 사용 가능

[3] 타입 추론으로 인해 생성자의 Double은 생략 가능

GenericTest<Double> gt3 = new GenericTest<>();

📌 Ref.

* JDK 1.7부터 타입 선언 시 타입 변수가 작성되면 타입 추론이 가능하기 때문에 생성자 쪽의 타입을 
  생략하고 작성할 수 있음 (, 빈 다이아몬드 연산자는 사용해야함)

👀 제네릭 클래스의 상속(extends)과 와일드 카드(wild card)

💁‍ 제네릭 클래스의 상속(extends)
: 제네릭 클래스 작성 시 extends 키워드를 이용하면 특정 타입만 사용하도록 제한을 걸 수 있음

  • ex) 토끼의 후손이거나 토끼인 경우에만 타입으로 사용 가능하며 그 외의 타입 지정시 컴파일 단계에서 에러를 발생

💁‍ 제네릭 클래스의 와일드 카드(wild card)
: 제네릭 클래스 타입의 객체를 메소드의 매개변수로 받을 때, 그 객체의 타입 변수를 제한할 수 있음


◼ Animal Classes

[1] Animal Class

public interface Animal {
}

[2] Mammal Class

public class Mammal implements Animal {
}

[3] Rabbit Class

public class Rabbit extends Mammal {
	
	public void cry() {
		System.out.println("토끼가 울음소리를 냅니다. 끾끼익!!");
	}
}

[4] Bunny Class

public class Bunny extends Rabbit {
	
	@Override
	public void cry() {
		System.out.println("바니바니 바니바니 당근! 당근! ㄴ(+_+)ㄱ");
	}
}

[5] DrunkenBunny Class

public class DrunkenBunny extends Bunny {
	
	@Override
	public void cry() {
		System.out.println("붸니붸니 붸니붸니 댕근.. 댕근.. ㄴ(@_@)ㄱ");
	}
}

[6] Reptile Class

public class Reptile implements Animal {

}

[7] Snake Class

public class Snake extends Reptile {
}

◼ RabbitFarm<T extends Rabbit> Class

public class RabbitFarm<T extends Rabbit> { 
>>> : Rabbit이거나 Rabbit의 후손만 가능

	private T animal;
	
	public RabbitFarm() {}
	
	public RabbitFarm(T animal) {
		this.animal = animal;
	}
	
	public void setAnimal(T animal) {
		this.animal = animal;
	}

	public T getAnimal() {
		return animal;
	}
}

◼ Application Class

>>> Rabbit의 상위 클래스, 인터페이스인 Animal, Mammal 또는 관계가 없는 Snake 타입으로는 생성 불가능
//	RabbitFarm<Animal> farm1 = new RabbitFarm<>(); // Bound mismatch 에러
//	RabbitFarm<Mammal> farm1 = new RabbitFarm<>(); // Bound mismatch 에러
//	RabbitFarm<Snake> farm1 = new RabbitFarm<>(); // Bound mismatch 에러
		
	RabbitFarm<Rabbit> farm1 = new RabbitFarm<>();
	RabbitFarm<Bunny> farm2 = new RabbitFarm<>();
	RabbitFarm<DrunkenBunny> farm3 = new RabbitFarm<>(); 
    >>>  farm1은 Rabbit, farm2는 Bunny, farm3는 DrunkenBunny인 것 보장
		
	farm1.setAnimal(new Rabbit());
	farm1.getAnimal().cry();
//  ((Rabbit)farm1.getAnimal()).cry();
	>>> 이와 같은 형변환이 필요 없음(제네릭의 장점)
		
	farm2.setAnimal(new Bunny());
	farm2.getAnimal().cry();
		
	farm3.setAnimal(new DrunkenBunny());
	farm3.getAnimal().cry();
    >>> farm3는 DrunkenBunny인 것이 보장이 되어있기 때문에 getAnimal의 cry()를 호출하면 
    	DrunkenBunny의 cry가 나옴

📌 Ref.

* 제네릭을 사용해서 올바른 타입을 타입 변수로 지정하는 경우에는 인스턴스 내부에 있는 타입 자체가 
  Rabbit 타입을 가지고 있는 것이 보장되어 있기 때문에 형변환이 생략

◼ WildCardFarm Class

public class WildCardFarm {

	>>> 매개변수로 전달받는 토끼 농장을 구현할 때 사용한 타입 변수에 대해 제한할 수 있음
	
	>>> 토끼 농장에 있는 토끼가 어떤 토끼이던 상관없다는 의미 (어떤 동물이든 상관없다는건 아님)                                                             >>> <T extends Rabbit> 범위여야하지만 어떤 토끼든 가능                                          
	public void anyType(RabbitFarm<?> farm) { 
    	farm.getAnimal().cry();
	}
	
	>>> 토끼 농장의 토끼는 Bunny이거나 그 후손 타입으로 만들어진 토끼 농장만 매개변수로 사용
	public void extendsType(RabbitFarm<? extends Bunny> farm) {
		farm.getAnimal().cry();
	}
	
	>>> 토끼 농장의 토끼는 Bunny이거나 그 부모타입으로 만들어진 토끼 농장만 매개변수로 사용
	public void superType(RabbitFarm<? super Bunny> farm) {
		farm.getAnimal().cry();
	}
}

◼ Application Class

	WildCardFarm wildCardFarm = new WildCardFarm();
		
>>> 토끼 농장 생성 자체가 불가능한 것은 매개변수로 사용할 수 없음
//	wildCardFarm.anyType(new RabbitFarm<Mammal>());
		
	/* 1. 매개변수의 타입 제한이 없는 경우 */
	wildCardFarm.anyType(new RabbitFarm<Rabbit>(new Rabbit()));
	wildCardFarm.anyType(new RabbitFarm<Bunny>(new Bunny()));
	wildCardFarm.anyType(new RabbitFarm<DrunkenBunny>(new DrunkenBunny()));
		
	/* 2. <? extends Bunny>인 경우 */ 
    >>> Bunny이거나 Bunny를 상속받은 클래스만 가능
//	wildCardFarm.extendsType(new RabbitFarm<Rabbit>(new Rabbit()));
	wildCardFarm.extendsType(new RabbitFarm<Bunny>(new Bunny()));
	wildCardFarm.extendsType(new RabbitFarm<DrunkenBunny>(new DrunkenBunny()));
		
	/* 3. <? super Bunny>인 경우 */ 
    >>> Bunny이거나 Bunny의 부모클래스만 가능 
	wildCardFarm.superType(new RabbitFarm<Rabbit>(new Rabbit()));
	wildCardFarm.superType(new RabbitFarm<Bunny>(new Bunny()));
//	wildCardFarm.superType(new RabbitFarm<DrunkenBunny>(new DrunkenBunny()));
	>>>
profile
Tiny little habits make me

0개의 댓글

관련 채용 정보