2024. 2. 20. 14:34ㆍ언어(Language)/JAVA
제네릭(Generic)이란?
제네릭(Generic)이란 일반적인 코드를 작성하고, 이 코드를 다양한 타입의 객체에 대하여 재사용하는 프로그래밍 기법이다. 제네릭은 타입을 파라미터화해서 컴파일시 구체적인 타입이 결정되도록 한다. 또한 클래스 내부에서 사용할 데이터 타입을 외부에서 지정해준다. 제네릭은 컬렉션, 람다식, 스트림 등에서 널리 사용된다.
ex)
public class Person<T> {
public T info;
public static void main(String[] args) {
Person<String> p1 = new Person<>();
Person<StringBuilder> p2 = new Person<>();
}
}
제네릭(Generic)의 장점
제네릭은 컴파일시 강한 타입 체크를 할 수 있다. 따라서 실행 시에 타입 에러가 나는 것이 아닌 컴파일 시에 미리 타입을 강하게 체크해서 에러를 사전에 방지해준다.
ex)
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("hello");
String str = (String)list.get(0);
}
}
이렇게 제네릭을 사용하지 않으면 list 객체에 어떤 타입의 객체도 받아올 수 있지만 get으로 해당 객체를 꺼내 올 때 빈번하게 타입변환이 발생하기 때문에 비효율적이다.
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
String str = list.get(0);
}
}
하지만 위와 같이 제네릭 타입을 이용해서 <String>을 지정해준다면 형변환을 하지 않아도 get 메소드를 사용해서 객체를 꺼내올 수 있다.
구체적인 예제를 보면서 이해해보자.
public class Box {
private Object object;
public void set(Object object) {
this.object = object;
}
private Object get() {
return object;
}
public static void main(String[] args) {
Box box = new Box();
box.set("hello");
String str = (String)box.get(); //빈번한 형변환 발생
}
}
위의 코드처럼 Box라는 클래스에서 자료형을 Object로 해놓으면 set메소드를 이용해서 "hello"를 저장할 때 String형인 "hello"를 Object로 형변환을 해야 하고 다시 get메소드로 꺼내올 때에도 Object형에서 String형으로 형변환을 해야하기 때문에 빈번한 형변환이 발생한다. 여기까지는 위와 비슷한 내용인데 이제 왜 Generic을 사용하면 좋은지 내부 과정이 어떻게 이루어지는지에 대해서 알아보자.
public class Box<T>{
private T t;
public void set(T t) {
this.t = t;
}
private T get() {
return t;
}
public static void main(String[] args) {
Box<String> box = new Box();
box.set("hello");
String str = box.get();
}
}
위와 같이 제네릭(Generic)을 사용하면 위의 List예제처럼 형변환을 하지 않고도 사용할 수 있다.
그런데 왜 형 변환 없이도 사용할 수 있는 것일까?
public class Box<String>{
private String t;
public void set(String t) {
this.t = t;
}
private String get() {
return t;
}
}
Box 클래스의 <String>으로 인스턴스화할 때 메모리가 할당이 되는데 이때 컴파일러가 T 부분을 String으로 바꿔준다고 생각하자. 그렇기 때문에 box 객체는 형변환을 하지 않고도 사용할 수 있는 것이다.
제네릭(Generic) 사용 방법
//클래스
public class ClassName<T> { ... }
//인터페이스
public Interface InterfaceName<T> { ... }
//제네릭 타입을 두 개 이상 사용하는 경우
public class MultiGeneric<K, V> { ... }
위의 경우는 클래스나 인터페이스에 선언한 경우와 제네릭 타입을 두 개 이상 사용하는 경우디ㅏ.
이때, T, K, V 타입은 해당 블록 { ... } 안에서만 유효하다.
public class Box<T> {
private T object;
public void set(T object) {
this.object = object;
}
public T get() {
return object;
}
}
위의 코드 중 <T>의 T를 타입 변수라고 한다. 이는 임의의 참조형 타입을 의미한다.
타입 변수(Type Variable)
E : Element <E>
K : Key <K>
N : Number <N>
T : Type <T>
V : Value <V>
타입 변수는 기호의 종류만 다를 뿐 임의의 참조형 타입을 의미한다는 것은 모두 같으며, 상황에 맞게 의미 있는 문자를 선택해서 사용한다.
주의해야 할 점은 타입 변수로 명시할 수 있는 것은 참조 타입(Reference Type) 밖에 올 수 없다는 것이다.
즉 int, double, char과 같은 Primitive Type은 올 수 없다. 그래서 int형, double형 등의 Primitive Type의 경우 Integer, Double 같은 Wrapper Type으로 사용해야 한다.
Test<Integer> test = new Test<Integer>();
List<String> list = new ArrayList<>();
실제 사용에서는 < > 안에 있는 String을 실 타입 매개변수(Actual Type Parameter)라고 하며, 실제 List 인터페이스에 선언되어 있는 List<E>의 E를 형식 타입 매개변수(Formal Type Parameter)라고 한다.
'언어(Language) > JAVA' 카테고리의 다른 글
[JAVA] StringTokenizer란? (split()과 비교) (0) | 2024.02.21 |
---|---|
[JAVA] Map & HashMap (1) | 2024.02.20 |
[JAVA] getter와 setter를 사용하는 이유 (0) | 2024.02.20 |
[JAVA] Long과 long의 차이 (0) | 2024.02.20 |
[JAVA] 생성자(Constructor)와 초기화 (0) | 2024.02.19 |