위 포스팅에서 설명한 캐스팅에 대해서 더 자세히 알아보자.
아래 코드와 같이 특정 가게에서는 구매 금액 기준에 따라 고객을 prime, vip, vvip로 나눈다고 하자.
그리고 각 객체는 Customer 클래스를 상속 받았다.
import java.util.ArrayList;
public class Shop {
public static void main(String[] agrs) {
Prime prime = new Prime();
VIP vip = new VIP();
VVIP vvip = new VVIP();
}
}
그리고 어느날 오픈 기념일을 맞아 모두에게 똑같이 10,000원 할인쿠폰을 주기로 했는데, (현재 예시는 고객 종류가 세개지만) 만약 고객의 종류가 많다면 포인트 지급 함수를 여러줄 작성해야 한다.
prime.addPoint(10000);
.
.
.
하지만 아래 코드처럼 ArrayList를 만들고 모두 부모 클래스로 형변환 해주면 간편하다.
ArrayList<Customer> customer = new ArrayList<>();
/* 형변환 */
customer.add(prime);
customer.add(vip);
customer.add(vvip);
/* 모든 customer에게 포인트 지급 */
for (Customer person : customer) {
person.addPoint(10000);
}
그런데 vvip에게는, vvip 에게만 제공해주는 감사쿠폰 발급 메소드도 쓰고 싶다.
이럴 때 instanceof
로 인스턴스 타입을 체크하고,
조건에 맞다면 다운 캐스팅을 사용해 특정 메소드를 사용할 수 있다.
for (Customer person : customer) {
person.addPoint(10000);
//person의 인스턴트 타입이 VVIP인 경우에만
if(person instanceof VVIP){
//person을 VVIP로 다운캐스팅(원복) 후 쿠폰지급
((VVIP) person).addCoupon();
}
}
그런데, 아래에 다른 코드를 보자.
Something 클래스가 있고 내부에는 thing 변수가 있다.
그리고 thing은 모든 클래스의 부모인 *Object 클래스 타입이다.
따라서 Something 의 set() 메소드에는 아무 값이나 넣을 수 있다.
public cass Something{
private Object thing;
public void set(Object obj){
this.thing = obj;
}
public Object get(){
return thing;
}
}
Object class는 모든 클래스의 부모 클래스이다.
최상위 클래스, 루트 클래스로도 불린다.
java.lang 패키지 안에 있는 클래스지만 자동으로 import 되기때문에
사용자가 별도로 import 하지 않아도 된다.
최상위 클래스인 만큼 모든 클래스는 object class를 상속받는다.
따라서 Object class에 있는 것은 모두 쓸 수 있고, 재정의도 가능하다.
예를 들면 대표적으로 toString(), equals() 등이 있다.
단, final로 선언된 것은 오버 라이딩할 수 없다.
더 자세한 내용은 java.lang 패키지에 대해 자바 doc에서 확인할 수 있다.
Something something = new Something();
Pizza kimchPizza = new Pizza();
something.set(kimchPizza);
Pizza openPizza = somthing.get();
하지만 get() 메소드의 return 타입은 Object 이기 때문에
Pizza openPizza = something.get();
코드에서 오류가 발생할 수 있다.
Object는 Phone으로 변환될 수 없기 때문이다.
아래와 같이 형 변환(Casting)을 하면 해결할 수 있지만
잘못된 타입을 입력하게 되면 오류가 발생해 위험할 수 있다.
Pizza openPizza = (Pizza) something.get();
다른 방법으로는 아래처럼 instanceof를 사용할 수도 있는데, 번거롭다.
if (something.get() instanceof Pizza){
openPizza = (Pizza) something.get();
}
이 때 사용하는 것이 Generic 이다.
generic 이라는 단어는 "일반적인" 이라는 뜻인데, 프로그래밍에서는
"데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 하는 방법" 이다.
더 자세히 예를 들어보면, 아래 코드는 위 코드의 Object 타입을 <T>로 대체한 것이다.
Public class Something<T> {
private T thing;
public void set (T obj) {
this.thing = obj;
}
public T get() {
return thing;
}
}
<> 꺽새 기호 안에 있는 T같은 것을 "타입 파라미터"라고 부르고,
이런 타입 파라미터를 받는 클래스를 제네릭 클래스 라고 한다.
이와 같이 <> 꺽새 안에 원하는 타입(<T>) 넣어 선언하면
이제 thing의 타입에는 미리 지정된 하나의 타입이 아니라,
외부 클래스에서 필요한 타입으로 지정하여 메소드를 사용할 수 있게 된다!!
두둥탁.
이제 위의 코드에서 set() 메소드 안에는 String, Int 등
원하는 타입을 넣을 수 있다.
보통 Generic은 아래 표와 같은 형식으로 많이 쓰인다.
(표 출처: https://st-lab.tistory.com/153)
반드시 위와 같이 따라야하는 규칙은 아니지만
통상적으로 사용하는, 암묵적인 규칙 정도로 이해하면 된다.
아무래도 일반적으로 많이 알려진 선언을 사용하면 편할 것이다.
그런데, 만약 <T>같이 제네릭 클래스를 사용하는 중에서도,
특정 클래스 범위로 제한을 두고 싶다면 extends
를 사용하면 된다.
예를 들어 <T extends Pizza>
같은 형태로 말이다.
그러면 Pizza를 상속받은 클래스들 중에서 자유롭게 필요에 의해 타입을 지정할 수 있다.