제네릭
- 클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 지정되는 것
- 한마디로 특정(specific) 타입을 미리 지정해주는 것이 아닌 필요에 의해 지정할 수 있도록 하는 일반(Generic) 타입이라는 것
- JDK 1.5 이전에서는 여러 타입을 사용하는 대부분의 클래스나 메서드에서 인수나 반환값으로 Object를 사용하였다. 하지만 이 경우에는 반환된 Object 객체를 다시 원하는 타입으로 변환해야 하며, 이때 오류가 발생할 가능성도 존재한다. JDK 1.5부터 도입된 제네릭을 사용하면 컴파일 시 미리 타입이 정해지므로, 타입 검사나 타입 변환과 같은 번거로운 과정을 생략할 수 있게 된다.
제네릭의 제거 시기
- 자바 코드에서 선언되고 사용된 제네릭 타입은 컴파일 시 컴파일러에 의해 자동으로 검사되어 타입 변환된다. 그리고서 코드 내의 모든 제네릭 타입은 제거되어 컴파일된 class파일에는 어떠한 제네릭 타입도 포함되지 않게 된다.
장점
1. 제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있다.
2. 클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해 줄 필요가 없다. 즉, 관리가 편하다.
3. 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아진다.
제네릭 사용방법
암묵적으로 아래와 같이 많이 쓰인다.
제네릭 클래스 및 인터페이스
T 타입은 해당 블럭 { … } 안에서까지 유효하다.
public class ClassName <T> { ... }
public Interface InterfaceName <T> { ... }
이렇게 생성된 제네릭 클래스를 사용해보자. 객체를 생성해야 하는데 이 때 구체적인 타입을 명시해주어야 한다.
public class ClassName <K, V> { ... }
public class Main {
public static void main(String[] args) {
ClassName<String, Integer> a = new ClassName<>();
이 때 주의해야 할 점은 타입 파라미터로 명시할 수 있는 것은 참초 타입밖에 올 수 없다. int, char 등과 같은 원시 타입은 올 수 없다. 그래서 int형, double형과 같은 원시 타입의 경우 Integer, Double과 같은 Wrapper Type을 사용해주어야 한다.
또한 바꿔 말하면 참조 타입이 올 수 있다는 것은 사용자가 정의한 클래스도 타입으로 올 수 있다는 것이다.
제네릭 클래스
package Java.JavaGeneric;
class ClassName<E> {
private E element; // 제네릭 타입 변수
public E get() {
return element;
}
public void set(E element) {
this.element = element;
}
}
public class Main {
public static void main(String[] args) {
ClassName<String> a = new ClassName<>();
ClassName<Integer> b = new ClassName<>();
a.set("10");
b.set(10);
System.out.println("a.get() = " + a.get());
System.out.println("a.get().getClass().getName() = " + a.get().getClass().getName());
System.out.println();
System.out.println("b.get() = " + b.get());
System.out.println("b.get().getClass().getName() = " + b.get().getClass().getName());
}
}
제네릭 메소드
public <T> T genericMethod(T o) {
...
}
[접근제어자] <제네릭 타입> [반환타입] [메소드명] ([제네릭 타입] [파라미터]) {
// 텍스
}
보면 ClassName이란 객체를 생성할 때 <>안에 타입 파라미터를 지정한다.
그러면 a객체의 ClassName의 E 제네릭 타입은 String으로 모두 변환된다.
b객체의 ClassName의 E 제네릭 타입은 Integer으로 모두 변환된다.
genericMethod()는 해당 객체의 파라미터 타입에 따라 T 타입이 결정된다.
package Java.JavaGeneric;
class Generic<E> {
private E element; // 제네릭 타입 변수
public E get() {
return element;
}
public void set(E element) {
this.element = element;
}
<T> T genericMethod(T o) {
return o;
}
}
public class Main {
public static void main(String[] args) {
Generic<String> a = new Generic<>();
Generic<Integer> b = new Generic<>();
a.set("10");
b.set(10);
System.out.println("a.get() = " + a.get());
System.out.println("a.get().getClass().getName() = " + a.get().getClass().getName());
System.out.println();
System.out.println("b.get() = " + b.get());
System.out.println("b.get().getClass().getName() = " + b.get().getClass().getName());
System.out.println();
// 제네릭 메소드 Integer
System.out.println("<T> returnType : " + a.genericMethod(3).getClass().getName());
// 제네릭 메소드 String
System.out.println("<T> returnType : " + a.genericMethod("ABCD").getClass().getName());
// 제네릭 메소드 ClassName b
System.out.println("<T> returnType : " + a.genericMethod(b).getClass().getName());
}
}
클래스에서 지정한 제네릭유형과 별도로 메서드에서 독립적으로 제네릭 유형을 선언하여 사용할 수 있다. 이러한 방식이 필요한 이유는 정적 메서드로 선언할 때 필요하기 때문이다.
제네릭은 유형을 외부에서 지정해준다. 쉽게 말해, new 생성자로 클래스 객체를 생성하고 <>사이에 파라미터로 넘겨준 타입으로 지정된다. 하지만 static의 경우 기본적으로 프로그램 실행 시 메모리에 이미 올라가있다. 그렇다면 static 메서드는 객체가 생성되기 전에 타입을 어디서 얻어올 수 있는가? 정답은 얻어올 수 없다. 객체 생성을 통해 접근할 필요 없이 이미 메모리에 올라가 있기 때문에 클래스 이름을 통해 바로 쓸 수 있지만 해당 타입이 무엇인지 알 수가 없기 때문에 에러가 난다. 이러한 이유로 제네릭이 사용되는 메서드를 정적메서드로 두고 싶다면 제네릭 클래스와 별도로 독립적인 제네릭 메서드가 되어야 한다.
package Java.JavaGeneric;
class ClassName<E> {
private E element; // 제네릭 타입 변수
public E get() {
return element;
}
public void set(E element) {
this.element = element;
}
static <T> T genericMethod1(T o) {
return o;
}
static <E> E genericMethod2(E o) {
return o;
}
}
public class Main {
public static void main(String[] args) {
ClassName<String> a = new ClassName<>();
ClassName<Integer> b = new ClassName<>();
a.set("10");
b.set(10);
System.out.println("a.get() = " + a.get());
System.out.println("a.get().getClass().getName() = " + a.get().getClass().getName());
System.out.println();
System.out.println("b.get() = " + b.get());
System.out.println("b.get().getClass().getName() = " + b.get().getClass().getName());
System.out.println();
// 제네릭 메소드 Integer
System.out.println("<T> returnType : " + a.genericMethod1(3).getClass().getName());
// 제네릭 메소드 String
System.out.println("<T> returnType : " + a.genericMethod1("ABCD").getClass().getName());
// 제네릭 메소드 ClassName b
System.out.println("<T> returnType : " + a.genericMethod1(b).getClass().getName());
System.out.println();
// 제네릭 메소드1 Integer
System.out.println("<E> returnType : " + ClassName.genericMethod1(3).getClass().getName());
// 제네릭 메소드1 String
System.out.println("<E> returnType : " + ClassName.genericMethod1("ABCD").getClass().getName());
// 제네릭 메소드2 ClassName a
System.out.println("<T> returnType : " + ClassName.genericMethod2(a).getClass().getName());
// 제네릭 메소드2 Double
System.out.println("<T> returnType : " + ClassName.genericMethod2(3.0).getClass().getName());
}
}
2편에 계속.
출처 :
https://st-lab.tistory.com/153
https://www.tcpschool.com/java/java_generic_various