제네릭

hyyyynjn·2021년 4월 11일
1

자바 스터디

목록 보기
7/15
post-thumbnail
  • 제네릭 타입을 컴파일 하면 class 파일에 제네릭이 남아있는지 확인하기. generic type erasure 에 대해서 설명하기

제네릭

  • 제네릭(Generic)
    • 뜻 : 데이터 타입을 일반화(Generalize)하는 것을 의미한다.
    • 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시 미리 지정하는 방법이다. (즉, 컴파일시 미리 타입검사를 수행하는 방법)
        1. 클래스나 메소드 내부에서 사용되는 객체의 안정성을 높일 수 있다.
        1. 반환값에 대한 타입 변환 및 타입 검사에 들어가는 노력을 줄일 수 있다.
  • Raw Type
    • 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않았을 때를 의미한다.
    • 권장하지 않는다.

제네릭 선언 및 생성

class MyArray<T> {

    T element;
    void setElement(T element) { this.element = element; }
    T getElement() { return element; }
}
  • T : 타입 변수(type variable). 임의의 참조형 타입을 의미한다.
    • T뿐만 아니라 어떠한 문자를 사용해도 상관 없다.
    • 여러개의 타입 변수는 ,로 구분하여 명시할 수 있다.
    • 타입 변수는 클래스뿐 아니라 메소드의 매개변수나 반환값으로 사용할 수 있다.
MyArray<Integer> myArr = new MyArray<Integer>();
MyArray<Integer> myArr = new MyArray<>(); // Java SE 7부터 가능함.
  • 제네릭 클래스를 생성할 경우 실제 타입을 명시하면 (Integer), 내부적으로 정의된 타입 변수가 명시된 실제 타입으로 변환되어 처리된다.
  • 실제 타입 명시할 때 기본타입(int)을 사용할 수 없고, Wrapper 클래스(Integer)를 사용해야한다.✅
  • Java SE 7부터 인스턴스 생성시 타입추정이 가능한 경우, 타입 생략이 가능하다.

제네릭 예제

import java.util.*;

class LandAnimal { public void crying() { System.out.println("육지동물"); } }
class Cat extends LandAnimal { public void crying() { System.out.println("냐옹냐옹"); } }
class Dog extends LandAnimal { public void crying() { System.out.println("멍멍"); } }

class Sparrow { public void crying() { System.out.println("짹짹"); } }


class AnimalList<T> {

    ArrayList<T> al = new ArrayList<T>();
    void add(T animal) { al.add(animal); }
    T get(int index) { return al.get(index); }
    boolean remove(T animal) { return al.remove(animal); }
    int size() { return al.size(); }

}
  • Cat과 Dog 클래스는 LandAnimal 클래스를 상속받는 자식 클래스이므로, AnimalList<LandAnimal>에 추가할 수 있다
  • 하지만 parrow 클래스는 타입이 다르므로 추가할 수 없다.

public class Generic01 {

    public static void main(String[] args) {

        AnimalList<LandAnimal> landAnimal = new AnimalList<>(); // Java SE 7부터 생략가능함.

        landAnimal.add(new LandAnimal());
        landAnimal.add(new Cat());
        landAnimal.add(new Dog());

        // landAnimal.add(new Sparrow()); // 오류가 발생함.

        for (int i = 0; i < landAnimal.size() ; i++) {
            landAnimal.get(i).crying();
        }
    }

}

제네릭의 제거 시기

  • 제네릭 타입은 컴파일시에 컴파일러에 의해 자동으로 검사되어 타입 변환된다.
  • 그리고 나서 모든 제네릭 타입은 제거되고 -> 🔥컴파일된 class 파일에는 어떠한 제네릭 타입도 포함되지 않게 된다.🔥
    • 제네릭을 사용하지 않은 코드와의 호환성 유지를 위해서이다.

Java Generics Type Erasure (제네릭 타입 소거)

구체화 vs 비구체화

  • 구체화 타입(reifiable type) : 자신의 타입 정보를 런타임에도 알고 있는 것
    • 배열이 구체화 타입에 해당한다.
  • 비구체화 타입(non-reify type) : 런타임에는 소거(erasure)가 되기 때문에 컴파일 타임보다 정보를 적게 가지는 것
    • 제네릭 타입이 비구체화 타입에 해당한다.
    • 제네릭은 컴파일 타임에 타입 체크를 한 뒤 런타임에는 타입을 지우는 방법을 사용하고 있다.

Generics Type Erasure이란

  • 소거(erasure)
    • 원소 타입을 컴파일 타입에만 검사하고 런테임에는 해당 타입 정보를 알 수 없는 것이다.
    • ✅ 다시말해, 컴파일 타입에만 타입 제약 조건을 정의하고, 런타임에는 타입을 제거한다는 뜻이다.

자바 컴파일러의 타입 소거

Unbounded Type(<?>, <T>)Object로 변환한다.

// 컴파일 할 때 (타입 소거 전) 
public class Test<T> {
    public void test(T test) {
        System.out.println(test.toString());
    }
}

// 런타임 때 (타입 소거 후)
public class Test {
    public void test(Object test) {
        System.out.println(test.toString());
    }
}

Bound Type(<E extends Comparable>)의 경우 Object가 아닌 Comparable로 변환한다.

// 컴파일 할 때 (타입 소거 전) 
public class Test<T extends Comparable<T>> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

// 런타임 때 (타입 소거 후)
public class Test {
    private Comparable data;

    public Comparable getData() {
        return data;
    }

    public void setData(Comparable data) {
        this.data = data;
    }
}

✋ 확장된 제네릭 타입에서 다형성 보존을 위해 어떠한 클래스나, 인스턴스를 상속 혹은 구현할때 bridge method를 생성한다.

public class Node<T> {
    public T data;
    public Node(T data) { this.data = data; }
    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

// 컴파일 할 때 (타입 소거 전) 
public class MyNode extends Node<Integer> {
    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

// 런타임 때 (타입 소거 후)
public class MyNode extends Node {

    // Bridge method generated by the compiler
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
    // ...
}
  • bridge 메서드는 type erasure 이후 Node 클래스에서의 setData메서드와 같은 이름의 메서드를 가지고 원래의 setData 메서드의 역할을 대신한다.

✋ 제네릭 타입을 사용할 수 있는 일반 class, interface, method에만 소거 규칙을 적용한다.
✋ 타입 안정성 측면에서 필요하면 type casting을 넣는다.


0개의 댓글