본문 바로가기

Backend Study/Java

[JAVA] 제네릭 (Generic)

1.1 제네릭이란? 

- 컴파일 시 타입을 체크해주는 기능이다.

- 형변환의 번거러움을 줄여주고, 타입을 체크해주므로 코드가 간결해진다.

즉, 데이터 형식에 의존하지 않고, 하나의 값이 다른 데이터 타입들을 가질 수 있도록 하는 방법이다.

1.2 활용 

만약, 어떤 자료구조를 만들어 배포하려고할 때 String, Integer 모두 지원하고 싶다고 가정해보자. 

이 때 String, Integer에 대한 클래스 모두 만드는 것은 비효율적이다. 이를 해결하기 위해 제네릭을 사용한다.

 

아래 타입들이 많이 사용된다. (암묵적인 규칙일 뿐 반드시 따라야하는 것은 아니다.)

타입 설명
<T> Type
<E> Element
<K> Key
<V> Value
<N> Number

 

1.3 사용방법

 

# Object 객체 사용
class Box {
	Object item;
    void setItem(Object item) { this.item=item; }
    Object getItem() { return item; }
} 

# 제네릭 사용
class Box<T> {
	T item;
    void setItem(T item) { this.item=item; }
    T getItem() { return item; }
}

 

1.4 제약사항

1) static 멤버에는 타입 변수 T를 사용할 수 없다.

ex. 

 

class Box<T>{
	static T item; // 에러 
    static int compare (T t1, T t2){} // 에러
}

 

 

2) 제네릭 타입의 배열 T[]를 생성하는 것은 허용되지 않는다.

ex.

 

class Box<T> {
	T[] itemArr;
    T[] toArray() {
    	T[] tempArr=new T[itemArr.length]; // 에러
        return tmpArr; 
    }
}

 

 

3) 참조변수와 생성자에 대입된 타입이 일치해야한다.

 

Box<Apple> appleBox = new Box<Apple>();

Box<Apple> appleBox = new Box<Grape>(); //에러

 

 

4) 두 지네릭 클래스가 상속관계이고, 대입된 타입이 일치하는것은 무관하다.

 

Box<Apple> appleBox = new FruitBox<Apple>();

 

1.4 제네릭 클래스 제한하기

 

class FruitBox<T extends Fruit> { // Fruit의 자손만 타입으로 지정 가능하다는 의미
	ArrayList<T> list = new ArrayList<T> ();
    void add(T item) {list.add(item);}
}

* 주의: 인터페이스의 경우에도 implements가 아닌 extends를 사용한다.

 

1.5 와일드 카드 '?' 

자바에서 타입 파라미터간에는 상/하위 관계가 없고, raw-type간에 상-하위 관계가 존재한다.

예를 들어 Number는 Integer에 상위이고, List<Integer>은 ArrayList<Integer>의 상위이지만, List<Number>, List<Integer>은 상 하위 관계라고 볼 수 없다.

 

조금 더 구체적인 예를 보면

 

List<String> stringList = new ArrayList<>();
List<Object> objectList = stringList;
objectList.add(123);

String s = stringList.get(0);

 

objectList에 stringList가 수행된 후 123이라는 수를 넣는 것이니, String 타입 변수에 Integer 타입 값을 넣는게 가능해져버린다.

그러므로 List<Object> 타입 변수에 List<String> 타입 변수를 넣을 수 없다.

(실제로 컴파일 오류가 난다.)

 

그래서 어떤 컬레션이든 받아서 출력하는 메소드를 만들기 위해 ?(와일드 카드) 개념이 등장하였다.

 

static void printCollection(Collection <Object> c) {
	for (Object e:c){
    	System.out.println(e);
    }
}

public static void main(String [] args) {
	Collection<String> c = new ArrayList<>();
    c.add("hi");
    printCollection(c);
    // 메소드 파라미터 타입은 Collection<Object>인데, String 타입을 받고 있기 때문에 오류 발생
}

 

static void pringCollection(Collection<?> c)

 

로 바꿔주면 오류가 고쳐진다.

 

추가로

 

static void printCollection(Collection<? extends Number> c){
		for ( Number e:c){
        	System.out.println(e);
            }
}


public static void main(String[] args){
	Collection<Integer> c = new ArrayList<>();
    c.add(123);
    printCollection(c);
}

 

extents를 사용하면 number을 상속한 어떤 것만 사용 가능하도록 제한 할 수 있다.

 

참고)

https://st-lab.tistory.com/153