제네릭이란
데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 하는 방법
특정 타입을 미리 지정해주는 것이 아닌 사용자의 필요에 의해 지정되는 타입
장점
제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있다.
클래스 외부에서 타입을 지정해주기 때문에 따로 타입변환이 필요 없다.
// 제네릭 미사용
List list = new ArrayList();
list.add("hi");
String str = (String) list.get(0); // 타입 변환 필요
// 제네릭 사용
List<String> list = new ArrayList();
list.add("hi");
String str = list.get(0); // 타입 변환 불필요
비슷한 기능을 지원하는 경우 코드의 재사용성이 높아진다.
네이밍
제네릭 타입을 선언할 때 클래스 선언시 <> 안에 어떤 단어가 들어가도 상관 없다. 그렇지만 자주 사용하는 규칙이 있다.
타입 | 설명 |
---|---|
<T> | Type |
<E> | Elemnet |
<K> | Key |
<V> | Value |
<N> | Number |
<R> | Result |
클래스, 인터페이스
public class TestGeneric<T> {
private T t;
public T getT() { return t; }
public void setT(T t) { this.t = t; }
}
// Example
class Main {
public static void main(String args[]) {
TestGeneric<String> a = new TestGeneric<String>();
TestGeneric<Integer> b = new TestGeneric<Integer>();
a.setT("test");
b.setT(10);
Systme.out.println(a.getT()); // test
Systme.out.println(a.getT().getClass().getName()); // java.lang.String
Systme.out.println(b.getT()); // 10
Systme.out.println(b.getT().getClass().getName()); // java.lang.Integer
}
}
public class TestMultiGeneric<K, V> {
private K first;
private V second;
public K getFirst() { return first; }
public V getSecond() { return second; }
public void set(K first, V second) {
this.first = first;
this.second = second;
}
}
// Example
class Main {
public static void main(String args[]) {
TestMultiGeneric<String, Integer> a = new TestMultiGeneric<String, Integer>();
a.set("test", 10);
System.out.println(a.getFirst()); // test
System.out.println(a.getFirst().getClass().getName()); // java.lang.String
System.out.println(a.getSecond()); // 10
System.out.println(a.getSecond().getClass().getName()); // java.lang.Integer
}
}
제네릭 메소드
제네릭 메소드는 매개타입과 리턴타입으로 타입파라미터를 갖는 메소드를 말한다.
제네릭 메소드를 선언하는 방법은 리턴 타입 앞에 "<>" 기호를 추가하고 타입 파라미터를 기술한 다음, 리턴 타입과 매개 타입으로 타입 파라미터를 사용하면 된다.
// [접근제어자] <제네릭타임> [리턴타입] [메소드명]([제네릭타입] [파라미터]) {}
public <T> T genericMethod(T o) {}
public class TestGenericMethod<E> {
private E element;
void set(E element) { this.element = element; }
void get() { return element; }
<T> T genericMethod(T o) { return o; }
}
// Example
class Main {
public static void main(String args[]) {
TestGenericMethod<String> a = new TestGenericMethod<String>();
TestGenericMethod<Integer> b = new TestGenericMethod<Integer>();
a.set("abc");
b.set(10);
// genericMethod() 는 파라미터 타입에 따라 T 타입이 결정된다
// java.lang.String
System.out.println(a.genericMethod("test").getClass().getName());
// java.lang.Integer
System.out.println(a.genericMethod(20).getClass().getName());
// TestGenericMethod
System.out.println(a.genericMethod(a).getClass().getName());
}
}
@SuppressWarnings("JavaDoc")
public class NamingUtil {
private static final ObjectMapper objectMapper;
static {
objectMapper = new Jackson2ObjectMapperBuilder().propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE).build();
}
/**
* @param target (Map<String, Object>)
* @param elementClass (DTO.class)
* @return T (DTO)
* @throws JsonProcessingException
* @description SnakeCase 필드로 이루어진 json(Map)을 elementClass DTO 클래스로 변환
*/
public static <T> T convertDTO(Map<String, Object> target, Class<T> elementClass) throws JsonProcessingException {
return objectMapper.readValue(new Gson().toJson(target), elementClass);
}
/**
* @param list (List<Map<String, Object>>)
* @param elementClass (DTO.class)
* @return List<T> (List<DTO)
* @throws JsonProcessingException
* @description List 내의 SnakeCase 필드로 이루어진 json(Map)을 elementClass DTO 클래스로 변환
*/
public static <T> List<T> convertDTOList(List<?> list, Class<T> elementClass) throws JsonProcessingException {
CollectionType listType = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, elementClass);
return objectMapper.readValue(new Gson().toJson(list), listType);
}
}
제한된 타입 파라미터
제네릭타입을 특정 범위로 좁혀 제한하고 싶을 때 사용
<K extends T> // T와 T의 자손 타입만 가능 (K는 들어오는 타입으로 지정 됨)
<K super T> // T와 T의 부모(조상) 타입만 가능 (K는 들어오는 타입으로 지정 됨)
<? extends T> // T와 T의 자손 타입만 가능
<? super T> // T와 T의 부모(조상) 타입만 가능
<?> // 모든 타입 가능. <? extends Object>랑 같은 의미
// Example
// Number를 상속받는 타입 파라미터 이므로 하위 클래스 타입인 Byte, Short, Integer, Long, Double 만 받을 수 있다.
public <T extends Number> int compare(T t1, T t2) {}
// Runnable 을 상속받은 타입만 받으므로 run() 메소드 사용가능
public void run(List<? extends Runnable> list) {
for (Runnable runnable : list) {
runnable.run();
}
}
와일드카드 <?>
제네릭 코드에서 ? 로 표기되어 있는 것을 말하며, 아직 알려지지 않은 타입을 나타냄
K extends T 와 ? extends T 의 차이
K는 특정 타입으로 지정이 되지만, ?는 타입이 지정되지 않는다는 의미다.
/*
* Number와 이를 상속하는 Integer, Short, Double, Long 등의
* 타입이 지정될 수 있으며, 객체 혹은 메소드를 호출 할 경우 K는
* 지정된 타입으로 변환이 된다.
*/
<K extends Number>
/*
* Number와 이를 상속하는 Integer, Short, Double, Long 등의
* 타입이 지정될 수 있으며, 객체 혹은 메소드를 호출 할 경우 지정 되는 타입이 없어
* 타입 참조를 할 수는 없다.
*/
<? extends T> // T와 T의 자손 타입만 가능
https://st-lab.tistory.com/153 https://thecodinglog.github.io/java/2020/12/15/java-generic-wildcard.html https://jehuipark.github.io/java/java-generic