제네릭 (Generic)
- 소스 레벨에서 데이터 타입이 결정되지 않고, 별칭(타입파라미터)만 지정한다.
- 데이터 타입은 필드의 타입, 매개변수 타입, 리턴타입 등이다.
- 객체 생성시점, 구현클래스 구현시점, 메소드 실행시점에 별칭을 대신할 데이터 타입을 외부에서 지정하는 것이다.
- 제네릭은 <> 다이아몬드 표기법으로 타입파라미터(별칭)를 지정한다.
- 객체 생성싯점, 구현클래스 구현시점, 메소드 실행시점에 데이터타입을 지정한다.
- 데이터타입은 클래스 혹은 인터페이스 타입만 가능하다.
- 기본 자료형은 제네릭의 데이터 타입으로 지정할 수 없다.
제네릭 클래스
- 타입 파라미터를 하나 이상 가지고 있는 클래스
- 여러 종류의 객체를 다루는 클래스를 제네릭 클래스로 정의하면 타입의 안전성을 보장받고, 형변환 코드를 제거할 수 있다.
제네릭 클래스 예
- 자료구조(Set, List, Map<K, V>)
- 데이터를 반복처리하는 것(Iterator, Enumeration)
제네릭 클래스 구현시 주의사항
- T타입의 배열을 생성할 수 없다.
- T타입의 클래스 변수를 생성할 수 없다.
제네릭 클래스 정의 예시
public class GenericBox<T> {
private T data;
public void add(T data) {
this.data = data;
}
public T get() {
return data;
}
}
public class App {
public static void main(String[] args) {
// T의 타입이 String
GenericBox<String> box1 = new GenericBox<String>();
box1.add("홍길동");
String value = box1.get();
// T의 타입이 Integer
// new GenericBox<> 타입파라미터 적는 곳을 생략해도 무방하다
GenericBox<Integer> box2 = new GenericBox<>();
box2.add(100);
int value2 = box2.get();
}
}
제네릭 클래스 사용하는 이유
- 클래스내부에서 사용할 데이터의 타입을 외부에서 지정하는 것이다.
- 객체 생성시점에 데이터 타입을 지정한다.
// Fruit 객체만을 저장하기 위해 FruitBox 제네릭 클래스
public class FruitBox<T extends Fruit> {
T fruit;
public void add(T t) {
this.fruit = t;
}
public T get() {
return fruit;
}
}
// 참고로 Fruit 클래스는 이러하다
public class Fruit {
}
public class App3 {
public static void main(String[] args) {
FruitBox<Apple> box1 = new FruitBox<Apple>();
box1.add(new Apple());
Apple apple= box1.get();
}
}
// 참고로 Apple 객체의 클래스는 이러하다
public class Apple extends Fruit{
}
public class App3 {
public static void main(String[] args) {
FruitBox<Banana> box2 = new FruitBox<Banana>();
box2.add(new Banana());
Banana banana = box2.get();
}
}
// Banana 객체 클래스
public class Banana extends Fruit{
}
Fruit 객체를 상속받은 Apple 객체와 Banana 객체가 타입 파라미터에 들어가도 오류가 발생되지 않음을 알 수 있다
그러나 이런식으로 제네릭 타입을 특정한 객체로 해두면 다른 타입은 들어가지 못한다

주석에도 적어놨지만
FruitBox 제네릭 클래스에 타입 파라미터를 <T extends Fruit> 으로 지정했기 때문에 Fruit 객체와 Fruit을 상속받은 객체들만 사용할 수 있음을 알 수 있다
위와 반대로 제네릭 클래스의 타입 파리미터가 상속받은 값이 아니면 어떻게 반응할까
클래스의 형변환처럼 형변환이 가능할까
public class App4 {
public static void main(String[] args) {
// box1은 Fruit 객체를 저장하는 박스 객체다
FruitBox<Fruit> box1 = null;
box1 = new FruitBox<Fruit>();
}
}
위 클래스에 아래와 같은 코드를 넣으면 어떻게 반응할까??
box1 = new FruitBox<Apple>();
box1 = new FruitBox<Banana>();

Type mismatch 에러가 발생한다
제네릭 객체의 타입 파라미터끼리는 형변환이 불가능한 것을 알 수 있다
와일드카드
- 제네릭 타입에 다형성을 적용할 때 사용한다.
- '?', 'extends', 'super'를 사용해서 다룰 객체의 상한과 하한을 지정할 수 있다.
- <?> : 제한없음. 타입 파라미터의 구체적인 타입으로 모든 클래스, 모든 인터페이스 타입이 가능하다
- <? extends X > : 상위 클래스 제한(X포함 X를 상속받은 자식들만). 타입 파라미터의 구체적인 타입으로 상위타입 혹은 지정된 상위타입의 하위타입만 가능하다
- <? super x> : 하위 클래스 제한(x포함 x의 조상들만). 타입 파라미터의 구체적인 타입으로 하위타입 혹은 지정된 하위타입의 상위타입만 가능하다
와일드카드 예시
public void 전공과정등록(Course<? extends Student> course) { ... }
// Course<Student> c = new Course<>();
// Course<CollegeStudent> c = new Course<>();
// Course<PostGraduatedStudent> c= new Course<>();
// 위의 3개 과정은 전공과정등록() 메소드의 매개변수에 전달할 수 있는 과정이다.
public void 대학원과정등록(Course<? extends PostGraduatedStudent> course) { ... }
// Course<PostGraduatedStudent> c= new Course<>();
// 위의 과정은 전공과정등록() 메소드의 매개변수에 전달할 수 있는 과정이다.
public void 직장인및일반인과정등록(Course<? super Worker> course) { ... }
// Course<Worker> c = new Course<>();
// Course<Person> c = new Course<>();
// 위의 2개 과정은 전공과정등록() 메소드의 매개변수에 전달할 수 있는 과정이다.
인터페이스에 제네릭 사용하기
// 구현클래스 정의 싯점에 데이터타입을 지정하기
public inteface Comparable<T> {
int compareTo(T other);
}
//제네릭 인터페이스를 구현한 클래스의 T가 Product로 대체된다.
public class Product implements Comparable<Product> {
private String name;
private int price;
public int compareTo(Product other) {
return this.price - other.price;
}
}
// 구현클래스 정의시점에 데이터타입을 지정하지 않고, 객체 생성시점에 지정하게 하기
public interface Collection<E> {
boolean add(E e);
}
public class ArrayList<E> implements Collection<E> {
...
public boolean add(E e) {
수행문
}
}
제네릭 메소드
- 메소드의 선언부에 타입파라미터가 선언된 메소드
- 메소드의 선언부에 <T>와 같은 타입파라미터 정의가 있어야 한다
- 제네릭 메소드는 제네릭클래스가 아닌 곳에서도 정의할 수 있다.
class Box<T> {
public void add(T t) { ... }
public T get(int index) { ... }
public <T> T[] toArray(T[] arr) { ... } // 제네릭 메소드
//이 메소드의 T는 Box<T>와 아무 상관이 없다.
//이 메소드의 T는 메소드 실행시 전달된 배열의 타입에 전적으로 의존한다.
}
class Arrays {
public static <T> void sort(T[] arr) { ... } // 제네릭 메소드
//sort() 메소드 실행시 전달받은 배열의 타입이 T의 타입이 된다.
//Arrays.sort(new String[]{"김유신", "강감찬"}); ---> <T>는 <String>이 된다.
}
class ApplicationContext {
public <T> T getBean(String beanName, Class<T> clazz) { ... }
//getBean() 메소드 실행시 전달받은 객체의 설계도 타입이 T의 타입이 된다.
//ctx.getBean("service", UserService.class) ---> <T>는 <UserService>가 된다.
}'Java' 카테고리의 다른 글
| Java_Map, Comparable (2) | 2023.11.21 |
|---|---|
| Java_Collection Framework, Iterator (0) | 2023.11.21 |
| Java_StringBuilder/Buffer, Date 클래스, Math 클래스 (0) | 2023.11.16 |
| Java_Wrapper 클래스 (0) | 2023.11.16 |
| Java_String 클래스 (0) | 2023.11.14 |