Programming Language/자바(JAVA)
[JAVA] 제네릭
토자맨
2024. 11. 26. 19:59
제네릭
결정되지 않은 타입을 파라미터로 처리하고 실제 사용할 때 파라미터를 구체적인 타입으로 대체시키는 기능
- 아래와 같이 Box 클래스를 선언하려고 하는데 필드의 타입을 무엇으로 할지 결정되지 않은 경우 모든 클래스의 조상인
Object
타입으로 선언할 수 있다. - 이렇게 하면 강제 타입 변환을 통해 어떤 객체든 대입될 수 있다.
- 해당 필드를 얻을 때 어떤 타입인지 알 수 없으므로 강제 타입 변환을 통해 얻어야 한다.
- 하지만 모든 클래스를 대상으로 조사해서 얻는 것은 불가능하기 때문에
Object
타입을 이용하는 것은 적절하지 않다.
public class Box {
public ? content;
}
- 이런 경우 사용하는 것이 제네릭이다. 아래 예시로 보자
- 선언부에
<>
부모를 붙이고 그 안에 타입 파라미터를 위치한다.<T>
: T가 타입 파라미터임을 뜻하는 기호로 타입이 필요한 자리에T
를 사용하면 된다.
- 객체를 생성할 때 타입을 대입하면 해당 타입으로 대체되어 객체가 생성된다.(T -> String)
- 주의할 점은 타입 파라미터는 클래스 및 인터페이스이므로 타입 변수인 int, char 등은 사용이 불가능하다.
- Integer, String 등만 사용 가능하다.
public class Box <T> {
public T content;
}
// <T>: T가 타입 파라미터임을 뜻하는 기호. 타입이 필요한 자리에 T를 사용할 수 있음을 알려줌
Box<String> box = new Box<String>();
box.content = "안녕하세요";
String content = box.content; // 강제 타입 변환 필요 없이 "안녕하세요"를 바로 얻을 수 있음
- 동일 타입으로 호출한다면
-Box<String> box = new Box<String>();
가 아닌Box<String> box = new Box<>();
로 생략해도 된다.
제네릭 타입
- 클래스
// 제네릭 타입
public class Product<K, M> { // 타입 파라미터로 K, M 정의
// 필드
private K kind; // 타입 파라미터를 필드 타입으로 사용
private M model;
public K getKind() { return this.kind; }
public M getModel() { return this.model; }
public void setKind(K kind) { this.kind = kind; }
public void setModel(M model) { this.model = model; }
}
public class Tv {}
public class Car {}
public class GenericExample {
public static void main(String[] args) {
// K는 Tv로 대체, M은 String으로 대체
Product<Tv, String> product1 = new Product<>();
// Setter 매개값은 반드시 Tv와 String을 제공
product1.setKind(new Tv());
product1.setModel("스마트Tv");
// Getter 리턴값은 Tv와 String이 됨
Tv tv = product1.getKind();
String tvModel = product1.getModel();
Product<Car, String> product2 = new Product<>();
product2.setKind(new Car());
product2.setModel("SUV자동차");
Car car = product2.getKind();
String carModel = product2.getModel();
}
}
- 인터페이스
public interface Rentable<P> {
P rent(); // 타입 파라미터 P를 리턴 타입으로 사용
}
public class Home {
public void turnOnLight() {
System.out.println("전등을 켭니다.");
}
}
public class Car {
public void run() {
System.out.println("자동차가 달립니다.");
}
}
public class HomeAgency implements Rentable<Home> {
@Override
public Home rent() {
return new Home(); // 리턴 타입이 반드시 Home이어야 함
}
}
public class CarAgency implements Rentable<Car> {
@Override
public Car rent() {
return new Car(); // 리턴 타입이 반드시 Car여야 함
}
}
public class GenericExample {
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();
}
}
public class Box <T> {
public T content;
// Box의 내용물이 같은지 비교
public boolean compare(Box<T> other) {
boolean result = content.equals(other.content);
return result;
}
}
public class GenericExample {
public static void main(String[] args) {
Box box1 = new Box();
box1.content = "100";
Box box2 = new Box();
box2.content = "100";
Box box3 = new Box();
box3.content = 100;
boolean result1 = box1.compare(box2);
System.out.println(result1); // true
boolean result2 = box1.compare(box3);
System.out.println(result2); // false
}
}
제네릭 메소드
메소드 선언부에 타입 파라미터를 정의된다.
public <A, B, ...> 리턴타입 메소드명(매개변수, ...) { ... }
public class Box<T> {
// 필드
private T t;
// Getter 메소드
public T get() {
return t;
}
// Setter 메소드
public void set(T t) {
this.t = t;
}
}
public class GenericExample {
// 매개변수, 리턴에서 타입 파라미터 사용
public static <T> Box<T> boxing(T t) { // 타입 파라미터 T 정의
Box<T> box = new Box<T>();
box.set(t);
return box;
}
public static void main(String[] args) {
// 제네릭 메소드 호출
Box<Integer> box1 = boxing(100); // T를 Integer로 대체
int intValue = box1.get(); // Integer 반환 -> int 자동 언박싱
System.out.println(intValue); // 100
// 제네릭 메소드 호출
Box<String> box2 = boxing("홍길동"); // T를 String으로 대체
String strValue = box2.get();
System.out.println(strValue); // 홍길동
}
}
제한된 타입 파라미터
모든 타입으로 대체할 수 없고, 특정 타입과 자식 또는 구현 관계에 있는 타입만 대체할 수 있는 타입 파라미터
- 예를 들어 숫자를 연산하는 제네릭 메소드는 대체 타입으로 Number 또는 자식 클래스(Byte, Short, Integer, Long, Double)로 제한할 필요가 있다.
public <T extends 상위타입> 리턴타입 메소드(매개변수, ...) { ... }
public class GenericExample {
// 제한된 타입 파라미터를 갖는 제네릭 메소드
public static <T extends Number> boolean compare(T t1, T t2) {
// T의 타입 출력
System.out.println("compare(" + t1.getClass().getSimpleName() + ", " + t2.getClass().getSimpleName() + ")");
// Number의 메소드 사용
double v1 = t1.doubleValue(); // Number 타입의 doubleValue() 메소드 호출
double v2 = t2.doubleValue();
return (v1 == v2);
}
public static void main(String[] args) {
// 제네릭 메소드 호출
boolean result1 = compare(10, 20); // T를 Integer 타입으로 대체
System.out.println(result1);
System.out.println();
// 제네릭 메소드 호출
boolean result2 = compare(4.5, 4.5); // T를 Double 타입으로 대체
System.out.println(result2);
}
}
/*
compare(Integer, Integer)
false
compare(Double, Double)
true
*/
와일드카드 타입 파라미터
제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 타입 파라미터로 ?(와일드카드) 사용 가능
리턴타입 메소드명(제네릭타입<? extends Student> 변수) { ... }
- Student와 자식 클래스만 가능하도록 제한(하위로)
리턴타입 메소드명(제네릭타입<? super Worker> 변수) { ... }
- Worker와 Person만 가능하도록 제한(상위로)
리턴타입 메소드명(제네릭타입<?> 변수) { ... }
- 어떤 타입이든 가능
public class Person {}
class Worker extends Person {}
class Student extends Person {}
class HighStudent extends Student {}
class MiddleStudent extends Student {}
public class Applicant<T> {
public T kind;
public Applicant(T kind) {
this.kind = kind;
}
}
public calss Applicant<T> {
public T kind;
public Applicant(T kind) {
this.kind = kind;
}
}
public class Course {
// 모든 사람이 등록 가능
public static void registerCourse1(Applicant<?> applicant) {
System.out.println(applicant.kind.getClass().getSimpleName() + "이(가) Course1 등록");
}
// 학생만 등록 가능
public static void registerCourse2(Applicant<? extends Student> applicant) {
System.out.println(applicant.kind.getClass().getSimpleName() + "이(가) Course2 등록");
}
// 직장인 및 일반인만 등록 가능
public static void registerCourse3(Applicant<? super Worker> applicant) {
System.out.println(applicant.kind.getClass().getSimpleName() + "이(가) Course3 등록");
}
}
public class GenericExample {
public static void main(String[] args) {
// 모든 사람이 신청 가능
Course.registerCourse1(new Applicant<Person>(new Person()));
Course.registerCourse1(new Applicant<Worker>(new Worker()));
Course.registerCourse1(new Applicant<Student>(new Student()));
Course.registerCourse1(new Applicant<HighStudent>(new HighStudent()));
Course.registerCourse1(new Applicant<MiddleStudent>(new MiddleStudent()));
// 학생만 신청 가능
// Course.registerCourse2(new Applicant<Person>(new Person()));
// Course.registerCourse2(new Applicant<Worker>(new Worker()));
Course.registerCourse2(new Applicant<Student>(new Student()));
Course.registerCourse2(new Applicant<HighStudent>(new HighStudent()));
Course.registerCourse2(new Applicant<MiddleStudent>(new MiddleStudent()));
// 직장인 및 일반인만 신청 가능
Course.registerCourse3(new Applicant<Person>(new Person()));
Course.registerCourse3(new Applicant<Worker>(new Worker()));
// Course.registerCourse3(new Applicant<Student>(new Student()));
// Course.registerCourse3(new Applicant<HighStudent>(new HighStudent()));
// Course.registerCourse3(new Applicant<MiddleStudent>(new MiddleStudent()));
}
}