ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] 제네릭 타입 정리하기();
    Java 2021. 4. 25. 23:44
    반응형

    API document을 보다가 제네릭 타입 표현이 많아 이번 기회에 다시 한번 제네릭을 정리하기로 결심하고 오랜만에
    자바 책을 다시 펼치게 되었다는...

    제네릭은 Java 5에서 처음 등장하였으며,클래스와 인터페이스, 메소드를 정의할 때, 타입을 파라미터로 사용하고, 파라미터는 코드 작성 시 구체적인 타입으로 대체되어 동적인 코드를 생성하도록 도와주는 기술이다.


    제네릭 클래스, 제네릭 인터페이스

    텍스트로 보면 곧바로 와닿지 않는다. 바로 코드를 보자.

    @Getter
    public class Height<T> {
        private T t;
    }

    위 클래스는 간단한 Height 클래스이며, 클래스명 뒤에 제네릭 클래스를 의미하는 <> 부호가 붙어있다. 해당하는 부호 사이에 타입 파라미터를 나타내는 T 문자열이 있으며 일반적으로 대문자 알파벳 한 글자로 표현하곤 한다.

    타입 파라미터는 Height 클래스를 실제 코드로 사용할 때, 구체적인 타입을 지정해서 구체화 한다.

    public class GenericMain {
    
        public static void main(String[] args) {
    
        // 타입 파라미터 Integer
            Height<Integer> heightA = new Height<>();
    
            // 타입 파라미터 Double
            Height<Double> heightB = new Height<>();
    
        }
    
    }

    변수명 heightA는 타입 파라미터를 Integer로 지정하였고, 변수명 heightB는 타입 파라미터를 Double로 지정하였다. 각 각의 타입 파라미터는 Height 객체의 임의의 타입파라미터 T와 매핑되어, 정수형, 실수형 객체 인스턴스가 생성된다.

    제네릭을 통해 하나의 클래스로 동적인 타입을 가지는 객체를 생성하는 것은 장점이 많다.

    예를 들어, 제네릭 타입이 아닌 모든 객체의 최상위 객체인 java.lang.Object 을 통해 정수형과 실수형 데이터를 받는다고 가정해보자.

    @Getter
    public class HeightV2 {
        private Object o;
    }
    public class GenericMain {
    
        public static void main(String[] args) {
    
            HeightV2 heightC = new HeightV2();
            // 정수형, 자동타입변환
            heightC.setValue(178);
        // 강제타입변환
            Integer a = (Integer) heightC.getValue();
    
    
            HeightV2 heightD = new HeightV2();
            // 실수형, 자동타입변환
            heightD.setValue(180.4);
        // 강제타입변환
            double b = (double) heightD.getValue();
    
        }
    
    }

    setter 메소드를 통해 정수형, 실수형 데이터를 넣을 땐, Object 타입으로 자동타입변환이 이루어지며, getter 메소드를 통해 데이터를 가져올 때는 강제타입변환이 필요하다. 이 과정은 시스템 자원을 낭비하는 코딩이 될 수 있다.(물론 미미하겠지만..)

    위와 같은 상황에서 제네릭 타입을 사용하면,

    Height<Integer> heightA = new Height<>();
    heightA.setT(178);
    Integer t = heightA.getT();
    
    Height<Double> heightB = new Height<>();
    heightB.setT(180.4);
    Double d = heightB.getT();

    저장할 때와 불러올 때 모두 자동타입변환 및 강제타입변환이 발생하지 않는다.


    제네릭 메소드

    제네릭 메소드는 리턴타입 앞에 <> 기호를 추가하고, 타입파리머터 및 리턴 타입, 매개 타입을 타입 파라미터의 타입으로 기술하여 사용한다.

    public static <T> Height<T> makeHeight(T t) {
        Height<T> height = new Height<>();
        height.setT(t);
        return height;
    }
    public static void main(String[] args) {
        Height<Integer> heightE = makeHeight(178);
        Height<Double> heightF = makeHeight(180.4);
    }

    매개값을 보고 구체적인 타입을 추정하여 제네릭 메소드를 호출할 수 있다.


    제한된 타입 파라미터

    제네릭 타입에 타입 파라미터를 제한할 수 있다. 타입 파라미터를 제한하기 위해서는 타입 파라미터 뒤에 extends 키워드를 통해 상위 타입을 명시한다.

    public class A extends B{
        ...
    }
    
    public class B {
        ...
    }
    
    public class C {
        ...
    }

    B 클래스를 상속한 A 클래스, B 클래스, C 클래스

    public static <T extends B> void makeAlphabet(T t) {
        if (t instanceof A) {
            System.out.println("Type A");
        } else if (t instanceof B) {
            System.out.println("Type B");
        } else if (t instanceof C) {
            System.out.println("Type C");
        } else {
            System.out.println("no matching");
        }
    }
    makeAlphabet(new A()); // 허용
    makeAlphabet(new B()); // 허용
    makeAlphabet(new C()); // 허용되지 않음

    instanceof 연산자를 사용하여 매개타입을 알아보는 메소드가 있다. 이를 통하여 타입 파라미터의 제한을 확인해 보자.

    <T extends B> 타입 파라미터를 통하여, 타입 파라미터는 B 타입을 최상위 타입으로 제한할 것이라는 것을 명시해주었다. 따라서 B 클래스를 상속한 A 클래스, B 클래스는 해당 메소드를 호출할 수 있지만, C 클래스의 경우, 제한된 타입 파라미터로 인식되어 컴파일 에러가 발생한다.

    반응형

    댓글

Designed by Tistory.