Java/Java

제네릭 ( Generic )

DDG9 2024. 6. 3. 17:07

제네릭 프로그래밍이란?

제네릭 프로그래밍은 자바에서 타입을 일반화하여 재사용 가능한 코드를 작성하는 기법입니다. 제네릭을 사용하면 클래스, 인터페이스, 메서드 등에 대해 특정 타입을 지정하지 않고, 다양한 타입에서 동작하도록 할 수 있습니다.

 

제네릭 프로그래밍의 주요 이점

  1. 타입 안정성: 컴파일 시 타입을 검사하여 런타임 에러를 줄일 수 있습니다.
  2. 코드 재사용성: 다양한 타입을 처리할 수 있는 일반화된 코드를 작성할 수 있습니다.
  3. 유지보수성: 타입 캐스팅을 줄여 가독성과 유지보수성을 향상시킵니다.

 

ch01

package ch01;

public class Plastic {

	@Override
	public String toString() {
		return "재료는 플라스틱 입니다";
	}
}
package ch01;

public class Powder {

	@Override
	public String toString() {
		return "재료는 파우더 입니다";
	}
}
package ch01;

public class ThreeDPrinter {

	Plastic material;

	// get, set
	public Plastic getMaterial() {
		return material;
	}

	public void setMaterial(Plastic material) {
		this.material = material;
	}
}
package ch01;

public class ThreeDPrinter2 {

	Powder material;

	// get, set
	public Powder getMaterial() {
		return material;
	}

	public void setMaterial(Powder material) {
		this.material = material;
	}
}
package ch01;

/**
 *  컴파일 시점에 material 데이터 타입으로는
 *  모든 클래스가 될 수 있다.
 */

public class ThreeDPrinter3 {

	Object material;

	// get, set
	public Object getMaterial() {
		return material;
	}

	public void setMaterial(Object material) {
		this.material = material;
	}
}
package ch01;

public class MainTest1 {

	public static void main(String[] args) {

		// 제네릭이란?
		// 무엇이든 담을 수 있는 제네릭 프로그래밍 -> ver 5.0 이후

		// 사용하는 이유
		// 우리가 변수를 사용한다고 하면 항상 자료형을 먼저 지정하게 되어 있다.
		// 변수의 이름은 같지만 데이터 타입(자료형)이 달라야 한다면
		// 제네릭 프로그래밍을 생각하자.

		ThreeDPrinter dPrinter1 = new ThreeDPrinter();
		dPrinter1.setMaterial(new Plastic());
		System.out.println(dPrinter1.material.toString());

		System.out.println("-----------------------------");
		// 위 ThreeDPrinter 한계는 재료가 플라스틱에 종속 되어있다.
		// 하지만 사용자 입장에서 재료를 파우더로 변경한다면
		// 코드의 수정이나 새로운 클래스가 필요하다.

		ThreeDPrinter2 dPrinter2 = new ThreeDPrinter2();
		dPrinter2.setMaterial(new Powder());
		System.out.println(dPrinter2.material.toString());

		System.out.println("-----------------------------");

		ThreeDPrinter3 dPrinter3 = new ThreeDPrinter3();
		dPrinter3.setMaterial(new Plastic());
		System.out.println(dPrinter3.material.toString());

		System.out.println("-----------------------------");

		ThreeDPrinter3 dPrinter3_2 = new ThreeDPrinter3();
		dPrinter3_2.setMaterial(new Powder());
		System.out.println(dPrinter3_2.material.toString());

		Plastic plastic01 = (Plastic) dPrinter3.getMaterial(); // 다운 캐스팅
		Powder powder01 = (Powder) dPrinter3.getMaterial(); // 오류나는 코드가 된다.
	}
}

 

ch02

package ch02;

public class GenericPrinter<T> {

	// T 라는 대체 문자를 사용, E - element, K - key, V - value
	// 등등 사실은 아무 문자나 가능하다
	// 자료형 매개변수(type parameter)
	// 이 클래스를 사용하는 시점에서 실제 사용될 자료형이 결정된다.

	private T material; // T 대체 문자형으로 변수를 선언

	// get, set
	public T getMaterial() {
		return material;
	}

	public void setMaterial(T material) {
		this.material = material;
	}
	
	// GenericPrinter<T> -- 참조 변수를 sysout(참조 변수)
	// --> 나의 멤버 material의 toString()으로 설계함
	@Override
	public String toString() {
		return material.toString();
	}
}
package ch02;

import ch01.Plastic;
import ch01.Powder;

public class MainTest2 {

	public static void main(String[] args) {

		// 재료 선언
		Plastic plastic01 = new Plastic();
		Powder power01 = new Powder();
		Water water01 = new Water();

		// 사용하는 시점에 T 대신 어떤 자료형을 사용할지 지정을 하면 된다.
		GenericPrinter<Plastic> genericPrinter1 = new GenericPrinter<>();
		genericPrinter1.setMaterial(plastic01); // 메서드 의존 주입

		// 최상위 Object를 활용할 때와 비교
		// 형변환(다운 캐스트) 할 필요가 없다
		Plastic returnPlastic = genericPrinter1.getMaterial();
		System.out.println(returnPlastic);

		// 컴파일 시점에 오류를 알려줘서 안정적인 코드 작업이 진행된다.
//		Powder returnPowder = genericPrinter1.getMaterial(); <-- 오류 발생

		GenericPrinter<Water> genericPrinter2 = new GenericPrinter<>();
		genericPrinter2.setMaterial(water01);
		System.out.println(genericPrinter2);

		// 제네릭 프로그래밍의 단점
		// 사용하는 시점에 무엇이든 담을 수 있기 때문에
		// 클래스 설계자 입장으로 바라볼 때
		// 의도하지 않은 타입이 들어 올 수 있게 된다.

		// 해결방법은 <T extends 클래스> 문법을 사용한다.
	}
}

 

ch03

package ch03;

// 직접 객체를 사용할 수 없게 강제성을 부여 - 추상 클래스
public abstract class Material {
	
	public abstract void doPrinting(); 
}
package ch03;

/**
 * T extends 클래스 문법을 사용하기 위해 설계
 */

public class Plastic extends Material {

	@Override
	public String toString() {
		return "재료는 플라스틱입니다";
	}

	@Override
	public void doPrinting() {
		System.out.println("플라스틱 재료로 출력합니다.");
	}
}
package ch03;

public class Powder extends Material {

	@Override
	public String toString() {
		return "재료는 파우더입니다";
	}

	@Override
	public void doPrinting() {
		System.out.println("파우더 재료로 출력합니다.");
	}
}
package ch03;

/**
 * @param <T> Material Material 을 상속받은 자식 클래스만 대체 문자에 들어올 수 있다.
 */

public class GenericPrinter<T extends Material> {

	private T material;

	public T getMaterial() {
		return material;
	}

	public void setMaterial(T material) {
		this.material = material;
	}

	@Override
	public String toString() {
		return material.toString();
	}
}
package ch03;

public class MainTest3 {

	public static void main(String[] args) {

		// <T extends 클래스> 사용하기

		// 상위 클래스의 필요성
		// T 자료형은 범위를 제한할 수 없음
		// 위 문법 사용해서 상위클래싱에 속한 자료형만 대체 문자 안에 들어올 수 있다.

		// ch03 패키지 자료형 사용
		GenericPrinter<Powder> genericPrinter1 = new GenericPrinter<>();
		genericPrinter1.setMaterial(new Powder());
		System.out.println(genericPrinter1.toString());

		// 컴파일 시점에서부터 오류 발생을 한다.
//		GenericPrinter<Water> genericPrinter2 = new GenericPrinter<>();
	}
}

'Java > Java' 카테고리의 다른 글

순수 자바 코드로 HttpServer 만들기  (0) 2024.06.04
소켓을 활용한 HTTP 통신  (0) 2024.06.03
네트워크 프로토콜  (0) 2024.05.24
1:N 소켓 양방향 통신  (0) 2024.05.24
1:1 양방향 통신 ( 채팅 기본 기능 구현 )  (0) 2024.05.22