- 제네릭 프로그래밍이란?
어떤 값이 하나의 참조 자료형이 아닌 여러 참조 자료형을 사용할 수 있도록 프로그래밍 하는 것
- 제네릭 사용법
제네릭에서는 여러 참조 자료형을 사용해야 하는 부분에 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);
댓글