본문 바로가기

Java/문법

16 - 4장 제네릭 타입 범위 제한

1. 제네릭 타입 범위 제한의 필요성 

 

-과일 클래스(사과, 배, 딸기) 문구 클래스(연필,지우개.볼펜)

-과일의 종류만 저장 및 관리하는 제네릭 클래스 생성? 문구류만 저장 및 관리하는 클래스 생성?

class Goods<T> { //과일류 또는 문구류만 저장하는 것은 불가능
	private T t;
    public T get() {
    	return t;
    }
    public void set(T t) {
    	this.t=t;
    }
}

제네릭 타입의 범위 제한

- 위 기능을 구현하려면 제네릭 타입으로 올 수 있는 실제 타입의 종류를 제한해야함

- 입력매개변수의 제네릭 타입 변수가 Number 클래스로 한정된다면 실제 제네릭타입으로는 Number 또는 Number 클래스의 자식 클래스인 Integer, Long, Float, Double등과 같은 타입만 지정 가능

 

2. 제네릭 타입 범위 제한의 종류와 타입 범위 제한 방법 

 

1) 제네릭 클래스의 타입 제한

 

- 제네릭 클래스의 정의 과정에서 제네릭 타입을 제한

- <제네릭 타입 변수 extends 상위 클래스>와 같이 제네릭 타입으로 대입될 수 있는 최상위 클래스를 extends 키워드와 함께 정의 

접근 지정자 class 클래스명 <T extends 최상위 클래스/인터페이스명> {
}
class Goods <T extends Fruit> {
	private T t;
    public T get() {return t;}
    public void set(T t) {this.t =t;}
}

Goods<Apple> goods = new Goods<>(); //o
Goods<Pencil>goods = new Goods<>(); //x

 - <T extends Fruit> -> 제네릭 타입으로 Fruit 또는 그 자식 클래스만 대입 가능

 

-extends -> '최상위 클래스/인터페이스로 지정하라'

-뒤에 나오는 요소가 클래스이든, 인터페이스이든 항상 extends 키워드를 사용해야 함

 

ex) 상속 관계 A<-B<-C

- 제네릭 타입으로 클래스 B 또는 클래스 B의 자식 클래스만 오도록 제한

- 클래스 D의 제네릭 타입으로 사용할 수 있는 타입은 B와 C로 한정

- 제네릭 타입을 생략하면 대입될 수 있는 모든 타입의 최상위 클래스가 입력된 것으로 간주 

class D <T extends B> {
	private T t;
    public T get() {return t;}
    public void set(T t) {this.t=t;}
}
D<A> d1 = new D<>(); //불가능
D<B> d2 = new D<>();
D<C> d3 = new D<>();
D d4 = new D<>(); //D<B> d4 =D<>();와 동일

 

2) 제네릭 메서드의 타입 제한

 

-<제네릭 타입 변수 extends 상위 클래스>와 같이 올 수 있는 최상위 타입을 정의 

-클래스와 인터페이스 모두 extends 키워드를 사용 

접근 지정자 <T extends 최상위 클래스/인터페이스명> T 메서드명(T t) [
	//최상위 클래스의 메서드 사용 가능
}

- 제네릭 메서드에서 중요한 것은 메서드 내부에서 사용할 수 있는 메서드의 종류

 <T extends String>

 -> 모든 타입의 최상위 타입이 String이기 때문에 해당 제네릭 메서드의 내부에서는 String 객체의 멤버(필드/메서드)를 사용 가능 

 -> String은 자식 클래스를 생성할 수 없는 final 클래스 

 

class GenericMethods {
	public <T> void method1(T t) {
    	char c = t.charAt(0); //object 메서드만 사용 가능 
        System.out.println(c);
     }
     
     public<T extends Stirng> void method2(T t) {
     	char c = t.charAt(0); //String의 메서드 사용 가능
        System.out.println(c);
     }
 }

 

<T extends Number>

 -> 제네릭 메서드인 method1()은 제네릭 타입으로 올 수 있는 최상위 타입을 Number 타입으로 제한 

 -> Number 클래스의 메서드인 intValue() 호출 가능 

class A{
	public <T extends Number> void method1(T t) {
    	System.out.println(t.intValue()); //값을 정수로 리턴하는 Number 클래스의 메서드 
     }
}

 

<T extends MyInterface>

 -> 인터페이스 MyInterface 정의하고 내부에는 print() 추상메서드를 갖고 있다.

 -> 제네릭 메서드 method1()은 최상위 제네릭 타입을 MyInterface로 한정 

 -> 이 메서드 내부를 호출하기 위해서 MyInterface 객체를 생성하고, 이 과정에서 print() 메서드를 구현해야 함

interface MyInterface {
	public abstrat void print();
}
class B{
	public <T extends MyInterface> void method1(T t) {
    	t.print();
     }
}

 

- 제네릭 메서드에서의 제네릭 타입 제한 범위 설정

package GenericMethod;

class A{
	public <T extends Number> void method1(T t) {
		System.out.println(t.intValue());
	}
}

interface MyInterface {
	public abstract void print();
}

class B{
	public<T extends MyInterface> void method1(T t) {
		t.print();
	}
}

public class GenericMethod {
	public static void main(String[] args) {
		A a= new A();
		a.method1(5.8);
		
		B b= new B();
		b.method1(new MyInterface(){
			//Override
			public void print() {
				System.out.println("print() 구현");
				}
			});
	}
}

 

3) 메서드 매개변수일 때 제네릭 클래스의 타입 제한

 

- 제네릭 클래스 타입 변수가 일반 메서드의 입력매개변수로 사용

- 제네릭 클래스 객체의 제네릭 타입

리턴 타입 메서드명(제네릭 클래스명<제네릭 타입명>참조변수명){
	//... 제네릭 타입을 특정 타입으로 확정
} 

리턴 타입 메서드명(제네릭 클래스명<?> 참조변수명){
	//... 제네릭 타입으로 어떤 것이 대입됐든 해당 제네리 객체이기만 하면 매개변수로 사용 가능
}

리턴 타입 메서드명(제네릭 클래스명<? extends 상위 클래스/인터페이스> 참조변수명) {
	//... 상위 클래스or 상위 클래스의  자식 클래스 타입
}

리턴 타입 메서드명(제네릭 클래스명<? super> 하위 클래스/인터페이스> 참조변수명) {
	//... 하위 클래스 or  하위 클래스의 부모 클래스 타입
}
method(Goods<A> v) //제네릭 타입=A인 객체만 가능
method(Goods<?> v) //제네릭 타입 = 모든 타입인 객체 가능
method(Goods<? extends B> v) //제네릭 타입 =B 또는 B의 자식 클래스인 객체만 가능
method(Goods<? super B> v) //제네릭 타입 =B 또는 B의 부모 클래스인 객체만 가능

 

- 메서드 매개변수로서 제네릭 클래스의 타입 제한 범위 설정

 -A<-B 상속관계를 가진다고 해서 Goods<A> <-Goods<B>의 관계를 갖는 것은 아님 

 -Goods<A> 와 Goods<B>는 서로 상속관계에 있는 것이 아니라 대입된 제네릭 타입만 다른 완벽히 동일한 클래스

package GenericMethod;

class A{}
class B extends A{}
class C extends B{}
class D extends C{}

class Goods<T> {
	private T t;
	public T get() {
		return t;
	}
	public void set(T t) {
		this.t=t;
	}
}

class Test{
	void method1(Goods<A> g) {} //case1
	void method2(Goods<?> g) {} //case2
	void method3(Goods<? extends B> g){}//case3
	void method4(Goods<? super B>g) {} //case4
}

public class GenericMethod {
	public static void main(String[] args) {
		Test t = new Test();
		
		//case1
		t.method1(new Goods<A>());
		//t.method1(new Goods<B>());
		//t.method1(new Goods<C>());
		//t.method1(new Goods<D>());
		
		//case2
		t.method2(new Goods<A>());
		t.method2(new Goods<B>());
		t.method2(new Goods<C>());
		t.method2(new Goods<D>());
		
		//case3
		//t.method3(new Goods<A>());
		t.method3(new Goods<B>());
		t.method3(new Goods<C>());
		t.method3(new Goods<D>());
		
		//case4
		t.method4(new Goods<A>());
		t.method4(new Goods<B>());
		//t.method4(new Goods<C>());
		//t.method4(new Goods<D>());
	}
}