Generic은 데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 하는 방법이다. -> 다형성
객체의 타입을 컴파일 시에 체크해줌으로써 의도하지 않은 타입의 객체가 저장되는 것을 막는다.
-> 객체의 타입 안정성을 높여준다
간단히 ArrayList 클래스를 살펴보면
public class ArrayList<E> extends AbstractList<E> ...
이렇게 되어있다. < > 괄호 안에 들어가는 타입이 제네릭 타입이다. 이를 통해 우리는 여러 데이터 타입의 ArrayList 객체를 생성할 수 있다. 만약 제네릭 타입이 없다면 각 타입에 맞게 클래스를 하나하나 만들어야 할 것이다..
ArrayList<String> stringArr = new ArrayList<>();
ArrayList<Integer> integerArr = new ArrayList<>();
ArrayList<Double> doubleArr = new ArrayList<>();
제네릭 타입은 클래스와 메서드에 선언할 수 있다.
<T>
를 통해 객체를 생성할 때 외부에서 타입을 지정한다.암묵적 규칙
<T>
: 타입<E>
: 요소<K, V>
: 키, 값public class Box<T> {
T item; // 타입 변수
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
public static void main(String[] args) {
Box<String> box = new Box<String>(); // 스트링만 받겠다
box.setItem("Box");
box.getItem();
box.setItem(123); // 컴파일 에러
}
이를 이해하려면 인스턴스 변수를 알아야 하는데, 인스턴스 변수는 실체화된 객체가 쓰는 변수를 말한다. 타입 변수는 new 를 통해 객체를 만들 때 지정하는 것이므로, static에 끼워 넣는건 말이 안되겠지??
public class Box<T> {
static T test; // 안된다
}
new 연산자는 컴파일 시점에 타입 T가 뭔지 알아야 하기 때문에
T[] lst; // 제네릭 배열 타입 선언은 가능
lst = new T[10]; // 불가능!!!
Box<T>
의 객체를 생성할 때 참조변수와 생성자에 대입된 타입이 일치해야 한다. 아니면 에러 발생Box<String> box = new Box<String>(); // Good
Box<String> box = new Box<Integer>(); // 타입 불일치
때문에 JDK 1.7 부터는 참조 변수의 타입만 쓰고 생성자에 타입을 지정하지 않아도 된다.
추정이 가능할 경우 타입을 생략할 수 있음
public class Box<T extends Collection> // Collection 타입의 자손들만 대입할 수 있음
Box<ArrayList> box1 = new Box<>(); // ArrayList는 Collection을 상속
Box<String> box2 = new Box<>(); // error
메소드의 선언 부에 적은 제네릭으로 리턴 타입, 파라미터 타입이 정해진다.
static <T> void 함수이름 ( T 매개변수 )
여기서 <T>
는 반환값이 아니다. 그냥 제네릭 메소드라는 것을 명시해주는 것
메소드에 선언된 제네릭 타입 매개변수는 결국 메소드 내에서만 사용될 것이여서 static이여도 된다! (아직은 잘 이해가 안가지만 그냥 제네릭 변수는 static으로 쓸 수 없지만, 제네릭 메소드는 쓸 수 있다 라고 알아둔다)
static <T> void test(T item) {
System.out.println(item);
}
List<String> arrStr = new ArrayList<>();
List<Object> arrObj = arrStr;
위 경우 될거 같지만, 컴파일 에러가 난다.
왜냐!!! 위에가 정상 작동한다면 arrStr에 Integer 타입 값을 넣는게 가능해진다...
⚠️ arrObj.add(123)
어떤 컬렉션이든 받아서 출력하는 메소드를 만들려면 와일드 카드를 쓰면 된다.
아래와 같이 쓰면 컴파일 에러 사라짐
List<String> arrStr = new ArrayList<>();
List<?> arrObj = arrStr;
List<?> arrObj = arrStr; // 제한 없음, 모든 타입 가능
List<? extends String> arrObj2 = arrStr; // T(String)와 그 자손들만 가능
List<? super String> arrObj3 = arrStr; // T(String)와 그 조살들만 가능
Box<Object> objBox = (Box<Object>)new Box<String>(); // ERROR. 형변환 불가능
Box<? extends Object> wBox = (Box<? extends Object>)new Box<String>(); // OK
Box<? extends Object> wBox = new Box<String>(); // 위 문장과 동일
컴파일러는 제네릭 타입을 이용해서 소스 파일을 체크하고, 필요한 곳에 형변환을 넣어준다. 그리고 제네릭 타입을 제거한다
즉, 컴파일된 파일에는 제네릭 타입에 대한 정보가 없다
→ 제네릭이 도입되기 이전의 소스 코드와의 호환성을 위해 (JDK 1.5부터 도입됨)