제네릭(Generic) - 27

목차

    제네릭(Generic)

    1. 제네릭 이란?

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

    제네릭 자료형 정의

    • 클래스에서 사용하는 변수의 자료형이 여러개 일수 있고, 그 기능(메서드)은 동일한 경우 클래스의 자료형을 특정하지 않고 추후 해당 클래스를 사용할 때 지정 할 수 있도록 선언하는 것
    • 실제 사용되는 자료형의 변환은 컴파일러에 의해 검증되므로 안정적인 프로그래밍 방식
    • 컬렉션 프레임워크에서 많이 사용되고 있음

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

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

    2. 시나리오 코드 1 - 제네릭 미사용 (Object 클래스 활용)

    public class Plastic {
    
    	@Override
    	public String toString() {
    		return "재료는 플라스틱 입니다.";
    	}
    }
    public class Powder {
    
    	@Override
    	public String toString() {
    		return "재료는 파우더 입니다.";
    	}
    }
    public class ThreeDPrinter {
    
    	Plastic material;
    
    	// get, set
    	public Plastic getMaterial() {
    		return material;
    	}
    
    	public void setMaterial(Plastic material) {
    		this.material = material;
    	}
    
    }
    public class ThreeDPrinter2 {
    
    	Powder material;
    
    	// get, set
    	public Powder getMaterial() {
    		return material;
    	}
    
    	public void setMaterial(Powder material) {
    		this.material = material;
    	}
    
    }
    /**
     * 컴파일 시점에 material 데이터 타입으로는<br>
     * 모든 클래스가 될 수 있다.
     */
    public class ThreeDPrinter3 {
    
    	Object material;
    
    	// get, set
    	public Object getMaterial() {
    		return material;
    	}
    
    	public void setMaterial(Object material) {
    		this.material = material;
    	}
    
    }

     

    public class MainTest1 {
    
    	public static void main(String[] args) {
    		ThreeDPrinter dPrinter1 = new ThreeDPrinter();
    		dPrinter1.setMaterial(new Plastic());
    		System.out.println(dPrinter1.material);
    
    		// 위 ThreeDPrinter 한계는 재료가 플라스틱에 종속 되어있다.
    		// 하지만 사용자 입장에서 재료를 파우더로 변경한다면
    		// 코드의 수정이나 새로운 클래스가 필요하다
    		System.out.println("------------------");
    		ThreeDPrinter2 dPrinter2 = new ThreeDPrinter2();
    		dPrinter2.setMaterial(new Powder());
    		System.out.println(dPrinter2.material);
    		System.out.println("------------------");
    
    		ThreeDPrinter3 dPrinter3 = new ThreeDPrinter3();
    		dPrinter3.setMaterial(new Plastic());
    		System.out.println(dPrinter3.material);
    		System.out.println("------------------");
    
    		ThreeDPrinter3 dPrinter3_2 = new ThreeDPrinter3();
    		dPrinter3_2.setMaterial(new Powder());
    		System.out.println(dPrinter3_2.material);
    
    		Plastic plastic01 = (Plastic) dPrinter3.getMaterial(); // 다운 캐스팅
    		Powder powder01 = (Powder) dPrinter3.getMaterial(); // 오류나는 코드가 된다.
    
    	}
    
    }

    3. 시나리오 코드 2 - 제네릭 활용

    package ch02;
    
    /**
     * 제네릭이란?<br>
     * 무엇이든 담을 수 있는 제네릭 프로그래밍 -> ver 5.0<br>
     * 
     * 사용하는 이유<br>
     * 우리가 변수를 사용한다고 하면 항상 자료형을 먼저 지정하게 되어있다.<br>
     * 변수의 이름이 같지만 데이터 타입(자료형)이 달라야 한다면<br>
     * 제네릭 프로그래밍을 생각하자.
     */
    public class GenericPrinter<T> {
    
    	// T 라는 대체 문자를 사용, E - element, K - key, V - value(사실은 아무 문자나 가능하다)
    	// 자료형 매개변수(type parameter)
    	// 이 클래스를 사용하는 시점에서 실제 사용될 자료형이 결정된다.
    
    	private T material; // T 대체 문자형으로 변수를 선언
    
    	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 powder01 = 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 클래스> 문법을 사용한다.
    		
    	} // end of main
    }

    4. 제네릭 <T extends 클래스> 활용

    // 직접 객체를 사용할 수 없게 강제성을 부여 추상 클래스
    public abstract class Material {
    	public abstract void doPrinting();
    }
    /**
     * T extends 클래스 문법을 사용하기 위해 설계
     */
    public class Plastic extends Material {
    
    	@Override
    	public String toString() {
    		return "재료는 플라스틱 입니다.";
    	}
    
    	@Override
    	public void doPrinting() {
    		System.out.println("플라스틱 재료로 출력합니다.");
    	}
    
    }
    /**
     * T extends 클래스 문법을 사용하기 위해 설계
     */
    public class Powder extends Material{
    
    	@Override
    	public String toString() {
    		return "재료는 파우더 입니다.";
    	}
    	
    	@Override
    	public void doPrinting() {
    		System.out.println("파우더 재료로 출력합니다.");
    	}
    	
    }
    /**
     * @param <T> Material <br>
     * 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();
    	}
    
    }
    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);
    		
    		///////////////////////////////////////////////////////////////
    		
    		// 컴파일 시점에서 부터 오류 발생을 한다.
    		// GenericPrinter<Water> genericPrinter2 = new GenericPrinter<>();
    	}
    
    }

    Java 유용한 클래스 - 3 으로 돌아가기