ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java - 컬렉션 프레임워크(제네릭)1-1
    개발언어/JAVA 2023. 11. 7. 13:46

    제네릭

    제네릭이란?

    프로그램에서 변수를 선언할 때 모든 변수는 자료형이 있다. 메서드에서 매개변수를 사용할 때도 자료형을 가지고 있다. 대부분은 하나의 자료형으로 구현한지만, 변수나 메서드의 자료형을 필요에 다라 여러 자료형으로 바꿀 수 있다면 프로그램이 훨씬 유연할 것이다. 이와 같이 어떤 값이 하나의 참조 자료형이 아닌 여러 참조 자료형을 사용할 수 있도록 프로그래밍하는 것을 '제네릭(Generic) 프로그래밍'이라고 한다. 제네릭 프로그램은 참조 자료형이 변환될 때 이에 대한 검증을 컴파일러가 하므로 안정적이다. 

     

    제네릭의 필요성

    3D프린터를 예로 들어 제네릭에 대해 이해해보자  3D프린터는 재료를 가지고 입체 모형을 만드는 일을 한다. 프린터에 스이는 재료는 여러가지가 있을 수 있는데, 쌓아 올려 입체 모형을 만드는 경우에 파우더나 플라스틱 액체와 같은 비롯한 여러가지 재료를 사용한다.

     

    public class ThreeDPrinter {
        private Powder material; // 재료가 플라스틱인 경우
    
        public Powder getMaterial() {
            return material;
        }
    
        public void setMaterial(Powder material) {
            this.material = material;
        }
    }

     

    public class ThreeDPrinter {
        private Plastic material; // 재료가 파우더인 경우
    
        public Plastic getMaterial() {
            return material;
        }
    
        public void setMaterial(Plastic material) {
            this.material = material;
        }
    }

     

    public class ThreeDPrinter {
        private Object material; // 재료가 파우더인 경우
    
        public Object getMaterial() {
            return material;
        }
    
        public void setMaterial(Object material) {
            this.material = material;
        }
    }

     

    그런데 재료만 바뀌었을 뿐 프린터 기능이 동일하다면 프린터 클래스를 두 개 만드는 것은 비효율 적이다. 이런 경우에는 어떤 재료든 쓸 수 있도록 material변수의 자료형을 Object로 사용할 수도 있다. 

     

    ThreeDPrinter printer = new ThreeDPrinter();
    
    Powder p1 = new Powder();
    
    //자동 형 변환됨
    printer.setMaterial(p1);
    
    //직접 형 변환 해야 됨
    Power p2 = (Power)printer.getMaterial();\

     

    setMaterial()메서드를 활용하여 Power를 재료로 선택할 때는 매개변수 자료형이 Object이므로 자동으로 형 변환이 된다. 하지만 ThreeDPrinter의 getMaterial()메서드의 반환형 역시 Object 형 이기 때문에 메서드로 Powder 자료형 변수를 받을 때는 반드시 형변환 해주어야 한다. 즉 어떤 변수가 여러 참조 자료형을 사용할 수 있도록 Object클래스를 사용하면 다시 원래 자료형으로 반환해 주기 위해 매번 형 변환을 해야 하는 번거로움이있다. 이런 경우에 사용하는 프로그래밍 방식이 제네릭이다. 여러 참조 자료형이 쓰일 수 있는 곳에 특정한 자료헝을 지정하지 않고, 클래스 나 메서드 정의한 후 사용하는 시점에 어떤 자료형을 사용할 것인지 지정하는 방식이다.

     

    제네릭 클래스 정의하기

    package genericex;
    
    //제네릭 클래스
    //type의 약자 T
    //매개변수 자료형
    public class GenericPrinter <T> {
        //T자료형을 선언한 변수
        private T meterial;
    
        //T자료형 변수 material을 반환하는
        //제네릭 메서드
        public void setMeterial(T meterial){
            this.meterial = meterial;
        }
    
        public T getMeterial(){
            return meterial;
        }
    }

     

    제네릭에서는 여러 참조 자료형이 들어 갈 수도 있는 변수의 타입을 Object가 아닌 하나의 문자로 표현한다. 앞에서 예로든 ThreeDPrinter를 제네릭 클래스로 정의 하면 위코드와 같다.

     

    코드를 자세히 살펴 보면 여러 자료형으로 바꾸어 사용할 material 변수의 자료형을 T라고 썻다. 이때 T를 자료형 매개변수(type parameter)라고 부른다. 클래스 이름을 GenericPrinter<T>라고 정의하고 나중에 클래스를 사용할 때(인스턴스를 생성할 때) T 위치에 실제 사용할 자료형을 지정한다. 클래스의 각 메서드에서 해당 자료형 명시가 필요한 부분에는 모두 T문자를 사용하여 구현한다.

     

    다이아몬드 연산자<>

    //생성부분의 자료형 생략가능
    ArrayList<String> list = new ArrayList< >();

     

    "<>"를 다이아몬드 연산자라고 한다. 선언된 자료형을 보고 생략된 부분이 String임을 컴파일러가 유추할 수 있기 때문에 생서 부분에서는 생략할 수 있다.

     

    자료형 매개변수 T와 static

    static 변수나 메서드는 인스턴스를 생성하지 않아도 클래스 이름으로 호출할 수 있다. static 변수는 인스턴스 변수가 생성되기 이전에 생성된다. 또한 static 메서드에서는 인스턴스 변수를 사용할 수 없다. 왜냐하면 T의 자료형이 정해지는 순간은 제네릭 클래스의 인스턴스가 생성되는 순간이다. 따라서 T의 자료형이 결정회는 시점보다 static변수나 static메서드가 정의되는 시점이 빠르기때문에 static 변수의 자료형이나 static메서드 내부 변수의 자료형으로 T를 사용할 수 없다.

     

    자료형 매개변수로 T 외에 다른 문자도 사용할 수 있다. E는 element, K는 key, V는 value를 의미한다. 의미가 그렇다는 것이지 꼭 해당하는 문자를 사용해야 하는 것은 아니다. A, B등 아무 문자나 사용해서 정의할 수도 있다.


    재네릭 클래스 사용하기

    GenericPrinter<Powder> powderPrinter = new GenericPrinter<Powder>();
    powderPrinter.setMeterial(new Powder());
    //명시적 형 변환을 하지 않음
    Powder powder = powderPrinter.getMeterial();
    용어 설명
    GenericPrinter<Powder> 제네릭 자료형(Generic type), 매개변수화된 자료형(parameterize)
    Powder 대입된 자료형

     

    T로 정의한 클래스 부분에 Power형을 넣어 주고, T형 매개변수가 필요한 메서드에 Powder클래스를 생성햐여 대입해준다. GenericPrinter<Powder>에서 어떤 자료형을 사용할지 명시했으므로 GenericPrinter<Powder>의 getMaterial()메서드에 반환할 때 형 변환을 하지 않는다. 이렇게 실제 제네릭 클래스를 사용할 때 T 위치에 사용한 Powder형을 '대입된 자료형'이라고 하고, Powder를 대입해 만든 GenericPrinter<Powder>를 '제네릭 자료형(Generic type)'이라고 한다.

     

    제네릭으로 구현하면 왜 형변환을 하지 않을까? 제네릭 클래스를 사용하면 컴파일러는 일단 대입되 자료형이 잘 쓰였는지 확인한다. 그리고 class 파일을 생성할 대 T를 사용한 곳에 지정된 자료형에 따라 컴파일하므로 형 변환을 하지 않아도 된다. 따라서 제네릭을 사용하면 컴파일러가 자료형을 확인해 주기 때문에 안정적이면서 형 변환 코드가 줄어든다.

     

    제네릭 클래스 사용 예제

    Powder.java

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

     

    Plastic.java

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

     

    GenericPrinter<T>

    //제네릭 클래스
    //type의 약자 T
    //매개변수 자료형
    public class GenericPrinter <T> {
        //T자료형을 선언한 변수
        private T meterial;
    
        //T자료형 변수 material을 반환하는
        //제네릭 메서드
        public void setMeterial(T meterial){
            this.meterial = meterial;
        }
    
        public T getMeterial(){
            return meterial;
        }
    
        @Override
        public String toString() {
            return meterial.toString();
        }
    }

     

    메서드 선언부나 메서드의 매개변수로 자료형 매개변수 T를 사용한 메서드를 '제네릭 메서드(generic method)'라고 한다. 제네릭 메서드는 일반 메서드 뿐아니라 static 메서드에서도 활용할 수 있다.

     

    GenericPrinter<T> 클래스 사용하기

    package genericex;
    
    public class GenericPrinterTest {
        public static void main(String[] args) {
            //Powder형으로 GenericPrinter 클래스 생성
            GenericPrinter<Powder> powderPrinter = new GenericPrinter<Powder>();
    
            powderPrinter.setMeterial(new Powder());
            Powder powder = powderPrinter.getMeterial();
            System.out.println(powderPrinter);
            //재료는 Powder입니다.
    
            //Plastic형으로 GenericPrinter 클래스 생성
            GenericPrinter<Plastic> plasticPrinter = new GenericPrinter<Plastic>();
    
            plasticPrinter.setMeterial(new Plastic());
            Plastic plastic = plasticPrinter.getMeterial();
            System.out.println(plasticPrinter);
            //재료는 Plastic입니다.
        }
    }

     

    [출저 - Do it! 자바 프로그래밍 입문 , 박은종]

    http://www.easyspub.co.kr/20_Menu/BookView/A001/267/PUB

     

    http://www.easyspub.co.kr/20_Menu/BookView/A001/267/PUB

     

    www.easyspub.co.kr

     

    댓글

Designed by Tistory.