본문 바로가기
JAVA

제너릭

by e-pd 2021. 2. 25.

https://github.com/whiteship/live-study/issues/14

학습 주제

  • 제네릭 사용법
  • 제네릭 주요 개념 (바운디드 타입, 와일드 카드)
  • 제네릭 메소드 만들기
  • Erasure

 

제너릭 

Java 1.5 부터 추가

클래스, 메서드 내에서 사용할 자료형을 클래스를 생성하면서 지정하여 사용

 

제너릭은 다이아몬드 연산자(Diamon operator)라고 읽음. 

<>으로 표기

 

보편적인 제너릭 타입 매개변수 명명

  • E - Element
  • K - Key
  • N - Number
  • T - Type
  • V - Value

 

제너릭을 쓰지않고 모든 타입이든지 받아 전달하는 객체를 만들어야한다고 가정하자.

쉽게 Object를 필드로 생각할 수 있다.

public class DataTransfer {
    private Object data;

    public Object getData() {
        return data;
    }

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

public class Main {
    public static void main(String[] args) {
        DataTransfer data = new DataTransfer();
        DataTransfer data2 = new DataTransfer();

        data.setData(100);
        data2.setData("A");

        Integer num = (Integer) data.getData();
        String text = (String) data2.getData();

        System.out.printf("%d, text" ,num, text);
    }
}

 

하지만 이 경우, 항상 자료를 꺼내 쓸 때 명시적으로 형변환이 필요하고 오류가 발생할 수 있다.

이러한 문제 상황에서 제네릭을 고려해볼 수 있다.

 

 

제너릭 선언

(접근 제어자) class 클래스명<참조 자료형> {
}

 

제너릭을 이용하면 다음과 같이 변경할 수 있다.

public class DataTransfer<T> {
    private T data;

    public T getData() {
        return data;
    }

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

public class Main {
    public static void main(String[] args) {
        DataTransfer<Integer> data = new DataTransfer<Integer>();
        DataTransfer<String> data2 = new DataTransfer<String>();

        data.setData(100);
        data2.setData("A");

        Integer num = data.getData();
        String text = data2.getData();

        System.out.printf("%d, text" ,num, text);
    }
}

 

Java 7 부터는 생성자를 추론해 타입을 추론할 수 있다.

List<String> group = new ArrayList<>();

 

제너릭은 한 개 이상도 사용가능하다.

public class DataTransfer<T, F> {
    T t;
    F f;

    public DataTransfer(T t, F f) {
        this.t = t;
        this.f = f;
    }

    public T getT() {
        return t;
    }

    public F getF() {
        return f;
    }

    public void printAll() {
        System.out.printf(t + " : " + f);
    }
}

public class Main {
    public static void main(String[] args) {
        DataTransfer<String, String> data = new DataTransfer<>("AA", "BB");

        data.printAll();
    }
}

Bounded Type

메서드가 받을 수 있는 타입을 제한하여 특정 타입만 받게 설정할 수도 있다.

 

public class DataTransfer<T extends String, F extends String> {
    T t;
    F f;

    public DataTransfer(T t, F f) {
        this.t = t;
        this.f = f;
    }

    public T getT() {
        return t;
    }

    public F getF() {
        return f;
    }

    public void printAll() {
        System.out.printf(t + " : " + f);
    }
}

 

와일드카드 제네릭 타입


메서드 인자 중에 제네릭 타입으로 넘어올 때 어떤 제네릭 타입이 들어올지 알 수 없을 때 사용하는 타입을 '?'으로 작성

 

 

public class Basket<T extends Number> {
    int sum;

    public void add(T num) {
        this.sum += num.intValue();
    }

    public int getSum() {
        return sum;
    }
}


public class Pay {
    int result = 0;
	
    // Pay에서는 자료형이 Integer인지 Float인지 모름
    public void calculate(Basket<?> basket) {
        result += basket.getSum();
    }
}

 

와일드 카드를 활용한 제한된 자료형

  • <? extneds 상위 클래스> : 상위 클래스를 상속 받은 자료형으로 제한
  • <? super 하위 클래스> : 하위 클래스가 상속 받고 있는 상위 클래스 자료형으로 제한

 


제너릭 메서드

재너릭 메서드 메서드의 반환될 자료형 앞에 다이아몬드 연산자를 넣어서 제너릭 타입 선언

 

[접근 제어자](static) <T> [반환된 자료형] [메서드명 (T t) {
	return 반환 자료형 리터럴;
}

extends 키워드를 사용하여 자료형을 제한할 수도 있다.

 

public class DataTransfer {
    String value;

    public <T> void setValue(T t) {
        this.value = t.toString();
        
    }

    public String getValue() {
        return value;
    }
}

Erasure

 

컴파일 타임에만 원소 타입을 검사하고 런타임에는 타입 정보를 소거한다.

즉, 실체화 되지 않는다. 

참고 : www.baeldung.com/java-type-erasure

public static  <E> boolean containsElement(E [] elements, E element){
    for (E e : elements){
        if(e.equals(element)){
            return true;
        }
    }
    return false;
}

 

byte코드를 살펴보면 타입정보는 소거되어 Object로 바뀌었다.

 

/ access flags 0x9
  // signature <E:Ljava/lang/Object;>([TE;TE;)Z
  // declaration: boolean containsElement<E>(E[], E)
  public static containsElement([Ljava/lang/Object;Ljava/lang/Object;)Z
   L0
    LINENUMBER 11 L0
    ALOAD 0
    ASTORE 2
    ALOAD 2
    ARRAYLENGTH
    ISTORE 3
    ICONST_0
    ISTORE 4
   L1
   FRAME APPEND [[Ljava/lang/Object; I I]
    ILOAD 4
    ILOAD 3
    IF_ICMPGE L2
    ALOAD 2
    ILOAD 4
    AALOAD
    ASTORE 5
   L3
    LINENUMBER 12 L3
    ALOAD 5
    ALOAD 1
    INVOKEVIRTUAL java/lang/Object.equals (Ljava/lang/Object;)Z
    IFEQ L4
   L5
    LINENUMBER 13 L5
    ICONST_1
    IRETURN
   L4
    LINENUMBER 11 L4
   FRAME SAME
    IINC 4 1
    GOTO L1
   L2
    LINENUMBER 16 L2
   FRAME CHOP 3
    ICONST_0
    IRETURN
   L6
    LOCALVARIABLE e Ljava/lang/Object; L3 L4 5
    // signature TE;
    // declaration: e extends E
    LOCALVARIABLE elements [Ljava/lang/Object; L0 L6 0
    // signature [TE;
    // declaration: elements extends E[]
    LOCALVARIABLE element Ljava/lang/Object; L0 L6 1
    // signature TE;
    // declaration: element extends E
    MAXSTACK = 2
    MAXLOCALS = 6
}

 

 

'JAVA' 카테고리의 다른 글

자바 ArrayList 공부  (0) 2021.10.05
람다식  (0) 2021.03.05
I/O  (0) 2021.02.15
애노테이션  (0) 2021.02.05
enum  (0) 2021.01.25