ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JAVA] Generic Type
    Language/Java 2020. 2. 1. 13:08

     

    오늘은 제네릭 타입에 대해 알아보겠습니다.

     

    Generic Type?

     

    제네릭 타입은 클래스나 메소드를 작성할 때, 사용할 객체 타입을 지정할 수 있도록 하는 기능입니다.

    지정할 타입을 꺾쇠(<>)로 감싸 표현해, 해당 데이터 타입만을 사용하도록 타입을 제한할 수 있습니다.

     

    이러한 제네릭 타입을 사용하는 이유는 무엇일까요?

     

    - 컴파일 시 에러 체크 가능

     

    타입이 일치하지 않는 에러가 컴파일 단계에서 검출되지 않는 경우가 종종 발생합니다.

    제네릭 타입을 사용하면 타입이 일치하지 않는 경우 실행 단계 이전인 컴파일 단계에서 타입 에러를 감지할 수 있습니다.

     

    - 불필요한 타입 변환 생략 가능

     

    구체적인 타입을 지정해, 불필요하게 타입을 변환하는 과정을 생략할 수 있습니다.

    List list = new ArrayList();
    
    list.add("Hi");
    
    String str = list.get(0); // 타입 불일치
    String str = (String) list.get(0); // 형변환

    타입을 지정해주지 않은 list의 각각의 아이템들은 Object타입입니다. 우리는 list변수에 "Hi"라는 문자열을 넣었지만 list에서는 이를 Object타입으로 받아들이고 반환합니다. 그러므로 get() 메소드를 통해 얻은 Object 타입의 데이터를 String타입의 변수에 담으면 에러가 발생하는 것입니다.

    이는 명시적 형변환을 수행함으로써 해결할 수 있습니다. 하지만 제네릭 타입을 이용하면 이러한 형변환의 과정을 거치지 않아도 됩니다.

    List<String> list = new ArrayList<>();
    
    list.add("Hi");
    
    String str = list.get(0);

    반면, 다음과 같이 String을 제네릭 타입으로 지정해서 만든 list에 데이터를 넣어주면 String타입으로 데이터를 받아들입니다. 그래서 String 타입의 변수에 형변환 없이 데이터를 대입할 수 있습니다.

     

    Generic Class

     

    제네릭 타입 클래스의 경우 클래스명 다음에  꺾쇠(<>)를 사용해 파라미터를 지정합니다. 보통 알파벳 대문자로 표시합니다.

    class Test<T>{
    	private T t;
    
    	public T getT() {
    		return t;
    	}
    
    	public void setT(T t) {
    		this.t = t;
    	}	
    }
    
    public class Main {
    
    	public static void main(String[] args) {
    
    		Test<String> t1 =new Test<String>();
    		t1.setT("Hello");
    		String str = t1.getT();
    		System.out.println(str);
    	}
    
    }

     

    위 코드의 경우 Test<String> 타입의 객체를 생성했으므로, Test클래스의 멤버변수인 t는 String 타입의 변수가 됩니다.

     

    class Test<T, K>{
    
    	private T t;
    	private K k;
        
    	public T getT() {
    		return t;
    	}
    
    	public void setT(T t) {
    		this.t = t;
    	}	
        
        	public K getK() {
    		return k;
    	}
    
    	public void setK(K k) {
    		this.k = k;
    	}
    }

    위와 같이 하나 이상의 파라미터를 콤마(,)로 연결하여 사용하는 것도 가능합니다.

     

    Generic Method

    제네릭 메소드는 매개 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드를 말합니다. 리턴 타입 앞에 꺾쇠(<>)를 이용해 타입 파라미터를 지정하고, 리턴 타입과 매개타입으로 타입파라미터를 사용합니다.

     

    제네릭 메소드를 호출하는 방법에는 두 가지가 있습니다.

    public <타입파라미터, ...> 리턴타입 메소드명(매개변수, ...) { ... }
    
    Test<String> = <String>makeTest("Hi"); // 명시적으로 String타입을 지정
    
    Test<String> = makeTest("Hi"); // String타입으로 추정 (컴파일러가 매개 값을 보고 타입 추정)

     

    타입 제한

    타입 파라미터에 지정되는 구체적인 타입은 제한될 필요가 있습니다.

    예를 들어, 숫자를 연산하는 제네릭 메소드는 매개값으로 Number 타입 또는 하위 클래스 타입(Byte, Short, Integer, Long, Double)의 인스턴스만 가져야 합니다. 예상치 못한 타입이 파라미터로 지정되는 것을 막기 위해 이를 제한하는 방법이 있습니다.

     

    타입 파라미터 뒤에 extends 키워드를 붙이고 상위 타입을 명시하는 것입니다. 상위 타입은 클래스 뿐만 아니라 인터페이스도 가능합니다.

     

    class Test<T extends Number>{
    	// Test클래스의 제네릭 타입으로는 Number타입의 하위 타입들만 올 수 있음
    }

     

    WildCard Type

    class Test<T extends Number>{
    	T t;
    	Test<T> sub;
    	
    	public T getT() {
    		return t;
    	}
    
    	public void setT(T t) {
    		this.t = t;
    	}
    	
    	public void put(Test<T> test) {
    		sub.put(test);
    	}
    	
    }
    
    public class Main {
    
    	public static void main(String[] args) {
    		Test<Integer> testI = new Test<>();
    		Test<Number> testN = new Test<>();
    		testI.put(testN); //testI의 sub은 <Integer> 이고 testN은 <Number>이므로 에러발생
    	}
    
    }
    class Test<T extends Number>{
    	T t;
    	Test<? extends Number> sub;
    	
    	public T getT() {
    		return t;
    	}
    
    	public void setT(T t) {
    		this.t = t;
    	}
    	
    	public void put(Test<? extends Number> test) {
    		sub.put(test);
    	}
    	
    }
    
    public class Main {
    
    	public static void main(String[] args) {
    		Test<Integer> testI = new Test<>();
    		Test<Number> testN = new Test<>();
    		testI.put(testN); // 와일드카드 형식을 사용함으로써 testI에 testN를 넣을 수 있음
    	}
    
    }

    제네릭 코드에서 물음표(?)는 와일드카드로 불리며, 알 수 없는 타입을 나타냅니다. 와일드카드는 파라미터, 필드, 지역 변수의 타입 또는 때때로 반환 타입과 같은 다양한 상황에서 사용될 수 있습니다. 하지만,와일드 카드는 제네릭 메소드 호출에 대한 형식 인수, 제네릭 클래스 인스턴스 생성, 또는 슈퍼타입으로 사용될 수 없습니다.

     

    ? extends T, ? super T와 같은 형식으로 상한 또는 하한을 제한하여 사용할 수 있습니다.

     

    Type Erasure

     

    이러한 제네릭은 코드 작성시 타입 체크 기능을 제공하지만 실제로 컴파일 이후의 코드에서는 타입 정보가 남아있지 않습니다.

    자바에서는 제네릭 클래스를 인스턴스화 할 때 해당 타입을 지우기 때문입니다. 이를 제네릭의 Type Erasure 라고 합니다.

     

    https://medium.com/asuraiv/java-type-erasure%EC%9D%98-%ED%95%A8%EC%A0%95-ba9205e120a3

     

    [JAVA] Type Erasure의 함정

    필자의 회사는 많은 조직이 서비스 성능 향상을 도모하기 위해 ‘memcached’ 를 사용한다. (물론 ‘redis’와 같은 다른 솔루션을 사용하는 팀도 상당수 있긴 하다) 그리고 스프링 프레임워크 기반의 웹 어플리케이션과 연동하기 위해 AOP…

    medium.com

    Type Erasure에 대해 읽어볼만한 좋은 글이있어 소개하고 마무리합니다.

    'Language > Java' 카테고리의 다른 글

    [JAVA] Enum이란?  (0) 2020.03.15
    [JAVA] Reflection  (0) 2020.02.01
    [JAVA] Reader/Writer  (0) 2019.12.07
    [JAVA] InputStream/OutputStream  (0) 2019.12.07
    [JAVA] Thread와 Multi Thread  (0) 2019.11.12
Designed by Tistory.