본문 바로가기
Programming Language/Java

[자바] 제네릭

by ggyongi 2021. 10. 26.
반응형

- 제네릭 프로그래밍이란?

어떤 값이 하나의 참조 자료형이 아닌 여러 참조 자료형을 사용할 수 있도록 프로그래밍 하는 것

 

- 제네릭 사용법

제네릭에서는 여러 참조 자료형을 사용해야 하는 부분에 Object가 아닌 하나의 문자로 표현함.

ex) public class GenericPrinter<T>

여기서 이 T를 자료형 매개변수라고 부르고 나중에 클래스를 사용할 때 이 T위치에 실제로 사용할 자료형을 지정.

 

- 자료형 매개변수 T와 static

static 변수나 메서드는 인스턴스 생성없이 클래스 이름으로 호출 가능함. static 변수는 인스턴스 변수 생성 이전에 생성.

또한 static 메서드에서는 인스턴스 변수를 사용할 수 없음. 

그런데 T의 자료형이 정해지는 순간은 제네릭 클래스의 인스턴스가 생성되는 순간임. 따라서 T의 자료형이 결정되는 시점보다 빠르기 때문에 static 변수의 자료형이나 static 메서드 내부 변수의 자료형으로 T를 사용할 수 없음.

 

- 제네릭 사용 예시

Powder, Plastic을 재료로 하는 3D프린터 클래스 예시

package first_project;

public class Powder {
	public void doPrinting() {
		System.out.println("Powder 재료로 출력합니다.");
	}
	
	public String toString() {
		return "재료는 Powder입니다";
	}
}

 

package first_project;

public class Plastic {
	public void doPrinting() {
		System.out.println("Plastic 재료로 출력합니다.");
	}
	
	public String toString() {
		return "재료는 Plastic입니다.";
	}
}

 

package first_project;

public class GenericPrinter<T> {
	private T material;
	
	public void setMaterial(T material) {
		this.material = material;
	}
	
	public T getMaterial() {
		return material;
	}
	
	public String toString() {
		System.out.println("toString() 실행");
		return material.toString();
	}
}

 

package first_project;

public class GenericPrinterTest {
	public static void main(String[] args) {
		GenericPrinter<Powder> powderPrinter = new GenericPrinter<Powder>();
		powderPrinter.setMaterial(new Powder());
		Powder powder = powderPrinter.getMaterial();
		System.out.println(powderPrinter);    // 재정의된 toString()이 실행됨
		
		
		GenericPrinter<Plastic> plasticPrinter = new GenericPrinter<Plastic>();
		plasticPrinter.setMaterial(new Plastic());
		Plastic plastic = plasticPrinter.getMaterial();
		System.out.println(plasticPrinter);    // 재정의된 toString()이 실행됨
	}

}

결과

toString() 실행
재료는 Powder입니다
toString() 실행
재료는 Plastic입니다.

 

- 제네릭에서 대입된 자료형을 명시하지 않는 경우

GenericPrinter powderPrinter2 = new GenericPrinter(); // 대입된 자료형 <Powder>를 명시하지 않음
powderPrinter2.setMaterial(new Powder());
Powder powder = (Powder) powderPrinter.getMaterial(); // 강제 형 변환
System.out.println(powderPrinter);

클래스에 대입된 자료형을 명시하지 않는 경우 컴파일 오류는 아니지만, 사용할 자료형을 명시하라는 경고가 뜸.

또한 컴파일러가 어떤 자료형을 사용할 것인지 알 수 없으므로 getMaterial() 메서드에서 강제로 형 변환을 해야 함.

따라서 되도록이면 대입된 자료형으로 사용할 참조 자료형을 지정하는 것이 좋음.

 

 

- T 자료형에 사용할 자료형 제한하기 <T extends 클래스>

GenericPrinter<T> 클래스의 T에 대입된 자료형으로 사용할 재료 클래스를 추상 클래스에서 상속받음

 

사용 예시

Material 클래스를 추상 클래스로 정의. 상속받은 클래스는 추상 메서드 doPrinting()을 반드시 구현해야 함

package first_project;

public abstract class Material {
	public abstract void doPrinting();
}

Powder 클래스와 Plastic 클래스에서는 Material을 상속받음 

package first_project;

public class Powder extends Material{
	public void doPrinting() {
		System.out.println("Powder 재료로 출력합니다.");
	}
	
	public String toString() {
		return "재료는 Powder입니다";
	}
}

 

package first_project;

public class Plastic extends Material {
	public void doPrinting() {
		System.out.println("Plastic 재료로 출력합니다.");
	}
	
	public String toString() {
		return "재료는 Plastic입니다.";
	}
}

 그리고 GenericPrinter 클래스 이름에 <T extends Material>을 명시. 이렇게 하면 Material을 상속받지 않은 클래스를 T에 대입하려 할 때 오류가 발생한다.

package first_project;

public class GenericPrinter<T extends Material> {
	private T material;
	
	public void setMaterial(T material) {
		this.material = material;
	}
	
	public T getMaterial() {
		return material;
	}
	
	public String toString() {
		System.out.println("toString() 실행");
		return material.toString();
	}
}

 

 

- <T extends 클래스>로 상위 클래스 메서드 사용하기

<T extends Material>를 선언하면 제네릭 클래스를 사용할 때 상위 클래스 Material에서 선언한 메서드를 사용할 수도 있음.

 

T는 컴파일 시 Object 클래스로 변환되어 이 경우 Object 클래스가 제공하는 메서드만 사용할 수 있음. 왜냐면 자료형을 아직 알 수 없기 때문임. 하지만 <T extends Material>의 경우 컴파일 시 T가 Material로 변환되기 때문에 Material에서 선언한 메서드를 사용할 수 있는 것.

 

사용 예시

package first_project;

public abstract class Material {
	public abstract void doPrinting();
	
	public void Hello() {
		System.out.println("hello");
	}
}

  

package first_project;

public class GenericPrinter<T extends Material> {
	private T material;
	
	public void setMaterial(T material) {
		this.material = material;
	}
	
	public T getMaterial() {
		return material;
	}
	
	public String toString() {
		System.out.println("toString() 실행");
		return material.toString();
	}
	
	public void printing() {
		material.doPrinting();    // 상위 클래스 Material의 메서드 호출
		material.Hello();         // 상위 클래스 Material의 메서드 호출
	}
}

 

package first_project;

public class GenericPrinterTest {
	public static void main(String[] args) {
		GenericPrinter<Powder> powderPrinter = new GenericPrinter<Powder>();
		powderPrinter.setMaterial(new Powder());
		powderPrinter.printing();
		
		
		GenericPrinter<Plastic> plasticPrinter = new GenericPrinter<Plastic>();
		plasticPrinter.setMaterial(new Plastic());
		plasticPrinter.printing();
	}

}

결과

Powder 재료로 출력합니다.
hello
Plastic 재료로 출력합니다.
hello

 

 

- 제네릭 메서드 활용하기

package first_project;

public class Point<T, V> {
	T x;
	V y;
	
	
	Point(T x, V y){
		this.x = x;
		this.y = y;
	}
	
	
	// 제네릭 메서드
	public T getX() {
		return x;
	}
	
	public V getY() {
		return y;
	}
}

 

package first_project;

public class GenericMethod {

	public static <T, V> double makeRectangle(Point<T, V> p1, Point<T, V> p2) {
		double left = ((Number) p1.getX()).doubleValue();
		double right = ((Number) p2.getX()).doubleValue();
		double top = ((Number) p1.getY()).doubleValue();
		double bottom = ((Number) p2.getY()).doubleValue();
		
		double width = right - left;
		double height = bottom - top;
		
		return width*height;
	}
	
	public static void main(String[] args) {
		Point<Integer, Double> p1 = new Point<>(0, 0.0);
		Point<Integer, Double> p2 = new Point<>(10, 10.0);
		
		double rect = GenericMethod.<Integer, Double>makeRectangle(p1,  p2);
		System.out.println("넓이는 "+ rect);
	}

}

결과

넓이는 100.0

 

위의 GenericMethod 클래스에서 makeRectangle 함수 호출 부분은 다음과 같이 대입 자료형이 생략되도 된다.

double rect = GenericMethod.makeRectangle(p1,  p2);

 

 

비전공자 네카라 신입 취업 노하우

시행착오 끝에 얻어낸 취업 노하우가 모두 담긴 전자책!

kmong.com

댓글