[Chap 12] 12.1 제네릭
제네릭이란?
상황에 따라 다른 형의 데이터형을 받아야된느 변수가 있다. 이처럼 어떤 값이 하나의 참조 자료형이 아닌 여러 참조 자료형을 사용할 수 있도록 프로그래밍하는 것을 제네릭 프로그래밍 이라고 한다.
제네릭의 필요성
얘를 들어서 3D프린터의 재료는 다양할 수 있다. 하지만 그 재료에 따라서 매번 변수(재료)만 다른 코드를 작성하기에는 너무 비효율적이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public class TreeDPrinter { private Powder material; //재료가 파우더형인 경우 public void setMaterial(Powder material) { this.material = material; } public Powder getMaterial() { return material; } } public class TreeDPrinter { private Plastic material; //재료가 플라스틱형인 경우 public void setMaterial(Plastic material) { this.material = material; } public Plastic getMaterial() { return material; } } | cs |
이때 필요한 것이 제네릭이다. 자료형 Object
를 사용하면 달라지는 자료형에 따라서 처리를 할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class TreeDPrinter { private Object material; public void setMaterial(Object material) { this.material = material; } public Object getMaterial() { return material; } } | cs |
하지만 Object
형의 단점은 setMaterial메서드 같은 경우는 자동으로 형 변환이 되지만, getMaterial메서드 같은 경우는 직접 형 변환을 해주어야 된다.
1 2 3 4 5 6 | ThreeDPrinter printer = new ThreeDPrinter(); Powder p1 = new Powder(); printer.setMaterial(p1); //자동으로 Powder형으로 형변환 Powder p2 = (Powder)printer.getMaterial(); //직접 형 변환이 필요 | cs |
결국 getMaterial
메서드 같은 경우는 Object형으로 리턴을 해주기 때문에 이 데이터형을 또 Powder형으로 바꾸어주어야 한다는 것이다. 하지만 이 점은 굉장히 번거롭다. 그래서 사용하는 것이 제네릭이라는 것이다.
다이아몬드 연산자 < >
1 2 | ArrayList<String> list = new ArrayList<> (); | cs |
클래스를 사용할 때, 이런 각괄호를 많이 봤다. 그리고 클래스에 데이터형을 넣어주었으면 뒤에 인스턴스에는 넣어주지 않아도 잘 작동했던 경험도 있다. 근데 이게 왜 정확히 그러는지 몰랐는데, 알고 봤더니 선언된 자료형을 보고 생략된 부분이 String임을 컴파일러가 유추할 수 있었기 때문이었다.
자료형 매개변수 T와 static
앞에서 사용자 정의 메서드를 만들때, public static ~ 이렇게 썼던 경우가 많다. static 변수나 메서드는 인스턴스를 생성하지 않아도 클래스 이름으로 호출할 수 있다. static변수는 인스턴스 변수가 생성되기 이전에 생성되기 때문이다. 그래서 static 메서드에서는 인스턴스 변수를 사용할 수 없다. static이 먼저 생성되기 때문에 static이 생성될 때에는 아직 인스턴스가 없어서 코드가 작동할 수가 없다. 같은 원리로 자료형 매개변수 T는 static보다 먼저 생성된다. 그래서 static 변수나 메서드의 자료형을 T를 사용할 수 없다. T가 생성될 때, 아직 static은 없는 존재기 때문이다. 그래서 생성 순서를 보면 T -> static -> 인스턴스 이렇게 되므로 코드를 작성할 때, 먼저 생성되는 변수 뒤에 나중에 생성되는 자료형, 인스턴스를 사용하지 않도록 주의해야 된다.
제네릭 클래스 사용하기
1 2 3 4 5 6 7 8 9 10 | //Powder.java public class Powder { public void doPrinting() { System.out.println("Powder 재료로 출력합니다."); } public String toString() { return "재료는 Powder입니다."; } } | cs |
1 2 3 4 5 6 7 8 9 10 11 | //Plastic.java public class Plastic { public void doPrinting() { System.out.println("Plastic 재료로 출력합니다."); } public String toString() { return "재료는 Plastic입니다."; } } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //GenericPrinter.java public class GenericPrinter<T> { //제네릭 자료형 사용하기 private T material; public void setMaterail(T material) { this.material = material; } public T getMaterial() { return material; } public String toString() { return material.toString(); } } | cs |
다이아몬드 연산자 사이에 T를 넣어줌으로써 만약 코드에서 T자리에 Powder이 들어간다면 위 코드에 있는 T를 모두 Powder로 바꾸어 컴파일 한다. 오호!
추상 클랙스를 상속받은 제네릭 데이터형 / 제네릭 데이터형 활용
먼저 추상 클래스 Material.java
를 만들어 줍니다.
1 2 3 4 5 | //Material.java public abstract class Material { public abstract void doPrinting(); } | cs |
그런다음 GenericPrinter.java
를 수정해줍니다. 왜냐하면 추상클래스 자체가 메서드만 선언되어있지 코드가 작성되지 않았으므로 무슨 역할을 하는 메서드인지를 알려주어야 하기 때문입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //GenericPrinter.java Material상속받음 public class GenericPrinter<T extends Material> { //제네릭 자료형 사용하기 + Material 상속 private T material; public void setMaterial(T material) { this.material = material; } public T getMaterial() { return material; } public String toString() { return material.toString(); } public void printing() { material.doPrinting(); //상위 클래스 Material의 메서드 호출 / 오버라이딩 } } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //GenericPriner.java public class GenericPrinterTest2 { 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(); } } | cs |
제네릭 메서드 활용하기
제네릭 메서드와 제네릭 클래스는 엄청난 차이가 있다. 위에서 제네릭 타입인 T를 보면 클래스 내에서는 전역 변수로 사용될 수 있다. 하지만 메서드 같은 경우는 메서드에서만 사용할 수 있으므로 지역 변수 형태를 띄고 있다. 제네릭 메서드를 선언하는 방법은 다음과 같다.
**public <자료형 매개변수=""> 반환형 메서드 이름(자료형 매개변스 ...) {}**
자료형>
public static <T, V> double makeRectangle(Point<T, V> p1, Point<T, V> p2) {}
이렇게 선언을 하면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //Point.java 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; } } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //GenericMethod.java 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.getX()).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); } } | cs |
코드를 보면, 제네릭 메서드를 호출할 때 역시 반환형 앞에 <Integer, Double>과 같은 데이터형을 알려주는 다이아몬드 연산자가 필요하다. 하지만 JDK 1.7 이상부터는 <>를 생략해도 된다고 한다.
댓글남기기