제네릭(Generic)
Java에서 Generic은, 클래스나 메서드에서 사용할 데이터 타입을 내부가 아닌 외부에서 지정하는 방법이다.
즉, 데이터 타입을 미리 지정해주는 대신 타입의 경계만 지정하고, #컴파일 때 필요한 타입으로 캐스팅하는것
[장점]
1. 제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 #컴파일 단계에서 방지할 수 있다.
2. 클래스 외부에서 타입을 지정해, 내부에서 따로 타입을 체크하고 변환해줄 필요가 없다. = 관리가 편함
3. 비슷한 기능을 지원하는 경우, 타입이 정해지지 않았으므로 코드의 재사용성이 높아진다.
[사용 방법]
타입 | 설명 |
<T> | Type |
<E> | Element |
<K> | Key |
<V> | Value |
<N> | Number |
정해진 규칙은 아니지만, 일반적으로 위와 같이 사용하고 있다.
#1. 클래스 및 인터페이스 사용
public class ClassName <T> { ... }
위와 같이 클래스명 옆에 <>를 사용해 제네릭을 적용할 수 있다.
이 때 주의할 점은 타입 파라미터로 지정하는 것은 #참조 타입 Only 지정할 수 있다.
즉, #기본형 타입은 올 수 없는데 만일 int, dobule과 같은 #기본형이 필요할 땐
Integer, Double과 같은 #Wrapper Type으로 지정해서 사용한다.
#참조타입이 올 수 있다는 것은 사용자가 정의한 #커스텀 클래스 타입 또한 타입 파라미터로 지정할 수 있다.
public class ClassName <T> { ... }
public class Student { ... }
public class Main {
public static void main(String[] args) {
ClassName<Student> a = new ClassName<Student>();
}
}
<제네릭 클래스 및 객체 사용 예제>
class ClassName<E>{
private E element;
public E getElement() {
return element;
}
public void setElement(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.setElement("10");
b.setElement(10);
System.out.println("a data: "+a.getElement());
System.out.println("b data: "+b.getElement());
System.out.println("a의 클래스타입: "+a.getElement().getClass().getName());
System.out.println("b의 클래스타입: "+b.getElement().getClass().getName());
}
}
위의 예제 실행 결과
이렇게 외부 클래스(main)에서 제네릭 클래스를 생성할 때
<> 안에 원하는 타입을 파라미터로 전달하여 생성하는 것이 Java의 #제네릭 프로그래밍이다.
#2. 제네릭 메서드 및 사용 예제
[접근제어자] <제네릭> [반환타입] [메서드명] ( [제네릭타입] [파라미터] ) {}
public <T> T genericMethod(T element) {
return element;
}
class ClassName<E>{
private E element;
public E getElement() {
return element;
}
public void setElement(E element) {
this.element = element;
}
public <T> T genericMethod(T element) {
return element;
}
}
public class Main {
public static void main(String[] args) {
ClassName<String> a = new ClassName<>();
ClassName<Integer> b = new ClassName<>();
a.setElement("10");
b.setElement(10);
System.out.println("a data: "+a.getElement());
System.out.println("b data: "+b.getElement());
System.out.println("a의 클래스타입: "+a.getElement().getClass().getName());
System.out.println("b의 클래스타입: "+b.getElement().getClass().getName());
System.out.println("<T> 메서드 return 타입 :"+a.genericMethod(3).getClass().getName());
System.out.println("<T> 메서드 return 타입 :"+a.genericMethod("hey").getClass().getName());
System.out.println("<T> 메서드 return 타입 :"+a.genericMethod(a).getClass().getName());
}
}
genericMethod()는 파라미터 타입에 따라 그 타입이 결정되도록 되어 있다.
위의 예제 실행 결과
즉, 클래스에서 지정한 제네릭 유형과 달리 메서드에서 독립적으로 제네릭 유형을 선택해 사용할 수 있다.
위와 같은 방식이 필요한 이유는 '정적(static) 메서드로 선언할 때' 필요하기 때문이다.
static 메서드는 객체가 생성되기 전에 이미 메모리에 올라가 있고, 클래스명.메서드 명으로 바로 사용 가능.
class ClassName<E>{
private E element;
public E getElement() {
return element;
}
public void setElement(E element) {
this.element = element;
}
public <T> T genericMethod(T element) {
return element;
}
public static <X> X exampleMethod(X example) {
return example;
}
}
public class Main {
public static void main(String[] args) {
System.out.println(ClassName.exampleMethod("hi"));
System.out.println("Method Type: "+ClassName.exampleMethod("I am IronMan").getClass().getName());
}
}
위의 예제 실행 결과.
이렇게 제네릭 클래스의 객체가 생성되기 전 제네릭 클래스와 별도로 제네릭 메서드를 사용할 수 있다.
[결론]
제네릭이란 데이터의 타입을 내부에서 미리 정하는게 아니라,
외부에서 필요에 따라 거기에 맞게끔 캐스팅 할 수 있도록 하는 것.
[장점]
1. 제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 #컴파일 단계에서 방지할 수 있다.
2. 클래스 외부에서 타입을 지정해, 내부에서 따로 타입을 체크하고 변환해줄 필요가 없다. = 관리가 편함
3. 비슷한 기능을 지원하는 경우, 타입이 정해지지 않았으므로 코드의 재사용성이 높아진다.
'백엔드 기술 > Java' 카테고리의 다른 글
SingleTon 디자인 패턴 (0) | 2023.04.20 |
---|---|
컴파일(Compile)과 런타임(Runtime) (0) | 2023.04.20 |
컬렉션과 스트림의 차이 (0) | 2023.04.19 |
자료 구조에서 Array VS List (0) | 2023.04.18 |
List, Set, Map 의 차이점 (0) | 2023.04.18 |