제네릭(Generics)
제네릭이란
- jdk 1.5부터 제공되는 기능
-
다양한 타입의 객체들을 다루는 메소드나 클래스의 타입을 미리 명시해서 저장할 객체(클래스 타입)을 제한하는 기능이다
-
사용 이유
- 컴파일 단계에서 잘못된 타입을 사용할 수 있는 문제를 제거한다
- 컬렉션에 저장된 여러 종류의 객체를 꺼내서 사용할 때, 종류에 따라 매번 강제 형변환을 해야하는 상황을 방지한다
=> 강제형변환은 성능에 좋지 못한 영향을 끼치기 때문에 최대한 방지하면 좋음 - 컬렉션, 람다식(함수적 인터페이스), 스트림, NIO에서 널리 사용한다
- 제네릭을 모르면 API Document 해석이 어렵기 때문에 학습에 제한이 생길 수 있다
- 사용 이점
- 컴파일 시 강한 타입 체크가 가능하다
=> 의도하지 않은 타입의 객체가 저장되는 것을 막는다 => 저장된 객체를 의도하지 않은 타입으로 사용하는 것을 막는다 - 형변환용의 복잡한 코드 제거 및 성능향상이 가능하다
- 컴파일 시 강한 타입 체크가 가능하다
제네릭 선언
-
제네릭은 static변수에 사용할 수 없다
=> ex)public class Student<T> { static T name; }
=> static은 상위 클래스가 인스턴스가 되기 전에 메모리에 올라가는데 이때 타입이 결정되어있지 않기 때문에 사용할 수 없음 -
static 메소드에도 제네릭을 사용할 수 없다
=> ex)static T getName(T name) { return name; }
=> 위의 static 변수와 마찬가지 이유로 타입이 정해지지 않았는데 메모리에 올리기 때문 -
하지만 제네릭 메소드는 static이 가능하다
=> ex) ` staticT getOneStudent(T id) { return id; }` => 제너릭 메소드는 호출 시에 매개 타입을 지정하기 때문에 static이 가능하다 -
이 외에 클래스 , 일반 메소드에도 제네릭 사용 가능
[ 제네릭 클래스 선언 ]
// [접근제한자] class [클래스이름] < 영문자 > { }
public class Box<T> { }
- 위에서 영문자 위치에 제네릭을 사용한다
- 일반적으로 대문자로 표기
- 클래스타입이 미정일 경우, 객체 생성시 정해지는 클래스 타입을 영문자가 받아서 영문자 사용위치에 적용하는 식으로 동작한다
- ex)
Box<String> box = new Box<String>();
=>public class Box<String> { }
- ex)
- 만약 두 개 이상의 타입으로 제네릭 타입 변수를 사용하고 싶다면 콤마로 구분하여 다른이름으로 타입 변수를 지정해주면 된다
[ 제네릭 메소드 선언 ]
public class Box<T> {
// [접근제한자] <Type1, Type2, … > [리턴타입] [메소드이름](매개변수, …) { … }
public <T> T method(T d) { return box; }
}
- 제네릭 메소드란 메소드의 선언부에 적은 제네릭으로 리턴타입, 파라미터의 타입이 정해지는 메소드이다
- 타입 변수를 명시적으로 지정할 수도 있고 컴파일러가 매개값의 타입을 보고 추정하도록 할 수도 있다
- 위의 예시의 경우
<T>
는 제네릭타입을 명시해준것이고Box<T>
는 리턴타입을 명시해 준것이다 - 다만 해당 메소드가 속한 클래스
Box<T>
의<T>
와 제너릭 메소드에 붙은<T>
는 같은 T를 사용하더라도 전혀 별개로 취급해야한다
=> 클래스에 표시하는<T>
는 인스턴스 변수와도 같이 인스턴스가 생성될 때 마다 지정되기 때문 => 제네릭 메소드에 붙은<T>
는 지역변수를 선언한 것과 같다고 할 수 있다 -
제네릭 제한하기
class Triangle<T extends Point> { }
- 제네릭 타입 변수에 extends 키워드를 사용하면 타입의 종류를 제한할 수 있다
=> 일반적인 방법으로 제네릭을 이용했을 경우라면 타입에 제한이 없음 - extends 뒤에 명시된 타입의 자손들만 타입 변수에 대입할 수 있게 된다
- 사용 이유
- 제네릭을 사용하면 타입 파라미터로 모든 클래스를 넣어줄 수 있다
=> 다양한 타입이 들어오면서 데이터의 정체성을 훼손시킬 수 있음 => 숫자타입만 원했는데 문자열 클래스가 들어가도 오류가 나지 않는 식으로 동작할 수 있음 - 위와같은 이유때문에 제네릭에 제약을 걸어 상한선(extends)과 하한선(super)을 설정해주는 것이다
- 제네릭을 사용하면 타입 파라미터로 모든 클래스를 넣어줄 수 있다
와일드카드
<?> // 제한 없음
<? extends 상위타입> // 상위 클래스 제한
<? super 하위타입> // 하위 클래스 제한
- 타입 변수를 매개변수나 리턴 타입으로 사용할 때 구체적인 타입 대신 와일드 카드를 사용 가능하다
- 와일드 카드 이용 3가지 방법
- 타입 변수를 대치할 수 있는 구체적인 타입으로 제한을 두지 않기
- extends 키워드를 이용해 해당 타입과 자손 타입들만 가능하도록 제한하기
- super 키워드를 이용해 해당 타입과 부모 타입들만 가능하도록 제한하기
제네릭 상속
public class ChildProduct<T, K, S> extends Product<T, K> { … }
- 제네릭 클래스도 부모 클래스가 될 수 있다
- 부모 클래스의 제네릭 타입을 상속해서 자식 틀래스에서도 제네릭 타입을 상속 할 수 있다
- 자식 제네릭 클래스는 부모의 것 외에도 추가적인 타입 변수를 가질 수 있다
- 다만 부모클래스와 같은 제네릭 타입은 필수적으로 가지고 있어야 한다
댓글남기기