자바 제네릭은 generic 타입 및 메소드를 정의하고 사용 할 수 있는 언어적 특성입니다.
제네릭 타입은 파라미터 타입에 실제 타입 변수가 제공되면 인스턴스화 됩니다. LinkedList 는 제네릭 타입이고 여기서 E 는 타입 파라미터 입니다. LinkedList 처럼 인스턴스화 될 때 Integer는 실제 타입 변수라고 합니다.
제네릭 타입을 사용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있기 때문입니다. 자바 컴파일러는 코드에서 잘못 사용된 타입 때문에 발생하는 문제점을 제거하기 위해 제네릭 코드에 대해 강한 타입 체크를 합니다. 실행 시 타입 에러가 나는것보다는 컴파일 시에 미리 타입을 강하게 체크해서 에러를 사전에 방지하는 것이 좋습니다. 또 제네릭 코드를 사용하면 타입을 국한하기 떄문에 요소를 찾아올 때 타입 변환을 할 필요가 없어 프로그램 성능이 향상되는 효과를 얻을 수 있습니다.
ArrayList list = new ArrayList(); //제네릭을 사용하지 않을경우
list.add("test");
String temp = (String) list.get(0); //타입변환이 필요함
ArrayList<String> list2 = new ArrayList(); //제네릭을 사용할 경우
list2.add("test");
temp = list2.get(0); //타입변환이 필요없음
제네릭 타입은 타입을 파라미터로 가지는 클래스와 인터페이스를 말합니다. 제네릭 타입은 클래스 또는 인터페이스 이름 뒤에 < > 부호가 붙고 사이에 타입 파라미터가 위치합니다.
public class 클래스명<T> {...}
public interface 인터페이스명<T> {...}
타입 파라미터는 정해진 규칙은 없지만 일반적으로 대문자 알파벳 한글자로 표현합니다.
타입인자 | 설명 |
---|---|
< T > | Type |
< E > | Element |
< K > | Key |
< N > | Number |
< V > | Value |
< R > | Result |
class ExClassGeneric<T> {
private T t;
public void setT(T t) {
this.t = t;
}
public T getT() {
return t;
}
}
위와 같이 클래스를 설계할 때 구체적인 타입을 명시하지 않고 타입 파라미터로 넣어두었다가 실제 설계한 클래스가 사용되어질 때 ExClassGeneric exGeneric = new ExClassGeneric<>(); 이런식으로 구체적인 타입을 지정하면서 사용하면 타입 변환을 최소화 시킬 수 있습니다.
interface ExInterfaceGeneric<T> {
T example();
}
class ExGeneric implements ExInterfaceGeneric<String> {
@Override
public String example() {
return null;
}
}
인터페이스도 위와 같이 클래스처럼 제네릭으로 설정해두고 활용할 수 있습니다.
class ExMultiTypeGeneric<K, V> implements Map.Entry<K,V>{
private K key;
private V value;
@Override
public K getKey() {
return this.key;
}
@Override
public V getValue() {
return this.value;
}
@Override
public V setValue(V value) {
this.value = value;
return value;
}
}
타입은 두개 이상의 멀티 타입 파라미터를 사용할수 있고 이 경우 각 타입 파라미터를 콤마로 구분합니다.
class People<T,M>{
private T name;
private M age;
People(T name, M age){
this.name = name;
this.age = age;
}
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
public M getAge() {
return age;
}
public void setAge(M age) {
this.age = age;
}
//Generic Mothod
public static<T,V> boolean compare(People<T,V>p1, People<T,V>p2) {
boolean nameCompare = p1.getName().equals(p2.getName());
boolean ageCompare =p1.getAge().equals(p2.getAge());
return nameCompare && ageCompare;
}
}
public class ExGeneric {
public static void main(String []args){
//타입 파라미터 지정
People<String,Integer> p1 = new People<String,Integer>("Jack",20);
//타입 파라미터 추정
People<String,Integer> p2 = new People("Steve",30);
//GenericMothod 호출
boolean result = p1.compare(p1,p2);
System.out.println(result);
}
}
제네릭 메서드를 정의할때는 리턴타입이 무엇인지와는 상관없이 내가 제네릭 메서드라는 것을 컴파일러에게 알려줘야합니다. 그러기 위해서 리턴타입을 정의하기 전에 제네릭 타입에 대한 정의를 반드시 명시해야합니다. 그리고 중요한 점이 제네릭 클래스가 아닌 일반 클래스 내부에도 제네릭 메서드를 정의할 수 있습니다. 그 말은, 클래스에 지정된 타입 파라미터와 제네릭 메서드에 정의된 타입 파라미터는 상관이 없다는 것입니다. 즉, 제네릭 클래스에 를 사용하고, 같은 클래스의 제네릭 메서드에도 로 같은 이름을 가진 타입파라미터를 사용하더라도 둘은 전혀 상관이 없습니다.
public class Calcu {
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj + " ");
}
}
public int sum(List<? extends Number> list) {
int sum = 0;
for (Number i : list) {
sum += i.doubleValue();
}
return sum;
}
public List<? super Integer> addList(List<? super Integer> list) {
for (int i = 1; i < 5; i++) {
list.add(i);
}
return list;
}
}
와일드카드 타입에는 총 세가지의 형태가 있으며 물음표(?)라는 키워드로 표현됩니다.
제네릭타입<?> : 타입 파라미터를 대치하는 것으로 모든 클래스나 인터페이스타입이 올 수 있습니다.
제네릭타입<? extends 상위타입> : 와일드카드의 범위를 특정 객체의 하위 클래스만 올 수 있습니다.
제네릭타입<? super 하위타입> : 와일드카드의 범위를 특정 객체의 상위 클래스만 올 수 있습니다.