Java공부(코딩)

코딩초보의 자바(Java)공부 24일차 { 제네릭 - Generic 2}

동곤일상 2025. 1. 8. 15:51
반응형

일단 오늘의 내용은 전에 발행한 글이 그대로 이어집니다!!!

2025.01.06 - [Java공부(코딩)] - 코딩초보의 자바(Java)공부 23일차 { 제네릭 - Generic1 }

 

코딩초보의 자바(Java)공부 23일차 { 제네릭 - Generic1 }

오늘은 제네릭메서드에관한 공부를 해볼까합니다중요한것이니 잘보고 배워가시는게 있으면 좋겠습니다!제네릭이 필요한 이유public class IntegerBox { private Integer value; public Integer getValue() { return val

ddkk1120.tistory.com


타입 매개변수 제한

동물병원을 만들거다

조건 : 각 동물병원에는 그 동물만 들어갈수있다.

ex)강아지동물병원인데 고양이가들어가거나할수없다.

 

 

여기서 핵심은 `<T extends Animal>` 이다.

타입 매개변수 `T` `Animal` 자식만 받을 있도록 제한을 두는 것이다. `T` 상한이 `Animal` 되는 것이다.

타입 매개변수 `T` 에는 타입 인자로 `Animal` , `Dog` , `Cat` 들어올 있다. 따라서 이를 모두 수용할 있는

`Animal` `T` 타입으로 가정해도 문제가 없다.

따라서 `Animal` 제공하는 `getName()` , `getSize()` 같은 기능을 사용할 있음.

package generic.ex3;

import generic.animal.Animal;

public class AnimalHospital<T extends Animal> {
	
	private T value;
	
	public AnimalHospital(T value) {
		this.value = value;
	}
	
	public void checkUp() {
		
		System.out.println(value);
		value.sound();
		
	}
	
	public void bigger(T target) {
		 if(value.getSize()>target.getSize()) {
			System.out.println(value.getName()+"이 더 큼");
		 }
		 else if(value.getSize() < target.getSize()) {
				System.out.println(target.getName()+"이 더 큼"); 
		 }
		 else {
			 System.out.println("둘의 크기는 같다");
		 }
		
	}
	

}

 

메인코드

package generic.ex3;

import generic.animal.Cat;
import generic.animal.Dog;

public class HospitalMain {
	public static void main(String[] args) {
		Dog dog = new Dog("멍멍이", 15);
		Dog dog2 = new Dog("멍멍이2", 18);
		Cat cat = new Cat("냐옹이", 10);
		
		AnimalHospital<Dog> dogHospital1 = new AnimalHospital<>(dog);
		AnimalHospital<Dog> dogHospital2 = new AnimalHospital<>(dog2);
		AnimalHospital<Cat> catHospital = new AnimalHospital<>(cat);
		
		dogHospital1.checkUp();
		dogHospital2.checkUp();
		dogHospital1.bigger(dog2);
//		dogHospital1.bigger(cat);강아지병원에 고양이가 들어가지못하게 막음
		
		catHospital.checkUp();

	}

}

Animal [name=멍멍이, size=15]

멍멍

 

Animal [name=멍멍이2, size=18]

멍멍

 

멍멍이2이 더 큼

 

Animal [name=냐옹이, size=10]

냐옹냐옹


제네릭 메서드

이번에는 특정메서드에만 제네릭이 들어오는

제네릭메서드에 대해 알아봅시다!

 

public class GenericMethod {
	
	public static Object objectMethod(Object obj) {
		System.out.println("obj print : "+obj);
		return obj;
	}
	
	public static<T> T genericMethod(T t) {
		System.out.println("generic print : "+t);
		return t;
	}
	public static<T extends Number> T numberMethod(T t) {
		System.out.println("numberPrint : "+t);
		return t;
	}

}

 

 

사용코드

package generic.ex4;

public class GenericMethodMain {
	public static void main(String[] args) {
		Integer i = 10;
		Object obj = GenericMethod.objectMethod(i);//object는 모두의 부모
		Integer d = (Integer)obj; //타입을 바꾸려면 다운캐스팅을해야함!
		System.out.println(d); 
		
		String a = "동곤 자바";
		String generic = GenericMethod.<String>genericMethod(a);
		System.out.println(generic);
		
		double b = 10.5;
		long c = 11111199999L;
		Double numberMethod = GenericMethod.numberMethod(b);
		Long numberMethod2 = GenericMethod.numberMethod(c);
		System.out.println(numberMethod);
		System.out.println(numberMethod2);
		
	}

}

obj print : 10

10

generic print : 동곤 자바

동곤 자바

numberPrint : 10.5

numberPrint : 11111199999

10.5

11111199999

 


**제네릭 타입**

정의: `GenericClass<T>`

타입 인자 전달: 객체를 생성하는 시점

) `new GenericClass<String>`

 

**제네릭 메서드**

정의: `<T> T genericMethod(T t)`

타입 인자 전달: 메서드를 호출하는 시점

) `GenericMethod.<Integer>genericMethod(i)`

 

인스턴스 메서드, static 메서드**

제네릭 메서드는 인스턴스 메서드와 static 메서드에 모두 적용할 있다.

 

class Box<T> { //제네릭 타입

static <V> V staticMethod2(V t) {} //static 메서드에 제네릭 메서드 도입

<Z> Z instanceMethod2(Z z) {} //인스턴스 메서드에 제네릭 메서드 도입 가능

}

 

class Box<T> {

T instanceMethod(T t) {} //가능

static T staticMethod1(T t) {} //제네릭 타입의 T 사용 불가능

static 메서드는 인스턴스 단위가 아니라 클래스 단위로 작동하기 때문에 제네릭 타입과는 무관하다.

따라서 static 메서드에 제네릭을 도입하려면 제네릭 메서드를 사용해야 한다.

}

 

타입매개변수 제한

타입매개변수를 Number로 제한

참고로 `Integer` , `Double` , `Long` 같은 숫자 타입이 `Number` 자식이다.

```java

public static <T extends Number> T numberMethod(T t) {}

 

제네릭 메서드 타입 추론

String generic = GenericMethod.<String>genericMethod(a);

이런한 형식으로 <> 로 타입인자를 전달하는일은 귀찮다.

String generic = GenericMethod.genericMethod(a);

이렇게 타입인자를 아예 전달하지 않아도 전달하는 인자(a)를보고

추론해서 String이라는 것을 알 수 있음.

 


제네릭메서드 활용

package generic.ex4;

import generic.animal.Animal;

public class AnimalMethod {
	
	static <T extends Animal>void animal(T t){
		System.out.println("동물이름 : "+t.getName() );
		System.out.println("동물 크기 : "+t.getSize() );
		t.sound();
	}
	
	static<T extends Animal> T getBigger(T t1,T t2) {
		return t1.getSize() > t2.getSize() ? t1 : t2;
		
	}

}
package generic.ex4;

import generic.animal.Animal;
import generic.animal.Cat;
import generic.animal.Dog;

public class AnimalMain2 {
	public static void main(String[] args) {
		Cat cat = new Cat("준냥이", 5);
		Dog dog = new Dog("아롱이", 8);
		Dog dog2 = new Dog("골든리트리버", 12);
		
		AnimalMethod.animal(cat);
		AnimalMethod.animal(dog);
		
		Animal bigger = AnimalMethod.getBigger(dog2, dog);
		System.out.println(bigger+"가 더 큽니다");
		

	}

}

제네릭 타입과 제네릭 메서드의 우선순위

package generic.ex4;

import generic.animal.Animal;

public class ComplexBox<T extends Animal>{

	private T animal;
	
	public void set(T animal) {
		this.animal = animal;
	}
	
	public<T> T printReturn(T t){
		System.out.println("animal.className : "+animal.getClass().getName());
		System.out.println("t.className : "+t.getClass().getName());
		//t.getName()으로 바로호출불가능 (T는 상속받은 클래스가없음)
		return t;
	}
	

}
import generic.animal.Animal;
import generic.animal.Cat;
import generic.animal.Dog;

public class ComplexboxMain {
	public static void main(String[] args) {
		Dog dog = new Dog("멍멍이", 8);
		ComplexBox<Animal> complexBox = new ComplexBox<>();
		complexBox.set(dog);
		
		Cat cat = new Cat("냐옹이이", 3);
		Cat printReturn = complexBox.printReturn(cat);
		System.out.println(printReturn);

	}

}

animal.className : generic.animal.Dog

t.className : generic.animal.Cat

Animal [name=냐옹이이, size=3]

 

타입매개변수가 T로 동일하지만

제네릭메서드가 우선순위를 가짐!!!

 


와일드카드

public class WildcardEx {
	static<T> void genericMethod(Box<T> box) {
		System.out.println("T : "+box.get());
	}

	static void printWildcardV1(Box<?> box) {
		System.out.println("T : "+box.get());
	}

	static<T extends Animal> void printGenericMethod2(Box<T> box) {
		T t = box.get();
		System.out.println("이름 : "+t.getName());
	}

	static void printWildcardV2(Box<? extends Animal> box) {
		Animal animal = box.get();
		System.out.println("이름 :"+animal.getName());
	}

	static <T extends Animal> T printAndReturnGeneric(Box<T> box) {
		T t = box.get();
		System.out.println("이름 = " + t.getName());
		return t;
	}
	static Animal printAndReturnWildcard(Box<? extends Animal> box) {
		Animal animal = box.get();
		System.out.println("이름 = " + animal.getName());
		return animal;
	}



}

T : 하하

T : 하하

이름 : 냐옹이

이름 :냐옹이

이름 = 냐옹이

이름 = 냐옹이

제네릭 메서드와 와일드 카드를 비교할 있게 같은 기능을 각각 하나씩 배치해두었다.

와일드카드는 `?` 사용해서 정의한다.

코드는 뒤에서 하나씩 설명하겠다.

 

 


비제한 와일드카드

//이것은 제네릭 메서드이다.
//Box<Dog> dogBox를 전달한다. 타입 추론에 의해 타입 T가 Dog가 된다.
static <T> void printGenericV1(Box<T> box) {
System.out.println("T = " + box.get());
}
//이것은 제네릭 메서드가 아니다. 일반적인 메서드이다.
//Box<Dog> dogBox를 전달한다. 와일드카드 ?는 모든 타입을 받을 수 있다.
static void printWildcardV1(Box<?> box) {
System.out.println("? = " + box.get());
}

메서드는 비슷한 기능을 하는 코드이다. 하나는 제네릭 메서드를 사용하고 하나는 일반적인 메서드에 와일드카

드를 사용했다.

 

와일드카드는 제네릭 타입이나 제네릭 메서드를 정의할 때 사용하는 것이 아니다. `Box<Dog>` , `Box<Cat>` 처럼

타입 인자가 정해진 제네릭 타입을 전달 받아서 활용할 때 사용한다.

 

와일드카드인 `?` 모든 타입을 받을 있다는 뜻이다.

 

다음과 같이 해석할 있다. `? == <? extends Object>`

이렇게 `?` 사용해서 제한 없이 모든 타입을 받을 있는 와일드카드 비제한 와일드카드라 한다.

여기에는 `Box<Dog> dogBox` , `Box<Cat> catBox` , `Box<Object> objBox` 모두 입력ㄷ가능

 


와일드카드2

상한 와일드카드

static <T extends Animal> void printGenericV2(Box<T> box) {
T t = box.get();
System.out.println("이름 = " + t.getName());
}
static void printWildcardV2(Box<? extends Animal> box) {
Animal animal = box.get();
System.out.println("이름 = " + animal.getName());
}

 

`box.get()` 통해서 꺼낼 있는 타입의 최대 부모는 `Animal` 된다. 따라서 `Animal` 타입으로 조회할

있다.

결과적으로 `Animal` 타입의 기능을 호출할 있다.

 

타입 매개변수가 필요한 경우

와일드카드는 제네릭을 정의할 사용하는 것이 아니다.

`Box<Dog>` , `Box<Cat>` 처럼 타입 인자가 전달된 제네릭 타입을 활용할 사용한다

static <T extends Animal> T printAndReturnGeneric(Box<T> box) {
T t = box.get();
System.out.println("이름 = " + t.getName());
return t;
}
static Animal printAndReturnWildcard(Box<? extends Animal> box) {
Animal animal = box.get();
System.out.println("이름 = " + animal.getName());
return animal;
}

`printAndReturnGeneric()` 다음과 같이 전달한 타입을 명확하게 반환할 있다.

Dog dog = WildcardEx.printAndReturnGeneric(dogBox)

 

반면에 `printAndReturnWildcard()` 경우 전달한 타입을 명확하게 반환할 없다.

여기서는 `Animal` 타입으 반환한다.

Animal animal = WildcardEx.printAndReturnWildcard(dogBox)

 

정리하면 제네릭 타입이나 제네릭 메서드가 필요한 상황이면 `<T>` 사용하고,

그렇지 않은 상황이면 와일드카드를사용하는 것을 권장한다.


하한 와일드카드

public class WildCardMain2 {
	public static void main(String[] args) {
		
		Box<Object> box1 = new Box<>();
		Box<Animal> box2 = new Box<>();
		Box<Dog> box3 = new Box<>();
		Box<Cat> box4 = new Box<>();
		
		writeBox(box1);
		writeBox(box2);
//		writeBox(box3);
//		writeBox(box4); 하한이 Animal
		
		Object object = box1.get();
        상한 제한 없이 선언한 타입 매개변수 `T` 는 `Object`로 변환된다.
//		Animal animal1 = (Animal)object; Animal타입으로 바꾸려면강제형변환필요
		System.out.println(object);
		
		Animal animal2 = box2.get();
		System.out.println(animal2);
		
		
		

	}
	static void writeBox(Box<? super Animal >t) {
		t.setValue(new Dog("멍멍이", 12));
	}

}

 

Animal의 아래(자식) 은

write에 들어갈수 없으며 

 

오직 Animal의 상위타입만 들어간다.


문제와풀이

준비

여러분은 게임속 케릭터를 클래스로 만들어야 한다.

`BioUnit` 유닛들의 부모 클래스이다.

`BioUnit` 자식 클래스로 `Marine` , `Zealot` , `Zergling` 있다.

문제를 풀기 전에 우선 다음 코드를 완성하자.

package generic.test.ex3.unit;

public class Biounit {
	private String name;
	private int hp;
	
	public Biounit(String name, int hp) {
		super();
		this.name = name;
		this.hp = hp;
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @return the hp
	 */
	public int getHp() {
		return hp;
	}

	/**
	 * @param hp the hp to set
	 */
	public void setHp(int hp) {
		this.hp = hp;
	}

	@Override
	public String toString() {
		return "Biounit [name=" + name + ", hp=" + hp + "]";
	}
	
	

}

 

public class Marine extends Biounit {

	public Marine(String name, int hp) {
		super(name, hp);
		// TODO Auto-generated constructor stub
	}
	
	

}
public class Zelot extends Biounit{

	public Zelot(String name, int hp) {
		super(name, hp);
		// TODO Auto-generated constructor stub
	}

}
public class Zergling extends Biounit {

	public Zergling(String name, int hp) {
		super(name, hp);
		// TODO Auto-generated constructor stub
	}

}

문제와 풀이1 - 제네릭 메서드와 상한

**문제 설명**

다음 코드와 실행 결과를 참고해서 `UnitUtil` 클래스를 만들어라.

`UnitUtil.maxHp()` 메서드의 조건은 다음과 같다.

유닛을 입력 받아서 체력이 높은 유닛을 반환한다. 체력이 같은 경우 아무나 반환해도 된다.

제네릭 메서드를 사용해야 한다.

입력하는 유닛의 타입과 반환하는 유닛의 타입이 같아야 한다.

 

public class UnitUtil {
	
	public static<T extends Biounit> T maxHp(T name1,T name2){
		return name1.getHp()>name2.getHp() ? name1 : name2;
		
	}
	public static void main(String[] args) {
		Zelot zelot1 = new Zelot("질럿1", 70);
		Zelot zelot2 = new Zelot("질럿2", 80);
		Zelot maxHp = maxHp(zelot1, zelot2);
		System.out.println(maxHp);

	}

}

Biounit [name=질럿2, hp=80]

 

 


문제와 풀이2 - 제네릭 타입과 상한

**문제 설명**

다음 코드와 실행 결과를 참고해서 `Shuttle` 클래스를 만들어라.

`Shuttle` 클래스의 조건은 다음과 같다.

제네릭 타입을 사용해야 한다.

`showInfo()` 메서드를 통해 탑승한 유닛의 정보를 출력한다.

 

public class Shuttle<T extends Biounit> {
	
	private T t;
	
	public void in(T t) {
		this.t = t;	
	}
	
	public void showInfo() {
		System.out.println(t.toString());
	}
	
	public static void main(String[] args) {
		Shuttle<Biounit> shuttle = new Shuttle<>();
		shuttle.in(new Marine("마린1", 40));
		
		shuttle.showInfo();

	}
	

}

문제와 풀이3 - 제네릭 메서드와 와일드카드

**문제 설명**

앞서 문제에서 만든 `Shuttle` 활용한다.

다음 코드와 실행 결과를 참고해서 `UnitPrinter` 클래스를 만들어라.

`UnitPrinter` 클래스의 조건은 다음과 같다.

`UnitPrinter.printV1()` 제네릭 메서드로 구현해야 한다.

`UnitPrinter.printV2()` 와일드카드로 구현해야 한다.

메서드는 셔틀에 들어있는 유닛의 정보를 출력한다.

(BioUnit이 toString되어있는걸 생각하자)

 

public class UnitPrinter {
	
	public static<T extends Biounit> void printV1(Shuttle<T> value) {
		T out = value.out();
		System.out.println(out);
	}
	
	public static void printV2(Shuttle<? extends Biounit> t) {
		Biounit out = t.out();
		System.out.println(out);
	}

}
public class UnitPrinterTest {

	public static void main(String[] args) {
		Shuttle<Biounit> shuttle1 = new Shuttle<>();
		shuttle1.in(new Zelot("질럿1", 30));
		Biounit out = shuttle1.out();
		
		Shuttle<Biounit> shuttle2 = new Shuttle<>();
		shuttle2.in(new Marine("마린1", 50));
		Biounit out2 = shuttle2.out();
		
		Biounit maxHp = UnitUtil.maxHp(out, out2);
		System.out.println(maxHp);
		
		UnitPrinter.printV1(shuttle1);
		UnitPrinter.printV2(shuttle2);
		
		
		
		

	}

}
Biounit [name=마린1, hp=50]
Biounit [name=질럿1, hp=30]
Biounit [name=마린1, hp=50]