꼬물꼬물

[자바의 정석] Generics(제너릭스) 본문

스터디/JAVA

[자바의 정석] Generics(제너릭스)

멩주 2022. 10. 25. 01:24
  • JDK 1.5에서 도입된 제너릭스

1.1 제너릭스란?

  • 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시 타입 체크를 해주는 기능이다.
  • 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.
  • 장점
    1. 타입 안정성을 제공
    2. 타입 체크와 형변환을 생략할 수 있어 코드가 간결해진다.

예)

class Box<T>{
	T item;
	
	void setItem(T item) { this.item = item; }
	T getItem() { return item; }
}
  • <T>의 T는 타입변수(type variable)라고 하며, ‘T’ype에서 따온 것으로 다른 것을 사용해도 된다.
    • ArrayList<E> : Element
    • K: key
    • V: value
  • 기호의 종류만 다를 뿐, 임의의 참조형 타입을 의미한다.
  • 용어
    • Box<T>: 제너릭 클래스. T의 Box 혹은 T Box라고 읽는다.
    • T: 타입 변수
    • Box: 원시 타입

 

제너릭의 제한

  • 객체 생성시 타입을 지정해줘야 한다.
    • 예) Box<String> box = new Box<String>();
  1. 모든 객체에 대해 동일하게 동작해야하는 static 멤버에는 타입 변수를 사용할 수 없다.
    • static 멤버는 타입 변수에 지정된 타입의 종류에 상관없이 동일한 것이어야 하기 때문이다.
  2. class Box<T>{ static T item; // 에러 static int compare(T t1, T t2) { ... } // 에러 }
  3. 제너릭 타입의 배열을 생성할 수 없다.
    • 제너릭 배열 타입의 참조변수를 선언하는 것은 가능하지만 new T[10] 은 불가능하다.
    class Box<T>{
    	T[] itemArr; // T 타입의 배열을 위한 참조변수, OK
    	
    	T toArray(){
    		T[] tmpArr = new T[itemArr.length]; // 에러, 제너릭 배열 생성불가.
    		return tmpArr;
    	}
    }
    
    • new 연산자는 컴파일 시점에 타입 T가 무엇인지 정확하게 알아야 한다.
    • 그러나 위의 코드는 컴파일 시점에 어떤 타입이 될지 전혀 알 수 없다.
      • instanceof 연산자도 같은 이유로 T를 피연산자로 사용할 수 없음.
      • 꼭 제너릭 배열을 생성해야 한다면, new 연산자 대신, Reflection API의 newInstance()와 같이 동적으로 객체를 생성하는 메서드로 배열을 생성하거나
      • Object로 배열을 생성해 복사한 다음 T로 형변환해 사용해야 한다.

1.6 제너릭 메서드

  • 메서드 선언부에 제너릭 타입이 선언된 메서드를 제너릭 메서드라 한다.
  • Collections.sort()가 제너릭 메서드
public static <T extends Comparable<? super T>> void sort(List<T> list) {
        list.sort(null);
}
  • 제너릭 클래스에서 정의된 타입 매개변수와 제너릭 메서드에 정의된 타입 매개변수는 전혀 다르다.
    • 같은 타입문자 T를 사용해도 같은 것이 아니라는 것에 주의해야 한다.
  • 제너릭 메서드는 제너릭 클래스가 아닌 클래스에도 정의될 수 있다.
class FruitBox<T> {
	...
	static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
}
  • 제너릭 클래스 FruitBox의 T와 제너릭 메서드 sort()에 선언된 타입 매개변수 T는 문자만 같을 뿐 서로 다른 것.
  • sort()가 static인 것에 주목하자!
    • static 멤버에는 타입 매개변수를 사용할 수 없지만, 메서드에는 제너릭 타입을 선언하고 사용할 수 있다.
  • 메서드에 선언된 제너릭 타입은 지역 변수를 선언한 것과 같다고 생각하면 된다.

 

내가 사용한 제너릭 메서드

private <K, V> String keyValueString(HashMap<K, V> map) {
    Object[] keySet = sortKey(map);
    StringBuilder sb = new StringBuilder();
    for (Object key: keySet){
        sb.append(key).append(": ").append(map.get(key)).append("\n");
    }
    return sb.append("\n").toString()
}

private <K, V> Object[] sortKey(Map<K, V> map) {
    Object[] keySet = map.keySet().toArray();
    Arrays.sort(keySet);
    return keySet;
}

 

 

추가

제너릭은 클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 지정된다.