java week14, 제네릭


목차



제네릭이란?


  • 데이터 타입(data type)을 일반화(generalize)하는 것을 의미한다.
  • 제네릭은 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는ㄴ 방법이다.
  • 컴파일 시 type check를 한다면
    • 클래스나 메소드 내부에서 사용되는 객체의 타입의 안정성을 높일 수 있다.
    • 반환값에 대한 타입 변환 및 타입 검사에 들어가는 노력을 줄일 수 있다.
  • Java 5 이전에는 여러 타입을 사용하는 대부분의 클래스나 메소드에서 인수나 반환 값으로 Object타입을 사용했었다. 이 경우에는 반환된 Object 개게를 다시 원하는 타입으로 타입을 변환해야하고, 이때 오류가 발생환 가능성이 생긴다. 제네릭을 사용하면서 컴파일 시에 미리 타입이 정해지므로, 타입 검사나 타입 변환과 같은 번거로운 작업을 생략할 수 있게 된다.

제네릭 사용법


제네릭의 선언 및 새성

class GenericSample<T>{
    T elements;
    void setElement(T element){
        this.element = element;
    }

    T getElement(){
        return element;
    }
}

타입변수

  • 아무런 이름이나 지정해도 컴파일하는데 지장이 업사.
  • 현존하는 클래스를 사용해도 되고 존재하지 않는 것을 사용해도 된다.
  • 임의의 참조형 타이을 의미한다.
  • 꼭 ‘T’를 사용안하고 어떠한 문자를 사용해도 되지만 아래의 네이밍을 지켜주는 것이 좋다.
  • 여러 개의 타입 변수는 쉼표로 구분하여 명시할 수 있다.
  • 타입 변수는 클래스에서뿐만 아니라 메소드의 매게변수나 반환값으로도 사용할 수 있다.

제네릭 타입의 이름 정하기

E : 요소 (Element, 자바 컬렉션에서 주로 사용) K : 키 N : 숫자 T : 타입 V : 값 S, U, V : 두번 재, 세번 째, 네 번째에 선언된 타입

예제

class GenericSample<T> {
    T element;

    public static void main(String[] args) {
        GenericSample<Integer> integerGenericSample = new GenericSample<>();
        integerGenericSample.setElement(3);
        GenericSample<String> stringGenericSample = new GenericSample<>();
        stringGenericSample.setElement("thewing");
        System.out.println("integerGenericSample.getElement() = " + integerGenericSample.getElement());
        System.out.println("stringGenericSample.getElement() = " + stringGenericSample.getElement());
    }

    public void setElement(T element) {
        this.element = element;
    }
    public T getElement () {
        return element;
    }
}

결과

integerGenericSample.getElement() = 3
stringGenericSample.getElement() = thewing

제네릭 주요 개념 (바운디드 타입, 와일드 카드)


바운드 타입 매개변수

바운드 타입은 특정 타입의 서브 타입으로 제한하다. 클래스나 인터페이스 설계할 때 가장 흔하게 사용할 정도로 많이 볼 수 있다.

예제

public class BoundTypeSample <T extends Number>{
    public void set(T value) {}

    public static void main(String[] args) {
        BoundTypeSample<Integer> boundTypeSample = new BoundTypeSample<>();
        boundTypeSample.set("Hi");
    }
}

위 코드일 경우 IntegerNumber의 서브타입이기 때문에 컴파일 에러가 나지 않지만 set에 Integer가 아닌 문자열을 전달하여 컴파일 에러가 발생한다.

WildCard

제네릭으로 구현된 메소드의 경우 선언된 타입으로만 매개변수를 입력해야한다. 이를 상속받은 클래스 혹은 부모 클래스를 사용하고 싶어도 불가능하고 어떤 타입이 와도 상관없는 경우에 대응하기 좋지 않다. 이를 위한 해법으로 Wildcard를 사용한다.

와일드 카드 종류

Unbounded WildCard

Unbounded Wildcard는 List<?> 와 같은 형태로 물음표만 가지고 정의되어지게된다. 내부적으로 Object로 정의되어서 사용되고 모든 타입의 인자를 받을 수 있다. 타입 파라미터에 의존하지 않는 메소드만을 사용하거나 Object 메소드에서 제공하는 기능으로 충분한 경우에 사용한다.

Object 클래스에서 제공되는 기능을 사용하여 구현할 수 있는 메서드를 작성하는 경우

타입 파라미터에 의존적이지 않은 일반 클래스의 메소드를 사용하는 경우

ex) List.clear, List.size

Upper Bounded Wildcard

List<? extends Foo> 와 같으 형태로, 특정 클래스의 자식 클래스만을 인자로 받는다는 것이다. 임의의 Foo 클래스를 상속받는 어느 클래스가 와도 되지만 사용할 수 있는 기능은 Foo 클래스에 정의된 기능만 사용이 가능하다.

Lower Bounded Wildcard

List<? super Foo>와 같은 형태로 사용하고, Upper Bounded Wildcard와 다르게 특정 클래스의 부모 클래스만을 인자로 받는다는 것이다.


제네릭 메소드 만들기


  • 제네릭 메소드란 메소드의 선언부에타입 변수를 사용한 메소드를 의미한다
  • 이때 타입 변수의 선언은 메소드 선언부에서 반환 타입 바로 앞에 위치한다

    예제

    ```java import java.util.Comparator; import java.util.List;

public class Collections { public static void sort(List list, Comparator<? super T> c) { list.sort(c); }

}

### Erasure
제네릭의 타입 소거(Generics Type Erasure)
- 원소 타입을 컴파일 타임에서만 검사를하고 런타임에는 해당 타입 정보를 알 수가 없다. 
- 컴파일 상태에만 제약 조건을 적용하고, 런타임에는 타입에 대한 정보를 소거하는 프로세스이다
- 제네릭 타입을 사용한 자바 클래스
```java
public class GenericErasureExample {

  public static void main(String[] args) {
      List<String> myList = new ArrayList<String>();
      myList.add("Hello");
      myList.add("World");

      String hello = myList.get(0);
      String world = myList.get(1);

      System.out.println(hello + " " + world);
  }
}
  • 디컴파일 결과
    public class GenericErasureExample {
    public GenericErasureExample() {
    }
    
    public static void main(String[] args) {
        List<String> myList = new ArrayList();
        myList.add("Hello");
        myList.add("World");
        String hello = (String)myList.get(0);
        String world = (String)myList.get(1);
        System.out.println(hello + " " + world);
    }
    }
    

    원래 작성한 자바 파일과 디컴파일한 코드를 잘 비교해 보면 제네릭 타입 선언 등의 내용이 삭제되고 변환된 것을 볼 수 있다. 이를 제네릭 삭제(Generic Erasure) 라고 한다.

    List<String> myList = new ArrayList<String>(); // 코딩 내용
    ArrayList myList = new ArrayList(); // 디컴파일 결과 
    
  • 변수명은 동일하지만 제네릭 타입 파라미터는 제거되었다. 타입 파라미터는 컴파일러에 의해 해석되는 부분이고 자바 가상 머신에서는 해석이 되지 않기 때문이다. 그래서 제네릭은 런타임에 체크하는 것이 아니라 컴파일 시에 정합성을 체크하게 된다.
  • 위의 비교로 알 수 있는 제네릭 관련 원칙
    • 제네릭은 컴파일 시에 해석되고 바이트 코드로 변환될 때는 제거된다. 즉, 자바 가상 머신은 제네릭을 고려하지 않고 실행되며 제네릭이 제거된 기본 클래스형으로만 처리한다.
    • 클래스 선언 시 사용된 제네렉 타입은 제거되며 메서드에서 리턴 받을 때는 컴파일러에 의해 형 변환된 코드가 자동 추가 된다.
  • 자바 가상 머신상에서 제네릭 코드를 제거하는 이유는 제네릭을 해석하기 위한 추가적인 자원 소모를 없애고 자바 가상 머신이 빠르게, 그리고 명확하게 동작하도록 하기 위해서다.





© 2019.04. by salmon2

Powered by theorydb