제네릭
- 타입을 일반화하는 것을 의미하며, 특별한 타입을 여러번 만드는 것을 방지함
- 결정되지 않는 타입을 파라미터로 처리하고 실제 사용할 때 파라미터를 구체적인 타입으로 대체시킴!
package ch13.sec01;
//타입 파라미터로 T를 사용함
public class Box<T> {
// T : 임의의 타입을 설정할 수 있음
// 임의의 타입의 값을 저장하는 변수 선언
public T content;
}
package ch13.sec01;
public class GenericExample {
public static void main(String[] args) {
// 파라미터 타입을 String 타입으로 결정
Box<String> box1 = new Box<>();
// String 타입의 값을 넣음
box1.content = "이동주";
// 제네릭 타입으로 받은 값을 String 변수에 대입
String str = box1.content;
System.out.println(str);
// 파라미터 타입을 int(Integer) 타입으로 변경
// 객체를 대입해야 하기 때문에 Integer 사용
Box<Integer> box2 = new Box<>();
// int 타입의 값을 넣음
box2.content = 100;
int value = box2.content;
System.out.println(value);
}
}
제네릭 타입
- 결정되지 않은 타입을 파라미터로 가지는 클래스와 인터페이스
- 선언부에 '<>' 부호가 붙고 그 사이에 타입 파라미터들이 위치함
- 타입 파라미터는 일반적으로 대문자 한 글자로 표현함
-> 에서 T는 타입 파라미터임을 뜻하는 기호임.
- 외부에서 제네릭 타입을 사용할 경우 타입 파라미터에 구체적인 타입을 지정함
-> 지정하지 않으면 Object 타입이 암묵적으로 사용됨
파라미터가 두 개인 클래스
package ch13.sec02.exam01;
//제네릭 타입을 두 개 사용하겠다는 의미
public class Product<K, M>{
// 필드를 두 개 생성
private K kind;
private M model;
// private로 필드를 선언했기 때문에
// getter, setter를 이용해 클래스의 필드를 사용 가능
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;
}
}
package ch13.sec02.exam01;
public class Car {
}
package ch13.sec02.exam01;
public class TV {
}
package ch13.sec02.exam01;
public class GenericExample {
public static void main(String[] args) {
// K는 TV로, M은 String으로 대체함
Product<TV, String> product1 = new Product<>();
// Setter의 매개값 설정
// TV 객체를 생성하여 매개값으로 설정 > TV 객체의 타입으로 설정!
product1.setKind(new TV());
// String 타입의 값 넣기
product1.setModel("스마트TV");
// getKind()의 리턴값 : TV 타입
TV tv = product1.getKind();
// getModel()의 리턴값 : String 타입
String tvModel = product1.getModel();
// K는 Car로, M은 String으로 대체함
Product<Car, String> product2 = new Product<>();
// Setter의 매개값 설정
// Car 객체를 생성하여 매개값으로 설정 > Car 객체의 타입으로 설정
product2.setKind(new Car());
// String 타입의 값 넣기
product2.setModel("SUV자동차");
// getKind()의 리턴값 : Car 타입
Car car = product2.getKind();
// getModel()의 리턴값 : String 타입
String carModel = product2.getModel();
}
}
인터페이스를 제네릭 타입으로 선언
package ch13.sec02.exam02;
// <P> : P라는 임의의 파라미터값 선정
public interface Rentable<P> {
// P를 리턴 타입으로 사용
P rent();
}
package ch13.sec02.exam02;
public class Car {
public void run() {
System.out.println("자동차가 달립니다");
}
}
package ch13.sec02.exam02;
public class Home {
// 메소드선언
public void turnOnLight() {
System.out.println("전등을 켭니다");
}
}
package ch13.sec02.exam02;
// Rentable 인터페이스를 참조함
public class HomeAgency implements Rentable<Home>{
// Rentable의 메소드를 오버라이드
@Override
public Home rent() {
// 파라미터값이 Home 객체로 설정되어서 리턴 타입이 Home 타입이여야 함
// Home 객체를 생성 후 해당 객체를 반환함
return new Home();
}
}
package ch13.sec02.exam02;
//Rentable 인터페이스를 참조함
public class CarAgency implements Rentable<Car>{
// Rentable의 메소드를 오버라이드
@Override
public Car rent() {
// 파라미터값이 Car 객체로 설정되어서 리턴 타입이 Car 타입이여야 함
return new Car();
}
}
package ch13.sec02.exam02;
public class GenericExam {
public static void main(String[] args) {
// HomeAgency 객체를 생성!
HomeAgency homeAgency = new HomeAgency();
// HomeAgency에 선언된 rent() 메소드 실행
Home home = homeAgency.rent();
// HomeAgency의 리턴 타입이 Home 객체 타입이므로
// Home 객체에 있는 메소드를 실행할 수 있음
home.turnOnLight();
// CarAgency 객체를 생성
CarAgency carAgency = new CarAgency();
// CarAgency에 선언된 rent 메소드 실행
Car car = carAgency.rent();
// CarAgency의 리턴 타입이 Car 객체 타입이므로
// Car 객체에 있는 메소드를 실행할 수 있음
car.run();
}
}
Box 내부의 내용물 비교
package ch13.sec02.exam03;
// <T> : 제네릭 타입을 한 개만 사용함
public class Box<T> {
// 임의의 타입 값을 content 변수에 저장
public T content;
// Box의 내용물이 같은 지 비교하는 메소드
// Box 객체에서 사용될 임의의 타입 매개변수를 가짐
public boolean compare(Box<T> other) {
// content에 저장된 값을 비교
return content.equals(other.content);
}
}
package ch13.sec02.exam03;
public class GenericExam {
public static void main(String[] args) {
// Box의 제너릭타입 객체 생성 및 <T>를 String 타입으로 설정
Box<String> box1 = new Box<>();
box1.content = "100";
// Box의 제너릭타입 객체 생성 및 <T>를 String 타입으로 설정
Box<String> box2 = new Box<>();
box2.content = "100";
// 두 값이 같은지 확인
boolean result1 = box1.compare(box2);
System.out.println("result1: " + result1);
}
}
제네릭 메소드
- 타입 파라미터를 가지고 있는 메소드로 타입 파라미터가 메소드 선언부에 정의
- 리턴 타입 앞에 <> 기호를 추가하고 타입 파라미터 정의 후 리턴 타입과 매개 변수 타입에서 사용함
- 타입 파라미터는 매개값의 타입에 따라 컴파일 과정에서 구체적인 타입으로 대체
package ch13.sec03;
// <T> : 임의의 타입을 하나 사용하는 제네릭 타입
public class Box<T> {
// private 접근 방식
private T t;
// Source > Generate 생성자 만들기를 통해 생성자 만듬
public Box(T t) {
super();
this.t = t;
}
// T(임의의 타입) 타입의 get 메소드
// 임의의 타입 (T) 값을 리턴받음
public T get() {
return t;
}
// T(임의의 타입) 타입의 set 메소드
// 임의의 타입을 매개변수로 받아서 변수 t에 저장
public void set(T t) {
// 해당 필드에 값 대입
this.t = t;
}
}
package ch13.sec03;
public class GenericExam {
// 제네릭 메소드
// <T>의 타입(임의의 타입)을 가지는 메소드
// Box<T> 객체의 형태로 반환한다는 의미
public static <T> Box<T> boxing(T t){
// 임의의 타입 T를 가지는 Box 객체 생성
// 임의의 타입 매개변수를 가지는 Box 생성자 호출
return new Box<T>(t);
}
public static void main(String[] args) {
// T를 Integer 타입으로 변환 후 정수 형태의 값을 넣음
Box<Integer> box1 = boxing(100);
// 위에서 받은 값을 intValue에 대입
int intValue = box1.get();
System.out.println(intValue);
// T를 Integer 타입으로 변환 후 정수 형태의 값을 넣음
Box<String> box2 = boxing("홍길동");
// 위에서 받은 값을 strValue에 대입
String strValue = box2.get();
System.out.println(strValue);
}
}
제한된 타입 파라미터
- 모든 타입으로 대체할 수 없고, 특정 타입과 자식 또는 구현 관계에 있는 타입만 대체할 수 있는 타입 파라미터
- 상위 타입은 클래스뿐만 아니라 인터페이스 가능
package ch13.sec04;
public class GenericExam {
// T(임의의 타입)가 Number 객체를 상속받음으로서 T의 타입은 수치 형태로만 사용 가능함
public static <T extends Number> boolean compare(T t1, T t2) {
// getClass() : 객체의 전체 클래스 이름 얻음
// getSimpleName() : 클래스의 이름 (패키지명 x) 반환
// getClass에서 객체의 전체 클래스명을 얻고, getSimpleName에서 클래스명만을 추출할 수 있음
// 해당 타입을 알기 위한 구문
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);
System.out.println();
// 수치 형태의 매개변수만 사용할 수 있도록 제한을 걸었기 때문에
// 문자열 형태의 인자는 사용 불가
// boolean result3 = compare("1", "1");
// System.out.println(result3);
// System.out.println();
}
}
와일드카드 타입 파라미터
- 제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 범위에 있는 모든 타입으로 대체할 수 있는 타입 파라미터 ? 표시
- super : 해당 객체를 기준으로 부모 객체까지 사용

package ch13.sec05;
public class Person {
}
class Worker extends Person{
}
class Student extends Person{
}
class HighStudent extends Student{
}
class MiddleStudent extends Student{
}
package ch13.sec05;
public class Applicant<T> {
public T kind;
// 임의의 타입의 매개변수를 가지는 Applicant 생성자 선언
public Applicant(T kind) {
this.kind = kind;
}
}
package ch13.sec05;
public class Course {
// 모든 사람이면 등록 가능
// <?> : 해당 범위에 있는 모든 타입으로 대체 가능
public static void registerCourse1(Applicant<?> applicant) {
System.out.println(applicant.kind.getClass().getSimpleName() +
"이(가) Course1을 등록함");
}
// 학생만 등록 가능
// <? extends a> a 객체와 그 자식 클래스들의 타입으로만 대체가 가능
public static void registerCourse2(Applicant<? extends Student> applicant) {
System.out.println(applicant.kind.getClass().getSimpleName() +
"이(가) Course2을 등록함");
}
// 일반인 및 직장인만 등록 가능
// <? super a> a객체와 그 부모 클래스들의 타입으로만 대체가 가능
public static void registerCourse3(Applicant<? super Worker> applicant) {
System.out.println(applicant.kind.getClass().getSimpleName() +
"이(가) Course3을 등록함");
}
}