제네릭을 사용하는 이유
제네릭 타입을 사용함으로써 잘못된 타입이 선언되어 런타임 시점에 발생하는 오류를 컴파일 시점에서 제거할 수 있다.
제네릭은 클래스와 인터페이스, 메소드를 정의할 때 타입을 파라미터로 사용할 수 있도록 도와준다.
장점
1. 컴파일 시 강한 타입 체크를 할 수 있다.
- 런타임에 에러가 나는 것 보다는 컴파일타임에 미리 오류를 잡을 수 있다.
2. 타입 변환을 제거할 수 있다.
-제네릭이 아닌 코드는 불필요하게 타입을 변환해야 하지만 제네릭 타입을 선언하면 타입 변환을 하지 않아도 되므로 성능이 향상된다.
다음 두개의 클래스는 어떻게 다를까?
1.
public class Shop{
public Shop() {}
private Object obj;
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
2.
public class Shop2<T>{
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
첫 번째의 경우는 Object 타입으로 선언되어 있어서 객체를 선언할 때 마다 그에 맞는 타입으로 형 변환을 해주어야 한다.
두 번째의 경우는 Object 타입과 달리 형 변환이 일어나지 않고 자연스럽게 호출한 타입으로 변환된다.
WhyUsingGenericStep02.Shop2 shop2 = outerClass.new Shop2();
shop2.setT("String"); //String 타입으로 변환
shop2.setT(1234); //Integer 타입으로 변환
shop2.setT(list); //ArrayList 타입으로 변환
복수의 제네릭
제네릭은 복수의 타입을 함께 선언할 수 있다.
public class Person<T, R> {
private T info;
private R age;
public Person(T info, R age) {
this.info = info;
this.age = age;
}
public T getinfo() {
return info;
}
public void setinfo(T info) {
this.info = info;
}
public R getAge() {
return age;
}
public void setAge(R age) {
this.age = age;
}
}
public class EmpInfo {
private int empno;
private int deptno;
private String addr;
public EmpInfo(int empno, int deptno, String addr) {
this.empno = empno;
this.deptno = deptno;
this.addr = addr;
}
public int getEmpno() {
return empno;
}
public void setEmpno(int empno) {
this.empno = empno;
}
public int getDeptno() {
return deptno;
}
public void setDeptno(int deptno) {
this.deptno = deptno;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
public class UsingMultiGeneric {
Person<EmpInfo, Integer> person = new Person<EmpInfo, Integer>(new EmpInfo(1,2,"songpa"),1);
}
단, 이때 제네릭은 int,double 과 같은 일반 데이터 타입은 사용할 수 없음을 기억하자.
메서드에서의 제네릭 사용 : public <T> Product<T> boxing(T t){...}
선언
public <U> void PersonInfo(U info) {
}
호출
Person<EmpInfo, Integer> person = new Person<EmpInfo, Integer>(new EmpInfo(1,2,"songpa"),1);
person.<Integer>PersonInfo(132);
코딩을 하다가 신기한걸 발견을 했다.
public <U> void PersonInfo(S info) {
}
이렇게 구체적인 타입과 매개변수 타입이 다르면 컴파일 오류가 발생하는 점을 발견하였다.
하지만 내가 한가지 모르고 있더 사실을 발견하였던 것이었다.
메서드에 제네릭을 선언하는 방법은
명시적으로 구체적인 타입을 지정하는 방법
매개변수로 구체적인 타입을 추정하는 방법 두가지가 있다.
리턴타입 변수 = <구체적인타입> 메소드명(매개값); // 명시적으로 구체적 타입을 지정
리턴타입 변수 = 메소드명(매개값); // 매개값을 보고 구체적 타입을 추정
이렇게 두가지 방식이 있지만 나는 친절한 개발자이기에 명시적으로 구체적인 타입을 지정할 것이다.
제네릭의 제한 : public <T> Child<T extends Parent> boxing(T t ) { ...}
제네릭을 제한한다는 것은 특정클래스를 상속받는 클래스 만을 타입으로 받기로 제한하는 것이다.
이때 등장하는 것이 extends, super, ?(와일드카드) 이다.
코드를 통해 살펴보자
public class Parent<T> {
public Parent() {
}
public Parent(T age) {
System.out.println("I am Parent"+age);
}
}
public class Child<T> extends Parent{
public Child() {
System.out.println("I am son of Parent");
}
}
public class UsingGeneric {
public <T> void UsingGeneric(Child<? super Parent> child) {
}
public <T extends Parent> Child<T> UsingGeneric(T t1) {
Child<T> child = new Child<>();
return child;
}
public <T extends Parent> T UsingGeneric(T t1) {
return t1;
}
}
위와 같이 적절히 사용하면된다.
와일드카드는 부모클래스로 부터 상속받는 클래스라면 어떠한 클래스도 사용할 수 있도록 하는 연산자이다.
단, implemets는 지원하지 않는다.
해당 메서드는 이론 뿐만아니라 실전에 많이 적용시켜서 손에 많이 익히는 것을 추천한다.
References
https://yaboong.github.io/java/2019/01/19/java-generics-1/
'Java > Java' 카테고리의 다른 글
(JAVA) 템플릿 메서드 패턴 / 팩토리 메서드 패턴 (0) | 2019.12.09 |
---|---|
(JAVA) 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2019.12.08 |
(JAVA) Static (0) | 2019.12.07 |
(JAVA) 내부클래스의 종류 및 사용법 (1) | 2019.12.07 |
(JAVA) 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2019.12.04 |