본문 바로가기
Back-End/Java

[Back-End][Java] 37. 제네릭(Generic) 프로그래밍 (1)

by nanee_ 2021. 9. 26.
728x90
반응형
SMALL

제네릭(Generic) 프로그래밍

: 다양한 자료형이 적용될 수 있는 클래스를 만드는 것

 

제네릭 프로그래밍을 사용해야하는 이유?

일반적으로 변수를 선언할 때 자료형을 먼저 지정하게 되어있다.

int num; double num; String name; 과 같이 말이다.

그런데, 여러 자료형으로 사용되어야할 변수가 필요한 경우가 있다. 

이 때, 우리는 제네릭 프로그래밍을 설계해 사용할 수 있다.

 

변수를 기반으로 클래스를 만든다고 가정할 때,

자료형으로 사용되기 때문에 여러 개의 클래스를 만들게 하거나 또는 변수명을 달리해서 코드에 복잡도, 즉 가독성이 떨어지게 된다.

그렇기 때문에 여러 개의 클래스를 만들게 된다면 너무 오버헤드(Overhead)된다.

 

제네릭 자료형

클래스에서 사용하는 변수의 자료형이 여러 개 일 수도 있고, 그 메서드는 동일한 경우 클래스의 자료형을 특정하지 않고

추후에 해당 클래스를 사용할 때 자료형을 지정할 수 있도록 지정해 두는 것이다.

 

 

예제코드

제네릭(Generic)을 사용하지 않는 경우와 사용하는 경우를 비교해보겠다.

 

1. 사용하지 않는 경우

Plastic.java

public class Plastic {

  @Override
  public String toString() {
    return "재료는 plastic 입니다.";
  }
}

 

Powder.java

public class Powder {

  @Override
  public String toString() {
  	return "재료는 Powder 입니다.";
  }
}

 

3D Printer의 재료가 되는 Plastic과 Powder를 toString을 재정의한 각 클래스를 생성해 준다.

 

ThreeDPrinter1.java

public class ThreeDPrinter1 {
	
  // member variable
  private Powder material;

  // getter setter
  public Powder getMaterial() {
    return material;
  }

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

}

1번 3D 프린터기에는 Powder를 재료로 사용한다.

Powder 타입으로 지정한 변수를 선언해 getter setter를 만들어 주었다.

 

ThreeDPrinter2.java

public class ThreeDPrinter2 {

  // member variable
  private Plastic material;

  // getter setter
  public Plastic getMaterial() {
    return material;
  }

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

}

 

2번 3D 프린터기에는 Plastic을 재료로 사용한다.

Plastic 타입으로 지정한 변수를 선언해 getter setter를 만들어 주었다.

 

ThreeDPrinter3.java

public class ThreeDPrinter3 {

  private Object material;

  public Object getMaterial() {
    return material;
  }

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

 

앞서 포스팅했던 클래스들의 최상위 클래스인 Object의 타입인 재료를 설정해주는 클래스도 getter setter를 생성해 만들어주었다.

 

mainTest.java

public class MainTest1 {
	
  public static void main(String[] args) {

    // 재료 객체화
    Powder powder = new Powder();
    Plastic  plastic = new Plastic();

    // 객체 생성
    ThreeDPrinter1 dPrinter1 = new ThreeDPrinter1();

    // 재료 세팅
    dPrinter1.setMaterial(powder);

    // 재료를 꺼내서 확인
    Powder tempPowder = dPrinter1.getMaterial();
    System.out.println(tempPowder.toString());

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

    ///////
    ThreeDPrinter2 dPrinter2 = new ThreeDPrinter2();
    dPrinter2.setMaterial(plastic);
    Plastic tempPlastic = dPrinter2.getMaterial();
    System.out.println(tempPlastic.toString());

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

    //////
    ThreeDPrinter3 dPrint3 = new ThreeDPrinter3();
    dPrint3.setMaterial(powder);
    Powder tempPowder2 = (Powder)dPrint3.getMaterial();
    System.out.println(tempPowder2.toString());		
  }	
}

 

main 함수가 있는 클래스에서 테스트를 해보겠다.

 

재료 Plastic, Powder와 프린터기를 객체화해서 힙 메모리 영역에 올려준다.

 

프린터기 클래스 안의 setMaterial 메서드를 활용해 재료를 객체화한 객체명으로 해당 메서드의 매개변수로 넣어준다.

그럼 재료가 세팅이 되는 것이다.

 

넣은 재료가 맞게 잘 들어갔는지 확인하기 위해 getMaterial 메서드를 활용해 가져오고,

그것을 해당 재료의 자료형으로 temp라는 변수를 만들어 담아준다.

temp 변수에 .연산자로 재정의 해놓은 toString 메서드를 활용해 재료를 확인해본다.

 

 

실행을 해보면 넣은 재료가 잘 알맞게 들어가있는 것을 확인할 수 있다.

 

 

그런데 우리는 여기서 프린터기가 자료형만 다르고 같은 변수명과 getter setter를 동일하게 사용하고 있는 것을 발견할 수 있다.

이 때, 우리는 제네릭 프로그래밍을 통해 더욱 간단하고, 유지보수에 용이한 코드를 짤 수 있다.

 

 

2. 사용하는 경우

 

 

위에 만들어 놓은 Plastic, Powder 클래스는 그대로 사용한다.

 

GenericPrinter.java

public class GenericPrinter<T> {

  // 멤버 변수
  private T material;

  // getter setter
  public T getMaterial() {
  return material;
  }

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

  // toString 재정의
  @Override
  public String toString() {
  return material.toString();
  }
}

 

T Type의 약자로 개발자들끼리 암묵적으로 사용하는 표기이다.

이 외의 E(Element), K(Key), V(Value) 등이 있다.

 

아무 문자나 넣어주면 되는데 이왕이면 위와 같은 대체 문자를 활용해 코드를 짠다면

다른 개발자와 코드를 공유할 때 이해하는데 도움을 준다.

 

int, double 등과 같은 자료형 매개변수(Type Parameter)를 사용할 때에는

이 클래스를 사용하는 시점에서 실제로 사용할 자료형을 지정해서

클래스를 선언하고 코딩을 하고 저장을 하는 컴파일 과정을 거치게 되는데, 

대체 문자를 활용하는 제네릭 자료형을 쓰면 자동적으로 변환되기 때문에 간편하다.

 

GenericPrinterTest.java

public class GenericPrinterTest {

  public static void main(String[] args) {
  
    // 재료 세팅
    Powder powder = new Powder();
    Plastic plastic = new Plastic();

    // 파우더를 재료로 하는 프린터 객체 생성
    GenericPrinter<Powder> genericPrinter = new GenericPrinter<>(); 

    // 재료 넣기
    genericPrinter.setMaterial(powder);

    // 재료 꺼내기
    Powder tempPowder = genericPrinter.getMaterial();
    System.out.println(tempPowder.toString());

    // 플라스틱을 재료로 하는 프린터 객체 생성
    GenericPrinter<Plastic> genericPrinter2 = new GenericPrinter<>();
    genericPrinter2.setMaterial(plastic);
    Plastic tempPlastic = genericPrinter2.getMaterial();
    System.out.println(tempPlastic.toString());
  }
}

 

main 함수가 있는 클래스에서 구현을 해보겠다.

 

테스트를 하는 과정은 앞서 했던 과정과 동일하다.

재료를 세팅(객체화)하고, 그 재료의 타입으로 매개변수를 가지는 프린터 객체를 생성해 

재료를 넣고, 재료를 꺼내 재정의된 toString으로 재료를 확인해 본다.

 

 제네릭을 사용하는 방법

사용을 할 때 그에 맞는 자료형을 T 대신에 <>안에 넣어주면 된다.

new 뒤의 <>에는 이클립스 내부에서 타입의 추론이 가능하기 때문에 생략해도 무방하다.

 

실행을 해보면 재료를 넣은대로 잘 된 것을 확인할 수 있다.

 

 

 

제네릭을 사용하지 않는 경우에는 프린터기 별로 하나의 클래스를 생성해 주며 같은 변수와 메소드들을 정의해 주었는데,

제네릭 프로그래밍을 사용하면서 하나의 프린터기 클래스를 만들어놓고 사용할 때 원하는 매개변수를 넣어서 사용할 수 있었다.

메모리 공간도 훨씬 적게 차지하고, 훨씬 편리하다.

 

 

 

 

 

 

728x90
반응형
LIST