[Java] Generic Type (일반 타입)
Generic Type
제네릭(Generic) 타입은 자바에서 클래스 또는 메서드를 정의할 때, 타입을 파라미터화하는 기능이다.
사용 목적은 다음과 같다.
1. 어떤 값이 하나의 참조 자료형이 아니라 여러개의 참조 자료형을 사용할 수 있도록 프로그래밍 할 수 있다.
2. 참조 자료형이 변환될 때 검증을 컴파일러가 하기 때문에 안정적이다.
우리가 많이 사용하는 컬렉션 프레임워크도 제네릭 타입을 사용하는 대표적인 예시이다.
List<Integer> list = new ArrayList<>();
컬렉션은 제네릭 타입을 사용하여 List가 여러가지 타입의 Element를 받을 수 있도록 설정할 수 있어 클래스 사용의 유동성이 매우 높다.
또한 제네릭을 사용하여 List를 선언함으로써 해당 List가 어떤 타입의 요소를 다루는지 명확하게 지정할 수 있다.
메서드에서의 Generic 타입 사용
public static <T, U> void printString(T ele1, U ele2){
System.out.println(ele1 + " " + ele2);
}
public static void main(String[] args) {
printString("Hello", 123);
}
제네릭 타입을 사용하는 기본적인 형태이다. 위의 코드는 ele1과 ele2가 어떤 자료형이든 상관 없이 사용할 수 있도록 만든 메서드이다.
<T>는 제네릭 타입 매개변수이다. T는 어떤 타입도 될 수 있다는 것을 메서드를 선언할 때 명시하는 것이다.
보통 제네릭 타입 매개변수는 T나, U등 대문자로 표현한다.
public static <T> T getElement(List<T> list, int index) {
if (list == null || index < 0 || index >= list.size()) {
return null;
}
return list.get(index);
}
메서드의 Argument 뿐만 아니라 Return type 또한 Generic Type을 사용할 수 있다.
위의 코드는 어떤 리스트의 특정 인덱스의 Element를 반환하는 코드를 작성한 것이다.
Generic의 부분적 제한
public static <T extends Number> T convertNumber(double value, Class<T> targetType) {
if (targetType == Integer.class) {
return targetType.cast((int) value); // int 타입으로 변환하여 반환
} else if (targetType == Double.class) {
return targetType.cast(value); // double 타입 그대로 반환
} else {
throw new IllegalArgumentException("Unsupported target type");
}
}
위 코드는 Value와, Integer.class와 같은 변환할 타입 클래스를 매개변수로 받아 변환해주는 코드이다.
다음 코드 제네릭 타입 매개변수로<T extends Number>를 사용하였다. 이는 Generic Type을 Number 클래스의 하위 클래스만을 대입할 수 있도록 제한한 것이다.
와일드카드 사용
public static <T> int getSize(List<T> list) {
return list.size();
}
public static int getSize2(List<?> list) {
return list.size();
}
모든 자료형을 커버하기 위해서 List<?>와 같은 비한정 와일드카드를 사용할 수 있다.
메서드나 클래스 내부에서 특정한 타입을 사용하지 않고, 모든 타입을 다룰 때 위와 같은 비한정 와일드카드를 사용하며, 모든 타입의 요소를 다룰 수 있는 유연한 코드를 작성할 수 있다.
public static <T> int getSize3(List<? extends T> list) {
T element = list.get(0);
return list.size();
}
메서드 내부에서 특정한 타입을 사용해야 하는 경우에는 와일드카드를 List<? extends T>와 같은 형태로 비한정 와일드카드를 사용하여 제네릭 타입이나 다른 와일드카드의 상위 타입으로 선언하여 사용할 수 있다.
클래스와 인터페이스에서의 Generic 타입 사용
class GenericClass <T> {
T element;
void setElement(T value){
this.element = value;
}
T getElement(){
return element;
}
}
public static void main(String[] args) {
GenericClass<String> g1 = new GenericClass();
g1.setElement("Hello");
g1.getElement();
GenericClass<Integer> g2 = new GenericClass();
g2.setElement(1234);
g2.getElement();
}
제네릭 클래스인 GenericClass<T>를 정의하였다. 제네릭 타입 매개변수 T를 가지며, 이를 사용하여 어떤 타입의 요소도 다룰 수 있도록 하였다.
제네릭 클래스의 주요 사용 목적은 각 인스턴스마다 자신만의 타입을 가질 수 있도록 하는 것이다. 위의 코드에서 GenericClass<String>은 문자열 타입의 요소를 다루는 인스턴스이고, GenericClass<Integer>는 정수 타입의 요소를 다루는 인스턴스이다.
Collection에서의 Generic
Collection 또한 Generic Type을 사용하는 대표적인 예시라고 하였다.
실제로 Java Collection 내부를 들여다 보면 다음과 같이 Generic Type을 사용하는 클래스임을 알 수 있다.
아래의 코드는 실제 java.util에서 제공하는 클래스와 인터페이스이다.
public interface List<E> extends Collection<E> {
int size();
boolean add(E e);
boolean remove(Object o);
//...
}
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
//...
}
ArrayList<E>에서 <E>는 제네릭 타입 매개변수이다. 이것은 요소의 타입을 파라미터화하여 특정한 타입의 요소만을 저장하도록 하는데 사용되었다.
List<E> 인터페이스 또한 제네릭을 사용하여 구현체가 어떤 타입의 요소를 저장할 수 있는지를 명시적으로 지정할 수 있도록 하였다.
아래의 코드는 Generic을 사용하여 ArrayList의 기능의 일부를 직접 작성하여 흉내낸 것이다.
import java.util.Arrays;
public class MyArrayList<T> {
private static final int INITIAL_CAPACITY = 10;
private Object[] array;
private int size;
public MyArrayList() {
array = new Object[INITIAL_CAPACITY];
size = 0;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void add(T element) {
ensureCapacity();
array[size++] = element;
}
public T get(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
return (T) array[index];
}
private void ensureCapacity() {
if (size == array.length) {
int newCapacity = array.length * 2;
array = Arrays.copyOf(array, newCapacity);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < size; i++) {
sb.append(array[i]);
if (i < size - 1) {
sb.append(", ");
}
}
sb.append("]");
return sb.toString();
}
public static void main(String[] args) {
MyArrayList<Integer> list = new MyArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println("Size of list: " + list.size());
System.out.println("Elements of list: " + list);
}
}
'Backend > Java' 카테고리의 다른 글
[Java] Collection(컬렉션) : 자료구조 및 시간복잡도 관점으로 보기 (1) | 2024.03.07 |
---|---|
[Java] 문자열(String) : 메서드, 형변환 및 StringBuilder & StringBuffer (0) | 2024.03.05 |
[Java] Stream API 사용하기 (1) | 2024.02.25 |
[Java] 람다식(Lambda Expression)과 함수형 프로그래밍 (1) | 2024.02.25 |
[Java] 내부 클래스(Inner Class) (멤버, 정적, 지역, 익명 내부 클래스) (0) | 2024.02.25 |