Box 클래스를 선언할 때 내용물로 content 필드를 선언하는 상황에서, box 안에 특정 클래스 타입으로 선언할 수 없기 때문에
아래와 같이 Object 타입으로 선언한다.
public class Box {
public Object content;
}
Object 타입은 모든 클래스의 최상위 부모 클래스로, 모든 객체는 부모타입인 Object로 자동변환 되므로 content 필드에는 어떤 객체든 대입이 가능하다.
Box box = new Box();
box.content = 모든 객체;
문제는 Box 내용물을 얻을 때, content 는 Object 타입이므로 어떤 객체가 대입됐는지 모른다.
이때 타입을 안다면 강제타입 변환을 거쳐 얻을 수 있다.
String content = (String)box.content;
어떤 내용물이 들어있는지 모르면 instanceof 연산자로 조사할 수 있지만 모든 종류의 클래스 대상으로 조사할 순 없다.
그래서 우린 box를 생성하기 전에 어떤 타입을 넣을지 알기에 타입을 미리 알려주면 content 에 무엇이 대입되고 읽을 때 어떤 타입으로 제공할지 알게된다.
이것이 제네릭이다.
제네릭(Generic) 이란 결정되지 않은 타입을 파라미터로 처리하고 실제 사용할 때 파라미터를 구체적인 타입으로 대체시키는 기능
Box 클래스에서 결정되지 않은 content 타입을 T라는 타입 파라미터로 정의한 것이다.
public class Box<T> {
public T content;
}
<T>는 T가 타입 파라미터(매개변수) 를 뜻하는 기호로, 타입이 필요한 자리에 T를 사용할 수 있음을 알려준다.
그래서 Box 클래스는 T가 무엇인지 모르지만 Box 객체가 생성될 시점에 다른 타입으로 대체된다는 것이다.
만약 내용물을 String 으로 저장한다면 아래와 같이 String으로 대체한다.
Box<String> box = new Box<>();
box.content = "공부해라 아냐 그건 너무 교과서야";
String content = box.content;
Box 내용물로 정수를 저장하고 싶으면 위처럼 타입 파라미터 T 대신 Integer로 대신한다 (Integer는 정수형을 표현하는 클래스타입)
<T> 는 단지 이름이기 때문에 A~Z 중 어떤 알파벳을 이용해도 좋다. 주의할 점은 대체 타입은 클래스 및 인터페이스라는 점이다.
int 대신 Integer을 사용한 것은 int는 기본 자료형이기 때문이다.
제네릭 타입
제네릭 타입은 결정되지 않은 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.
제네릭 타입은 선언부에 '<>' 부호가 붙고, 그 사이에 타입 파라미터들이 위치한다.
public class 클래스명<A, B, ...> { ... }
public interface 인터페이스명<A, B, ...> { ... }
타입 파라미터는 변수명과 동일한 규칙에 따라 작성할 수 있지만, 일반적으로 대문자 알파벳 한 글자로 표현한다.
외부에서 제네릭 타입을 사용하려면 타입 파라미터에 구체적인 타입을 지정해야하고, 타입을 지정하지 않으면 Object 타입이 암묵적으로 사용된다.
클래스를 제네릭으로 사용한 예시
package Generic;
public class Product<K, M> {
private K kind;
private M model;
public K getKind() {
return kind;
}
public void setKind(K kind) {
this.kind = kind;
}
public M getModel() {
return model;
}
public void setModel(M model) {
this.model = model;
}
}
package Generic;
public class Tv { }
package Generic;
public class Car { }
package Generic;
public class GenericExample {
public static void main(String[] args) {
Product<Tv, String> product1 = new Product<>();
product1.setKind(new Tv());
product1.setModel("스마트 tv");
Tv tv = product1.getKind();
String tvModel = product1.getModel();
System.out.println(tvModel);
Product<Car, String> product2 = new Product<>();
product2.setKind(new Car());
product2.setModel("벤츠");
Car car = product2.getKind();
String carModel = product2.getModel();
System.out.println(product2);
}
}
인터페이스를 제네릭 타입으로 선언한 예시
package Generic;
public interface Rentable <P>{
P rent();
}
package Generic;
public class Home {
public void turnOnLight(){
System.out.println("전등을 킨다");
}
}
package Generic;
public class Car {
public void run(){
System.out.println("자동차가 달린다");
}
}
package Generic;
public class HomeAgency implements Rentable<Home>{
@Override
public Home rent(){
return new Home(); //리턴타입이 반드시 home 이어야함.
}
}
package Generic;
public class CarAgency implements Rentable<Car>{
@Override
public Car rent(){
return new Car();
}
}
package Generic;
public class GenericExampleImpl {
public static void main(String[] args) {
HomeAgency homeAgency = new HomeAgency();
Home home = homeAgency.rent();
home.turnOnLight();
CarAgency carAgency = new CarAgency();
Car car = carAgency.rent();
car.run();
}
}
위 GenericExampleImpl 코드는 HomeAgency 와 CarAgency 에서 대여한 Home, Car을 이용하는 방법을 보여준다.
제한된 타입 파라미터
타입 파라미터를 대체하는 구체적인 타입을 제한하는 경우가 있다.
숫자를 연산하는 제네릭 메서드는 대체 타입으로 Number 또는 자식 클래스(Byte, Short, Integer, Long, Double)로 제한해야 할 필요가 있다.
이처럼 모든 타입으로 대체할 순 없고, 특정 타입과 자식 또는 구현 관계에 있는 타입만 대체할 수 있는 타입 파라미터를 제한된 타입 파라미터라고 한다.
정의
public <T extends 상위타입> 리턴타입 메서드(매개변수, ...) { ... }
타입 파라미터가 Number 타입으로 제한되면서 Object 의 메서드 뿐 아니라 Number가 가지고 있는 메서드도 사용 가능하다.
public <T extends Number> boolean compare(T t1, T t2) {
double v1 = t1.doubleValue(); //Number의 doubleValue() 메서드 사용
double v2 = t2.doubleValue();
return (v1 == v2)
}
위 코드에서 doubleValue() 메서드는 Number 타입에 정의되어 있는 메서드이다.
제한된 타입 파라미터 사용 예시 코드
package Generic;
public class GenericExampleRestrict {
public static <T extends Number> boolean compare(T t1, T t2){
System.out.println("compare(" + t1.getClass().getSimpleName()
+ ", " + t2.getClass().getSimpleName() + ")");
double v1 = t1.doubleValue();
double v2 = t2.doubleValue();
return v1 == v2;
}
public static void main(String[] args) {
boolean result1 = compare(10,20);
System.out.println(result1);
System.out.println();
boolean result2 = compare(4.5, 4.5);
System.out.println(result2);
}
}
타입 파라미터를 가지는 제네릭 메서드 사용 예시
타입 파라미터가 메서드 선언부에 정의된다는 점에서 제네릭 타입과 차이가 있다.
package Generic;
public class Box <T>{
private T t;
public T get() {
return t;
}
public void set(T t) {
this.t = t;
}
}
package Generic;
public class GenericExampleMethod {
public static <T> Box<T> boxing(T t){
Box<T> box = new Box<>();
box.set(t);
return box;
}
public static void main(String[] args) {
Box<Integer> box1 = boxing(100);
int intValue = box1.get();
System.out.println(intValue);
Box<String> box2 = boxing("윤채원");
String strValue = box2.get();
System.out.println(strValue);
}
}
와일드카드 타입 파라미터
제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 타입 파라미터로 ?(와일드카드)를 사용할 수 있다.
?는 범위에 있는 모든 타입으로 대체할 수 있다는 표시다.
만약 아래와 같은 상속 관계가 있을 때,
타입 파라미터의 대체 타입으로 Student와 자식 클래스인 HighStudent, MiddleStudent만 가능하도록 매개변수를 다음과 같이 선언할 수 있다.
리턴타입 메서드명(제네릭타입<? extends Student> 변수) { ... }
반대로 Worker의 부모 클래스인 Person만 가능하도록 매개변수를 다음과 같이 선언한다.
리턴타입 메서드명(제네릭타입 <? super Worker> 변수) { ... }
어떤 타입이든 가능하도록 매개변수를 선언할 수 있다.
리턴타입 메서드명(제네릭타입<?> 변수) { ... }
'자바' 카테고리의 다른 글
[JAVA] 컬렉션 자료구조 (Map, Hashtable, properties) (0) | 2023.09.21 |
---|---|
[JAVA] 컬렉션 자료구조 (List, Set) (0) | 2023.09.21 |
[JAVA] 다형성 (0) | 2023.09.18 |
[JAVA] 예외 처리 (0) | 2023.09.18 |
[JAVA] 상속 (0) | 2023.09.15 |