1. 개요
정적언어(C, C++, Java 등등)을 다루다보면 제네릭이란 용어를 자주 접하게 된다.
사전적 의미론 '일반적인' 이라는 뜻이지만 이것만 보면 뭔 말인지 이해하기 힘들다.
부연설명을 하자면 '데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을
가질 수 있도록 하는 방법' 이다.
ArrayList, LinkedList 등을 생성할 때,
[객체]<[타입]> [객체명] = new [객체]<[타입]>(); 의 형태로 원하는 자료형을 넣고 생성한다.
자료형마다 클래스를 만드는 방법도 있지만, 지나치게 비효율적이다.
이때, 제네릭(Generic)은 클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해
지정되는 것을 의미한다.
특정(Specific)타입을 미리 지정하는 것이 아닌, 필요에 의해 지정할 수 있도록 하는 일반(
Generic)타입이라는 것이다.
제네릭의 장점은
・ 잘못된 타입을 사용하는 것을 컴파일 단계에서 방지할 수 있다.
・ 클래스 외부에서 타입을 지정하니 타입체크와 변경의 수고를 던다.
・ 비슷한 기능을 지원하는 경우, 코드의 재사용성이 높아진다.
제네릭의 타입은 뭘 넣어도 되지만 일반적으로 쓰이는 암묵적인 룰이 있다.
타입 설명
<T> Type // E랑 밑에 거 빼고 거의 다
<E> Element // List와 같은 컬렉션 클래스와 같은 배열 기반은 element가 적절하다.
<K> Key
<V> Value
<N> Number
2. 사용 예
사용법은
클래스, 인터페이스 선언
public class Class <T> {...}
public interface Interface <T> {...}
이 경우, T에 들어가는 자료형에 따라 적용되는 T가 달라진다.
public class Class <T, K> {...}
public interface Interface <T, K> {...}
public class HashMap <K, V> {...} // 해쉬는 key, value형태로 데이터값을 저장하는
// 대표적인 Map 컬렉션이다.
public class main {
public static void main(String[] args){
Class<String, Integer> a = new Class<String, Integer>();
}
}
위와 같이 구현하면 T는 String, K는 Integer가 된다.
이때 주의해야 할 점은 타입 파라미터로 명시할 수 있는 것은 Reference 타입만 넣을 수 있다.
즉, int, double, char 같은 primitive 타입은 올 수 없는 것이다.
그래서 primitive 타입은 Integer, Double같은 Wrapper 타입으로 쓰는 것이다.
바꿔말하면 Reference 타입이 올 수 있다는 것은 사용자가 정의한 클래스도 타입으로 올 수
있다는 것이다.
2-2 제네릭 클래스
class Class<E> {
private E element; // 제네릭 타입 변수
void set(E element) { // 제네릭 파라미터 메소드
this.element = element;
}
E get() { // 제네릭 타입 반환 메소드
return element;
}
}
class Main{
public static void main(String[] args){
Class<String> a = new Class<String>();
Class<Integer> b = new Class<Integer>();
a.set("10");
b.set(10);
System.out.println("a data : " + a.get());
// 반환된 변수의 타입 출력
System.out.println("a E Type : " + a.get().getClass().getName());
System.out.println();
System.out.println("b data : " + b.get());
// 반환된 변수의 타입 출력
System.out.println("b E Type : " + b.get().getClass().getName());
}
}
//
a data : 10
a E Type : java.lang.String
b data : 10
b E Type : java.lang.Integer
Class란 객체를 생성할 때, <> 안에 타입 파라미터를 지정한다.
a객체의 Class의 E 제네릭 타입은 String으로 모두 변환되고,
b객체의 Class의 E 제네릭 타입은 Integer로 모두 변환된다.
class Class<K, V> {
private K first;
private V second;
void set(K first, V second){
this.first = first
this.second = second
}
}
의 형태로 한 클래스에서 여러 타입 파라미터를 받을 수도 있다.
2-3 제네릭 메소드
메소드에도 제네릭타입을 쓸 수 있다.
[접근 제어자] <제네릭 타입> [반환 타입] [메소드 명]([제네릭 타입]) [파라미터]) {
...
}
public <T> genericMethod(T o) {
....
}
System.out.println("<T> returnType : " + a.genericMethod("ABCD").getClass().getClass().getName());
//
<T> returnType : java.lang.Integer
물론 저기다 클래스의 인스턴스를 넣어도 된다. 그럼 타입은 그 클래스 이름으로 나온다.
제네릭 메소드를 쓰면 제네릭 클래스와 별개로, 자료형을 받을 수 있다.
이런 방식이 필요한 이유는 '정적 메소드로 선언할 때 필요'하기 때문이다.
제네릭은 유형을 외부에서 지정해준다고 했다. 즉 해당 클래스 객체가 인스턴스화 했을 때,
new 생성자로 클래스 객체를 생성하고, <> 괄호 사이에 파라미터로 넘겨준 타입으로 지정
된다는 뜻이다.
하지만 static변수, 함수 등 static이 붙은 것들은 기본적으로 프로그램 실행시 메모리에
이미 올라가있다.
이 말은 객체 생성을 통해 접근할 필요없이 이미 메모리에 올라가 있기 때문에 클래스 이름을
통해 바로 쓸 수 있다는 것이다.
근데 거꾸로 생각해보면 static 메소드는 객체가 생성되기 전부터 이미 메모리에 올라가있는데
타입을 어디서 얻어올 수 있을까
class Class<E>{
// 클래스와 같은 E 타입이지만, static 메소드는 객체가 생성되기 이전 시점에
// 메모리에 먼저 올라가기 때문에 E 유형을 클래스로부터 얻어올 방법이 없다.
static E genericMethod(E o){ // error
return o;
}
}
class Main {
public static void main(String[] args){
// Class 객체가 생성되기 전에 접근할 수 있으나 유형을 지정할 방법이 없어 에러가 남
Class.genericMethod(3);
}
}
이런 문제가 있으니 제네릭이 사용되는 메소드를 정적 메소드로 두고싶은 경우, 제네릭 클래스와
별도로 독립적인 제네릭이 사용되어야 한다.
class Class<E> {
private E element; // 제네릭 타입 변수
void set(E element) { // 제네릭 파라미터 메소드
this.element = element;
}
E get() { // 제네릭 타입 반환 메소드
return element;
}
static <E> E genericMethod1(E o) { // 제네릭 메소드
return o;
}
static <T> T genericMethod2(T o) {
return o;
}
}
class Main{
public static void main(String[] args){
Class<String> a = new Class<String>();
Class<Integer> b = new Class<Integer>();
a.set("10");
b.set(10);
System.out.println("<E> returnType : " + Class.genericMethod1(3).getClass().getName();
System.out.println("<E> returnType : " + Class.genericMethod1("ABCD").getClass().getName();
System.out.println("<T> returnType : " + Class.genericMethod2(a).getClass().getName();
System.out.println("<T> returnType : " + Class.genericMethod2(3.0).getClass().getName();
}
}
//
<E> returnType : java.lang.Integer
<E> returnType : java.lang.String
<T> returnType : Class
<T> returnType : java.lang.Double
제네릭 메소드는 클래스 타입과 별도로 지정된다.
<> 괄호 안에 타입을 파라미터로 보내 제네릭 타입을 지정해주는 것이 제네릭 프로그래밍이다.
'자바' 카테고리의 다른 글
객체지향 3. 객체지향 설계 5원칙 SOLID (0) | 2022.03.10 |
---|---|
객체지향 2. 객체지향 4가지 핵심요소 캡슐화, 상속, 다형성, 추상화 (0) | 2022.03.10 |
객체지향 1. 객체지향이란 (0) | 2022.03.10 |
자바 객체 (0) | 2022.02.16 |
7주차, 제네릭 사용 예 (0) | 2022.02.05 |
댓글