프로그래밍 언어/JAVA

[JAVA] 자바의 제네릭 Generic

Rateye 2021. 1. 19. 15:51
728x90
반응형
  • Object 타입을 파라미터로 갖는 경우 모든 데이터타입을 전달받을 수 있고 모든 객체를 전달할 수 있기 때문에 데이터 저장 시 편리함
  • 그러나, 객체를 꺼내서 사용해야할 경우 형변환이 필요하며 잘못된 변환 수행 시 ClassCastException 이 발생할 수도 있다
class Toy {
	String toyName;
}

class Icecream {
	String icecreamName;
}

class NormalBox {
	Object item;
	
	public NormalBox() {}

	public NormalBox(Object item) {
		this.item = item;
	}

	public Object getItem() {
		return item;
	}

	public void setItem(Object item) {
		this.item = item;
	}

}
		NormalBox box = new NormalBox();
		box.setItem(new Toy());
		
		Object item = box.getItem();
		//		Toy toy = (Toy) item; // 형변환을 통해 Toy 타입으로 변환
		// => 만약, Toy 가 아닌 다른 객체일 경우 문제 발생하므로
		//    형변환 전 변환 가능 여부를 판별해야한다!
		// => instanceof 연산자를 통해 변환 가능 여부 판별
		if(item instanceof Toy) { // item 객체를 Toy 타입으로 변환 가능한가?
			Toy toy = (Toy) item;
		} else if(item instanceof Icecream) { // Icecream 타입으로 변환 가능한가?
			Icecream icecream = (Icecream)item;
		} else {
			System.out.println("변환 불가능한 객체!");
		}

제네릭 Generic

  • 컴파일 시점에 사용 가능한 객체의 타입을 체크하는 것
  • 클래스 또는 인터페이스 정의할 때 해당 클래스 또는 인터페이스에서 사용하기 위한 어떤 타입을 미리 지정하는 것이 아니라 제네릭 타입으로 선언한 후 실제 객체 사용을 위해 인스턴스를 생성하는 시점에서 사용할 타입을 결정하는 것
  • 자바에서 제공하는 컬렉션 프레임워크 등의 클래스 및 인터페이스에는 제네릭이 적용된 경우가 많으며, 이 클래스 등의 인스턴스 생성 시 실제 사용할 데이터타입을 지정해줘야 한다
주의사항!
1. 제네릭 타입은 static 멤버에 지정 불가
인스턴스 생성 시점에 타입이 결정되어야 하는데 static 멤버는 인스 턴스 생성 시점보다 먼저 로딩되기 때문
2. private static T some2;제네릭타입은 static 멤버에 사용 불가

제네릭 타입을 적용한 클래스 정의

  • 클래스 또는 인터페이스 선언 시 이름 뒤에 <> 기호를 쓰고 해당 기호 사이에 알파벳 대문자 한 글자를 지정(제네릭 타입 지정) (클래스 내에서 사용하게 될 임시 데이터타입 이름 지정) 주로 대문자 T(Type) 또는 E(Element) 를 사용
  • 지정된 임시 데이터타입은 객체 생성 시점에서 실제 데이터타입으로 변경됨 (선언 시점에서는 실제 존재하지 않는 데이터타입)
class Toy {
	String toyName;
}

class Icecream {
	String icecreamName;
}
class NormalBox {
	// Toy, Icecream 등 다양한 타입을 저장하기 위해 Object 타입 변수를 선언
	private Object some;

	public Object getSome() {
		return some;
	}

	public void setSome(Object some) {
		this.some = some;
	} 
	
}

제네릭 클래스 정의

class GenericBox<T> { // 임시 데이터타입으로 T 를 지정
	private T some; // 변수 some 의 데이터타입은 무엇으로도 변할 수 있음
	
//	private static T some2; // 제네릭타입은 static 멤버에 사용 불가
	
//	T obj = new T(); // 제네릭타입으로 인스턴스 생성 불가
	
	public T getSome() {
		return some;
	}

	public void setSome(T some) {
		this.some = some;
	}
	
	public void method() {
		// 제네릭타입은 instanceof 연산자의 타입 파라미터로 사용 불가
		Object o = new Object();
		//		if(o instanceof T) {}
	}
	
}

 

		// 제네릭 타입이 적용된 GenericBox 클래스의 인스턴스 생성
		// => 주의! 제네릭 타입에 실제 데이터타입 지정 시
		//    반드시 참조 데이터타입을 사용해야한다!
		//		GenericBox<int> gb; // 기본데이터타입을 제네릭 타입에 지정 불가!
		//		GenericBox<Integer> gb; // Wrapper 클래스 타입으로 사용 가능!
		
		// GenericBox 인스턴스의 제네릭 타입을 Toy 타입으로 지정하기 위해
		// 클래스명 뒤에 <Toy> 타입을 지정하고
		// 생성자 호출 코드의 생성자명 뒤에도 <Toy> 타입을 지정
		// => 클래스 내의 임시 데이터타입(T)이 모두 Toy 타입으로 바뀜
		GenericBox<Toy> toyBox = new GenericBox<Toy>();
		
		// 파라미터 타입이 Toy 타입으로 변경되었으므로
		// 다른 데이터타입은 사용 불가능하게 바뀐다!
		// => 즉, 데이터를 저장하는 시점에서 미리 타입 검사가 수행됨
		//		toyBox.setSome(new Icecream()); // Toy 타입 객체만 전달 가능
		toyBox.setSome(new Toy()); // Toy 타입 전달 가능
		
		// 저장된 객체를 꺼내올 때 별도의 변환 없이
		// 원본 그대로의 타입을 사용 가능(문제 발생 소지 없음)
		Toy toy = toyBox.getSome();
		
		// 만약, 다른 데이터타입의 객체를 저장해야할 경우
		// 새로운 객체를 생성하는 시점에서 해당 데이터타입을 지정하면 됨
		GenericBox<Icecream> icecreamBox = new GenericBox<Icecream>();
		// 제네릭타입 T 가 Icecream 타입으로 변경됨
		icecreamBox.setSome(new Icecream());
		Icecream icecream = icecreamBox.getSome();
		
		// =====================================================
		// 만약, Object 타입을 사용해야하는 경우
		//		GenericBox<Object> objectBox = new GenericBox<Object>();
		// Object 타입의 경우 제네릭타입 지정을 생략해도 자동으로 적용됨
		GenericBox objectBox = new GenericBox();
		objectBox.setSome(new Icecream());
		objectBox.setSome(new Toy());

 

제네릭 타입을 적용한 클래스 상속

  • 부모로부터 상속받을 때 제네릭타입도 상속받아 표현하려면 서브클래스에도 부모의 제네릭 타입을 반드시 명시해야한다! 단, 서브클래스만의 제네릭 타입을 추가하는 것은 상관없음
interface Useable<D> {}

class SuperClass<P> {
	protected P product;
}

class SubClass<P, D, M> extends SuperClass<P> implements Useable<D> {
	
}

제네릭 타입의 파라미터 타입 제한

  • 제네릭 타입으로 사용된 타입 파라미터에는 Object 타입을 비롯한 모든 타입을 지정 가능한데 만약, 특정 타입만 지정하도록 강제하려면 extends 키워드를 사용하여 타입 파라미터의 강제성을 부여 가능
  • 인터페이스를 타입 제한용으로 지정할 때에도 extends 사용

기본 문법

class 클래스명<제네릭타입명 extends 클래스타입> {}

인스턴스 생성 시점에서 지정 가능한 제네릭타입은 Number 클래스와 Number 클래스의 서브클래스 타입만 지정

ex. Integer, Double 등

public class Ex2 {

	public static void main(String[] args) {
		// NumberBox 클래스의 제네릭타입은 Number 타입까지만 사용 가능하므로
		// String 등 Number 타입이 아닌 타입 지정 불가능
		//		NumberBox<String> box = new NumberBox<String>(); // 오류 발생
		//		NumberBox<Object> box = new NumberBox<Object>(); // 오류 발생
		
		NumberBox<Integer> box = new NumberBox<Integer>();
	}

}

class NumberBox<T extends Number> {}
// => 제네릭타입으로 지정 가능한 타입은 Number 와 그 서브클래스 타입만 가능

제네릭 타입이 적용된 메서드

  • 특정 메서드 내에서만 사용 가능한 타입 지정
  • 메서드 리턴타입 앞에 제네릭 타입을 지정
  • 리턴타입 또는 파라미터 타입으로 사용 가능

기본 문법

[제어자] <제네릭타입> 리턴타입 메서드명([파라미터...]) {}

제네릭타입 지정 방법

메서드 호출 시 메서드명 앞에 제네릭 타입을 명시

참조변수명.<데이터타입>메서드명([파라미터...]);
class GenericMethod {
	
	public void normalMethod() {
		System.out.println("일반 메서드");
	}
	
	public <P> void genericMethod1(P p) {
		// 메서드 파라미터로 전달받을 데이터타입을 제네릭타입 P 로 지정
		// => 리턴타입 앞에 제네릭타입 <P> 지정 필수!
		System.out.println("파라미터 타입 : " + p.getClass().getName());
		System.out.println("전달받은 데이터 : " + p);
	}
	
	public <P> P genericMethod2(P p) {
		// 메서드 파라미터와 리턴타입을 제네릭타입 P 로 지정
		// => 리턴타입 앞에 제네릭타입 <P> 지정 필수!
		System.out.println("파라미터 타입 : " + p.getClass().getName());
		System.out.println("전달받은 데이터 : " + p);
		return p;
	}
	
}
		GenericMethod gm = new GenericMethod();
		gm.<String>genericMethod1("홍길동");
		gm.<Integer>genericMethod1(10);
		
		int num = gm.<Integer>genericMethod2(20);
		System.out.println("리턴받은 데이터 : " + num);
		
		String str = gm.<String>genericMethod2("홍길동");
		System.out.println("리턴받은 데이터 : " + str);

제네릭 메서드에서의 타입 파라미터 제한

// 제네릭 메서드에서의 타입 파라미터 제한
		WildcardGenericType wgt = new WildcardGenericType();
		
		// 메서드 내의 파라미터 타입에 제네릭 타입을 ? 로 지정했을 경우
		// extends Object 와 동일하게 취급되므로 모든 타입을 제네릭 지정 가능
		wgt.method1(new GenericBox3<Object>());
		wgt.method1(new GenericBox3<Person>());
		wgt.method1(new GenericBox3<SpiderMan>());
		
//		wgt.method2(new GenericBox3<Object>()); // 오류 발생
		// Person 또는 Person 클래스의 서브클래스 타입(SpiderMan)만 가능
		wgt.method2(new GenericBox3<Person>());
		wgt.method2(new GenericBox3<SpiderMan>());
		
		wgt.method3(new GenericBox3<Object>());
		wgt.method3(new GenericBox3<Person>());
//		wgt.method3(new GenericBox3<SpiderMan>()); // 오류 발생
		// Person 또는 Person 클래스의 슈퍼클래스 타입(Object)만 가능
		
		System.out.println("=========================");
		
		// 제네릭 타입을 메서드 파라미터에서 제한하는 예
		ArrayList<Number> list = new ArrayList<Number>();
//		list.addAll(c);
		// public boolean addAll(Collection<? extends Number> c)
		// => addAll() 메서드 파라미터로 Collection 객체를 전달 가능하나
		//    이 때, Collection 객체의 제네릭 타입은 Number 타입 또는
		//    Number 클래스의 서브클래스 타입만 사용 가능하다!
728x90
반응형