제네릭

이동주·2025년 3월 13일

JAVA

목록 보기
24/30

제네릭

  • 타입을 일반화하는 것을 의미하며, 특별한 타입을 여러번 만드는 것을 방지함
  • 결정되지 않는 타입을 파라미터로 처리하고 실제 사용할 때 파라미터를 구체적인 타입으로 대체시킴!
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을 등록함");
	}
}
profile
끄작끄작

0개의 댓글