평범한 연구소

[JAVA] 제네릭 generic 본문

JAVA/기본 개념

[JAVA] 제네릭 generic

soyeonisgood 2022. 8. 3. 00:03

제네릭

  • 다양한 타입의 객체 다루는 클래스나 인터페이스에서 사용할 데이터 타입을 인스턴스 생성 시 결정하는 것.
  • 데이터 형에 대한 별도의 메소드나 멤버 변수를 구현할 필요 없이 미리 정의된 메소드 또는 멤버 변수에 서로 다른 자료 형으로 처리할 수 있다.
  • 객체 타입을 컴파일 할 때 체크하므로 객체 타입의 안정성 높이고 불필요한 형 변환 줄임. (런타임오류 발생 확률 줄어든다)
  • 객체 생성 시. 즉, 실행할 때 자료형이 결정
  • 속도는 일반 자료형보다 느리지만, 안정성 높고 불필요한 형변환이 줄어듬.
  • 반복적인 코드를 줄일 수 있으며, 재사용성 증가로 유지보수가 편리함.
  • 엄격한 문법으로, 부모클래스도 허용X. =상속 허용X

 

제네릭 클래스 및 제네릭 인터페이스

  • 타입 파라미터를 하나 이상 갖는 제네릭 타입의 클래스 또는 인터페이스
  • 클래스나 인터페이스를 선언할 때 타입 지정하지 않고 인스턴스를 생성할 때 사용할 타입을 파라미터로 지정
  • 클래스나 인터페이스를 선언할 때 클래스 또는 인터페이스 이름 뒤에 <> 부호 붙이고, <>부호 사이에 타입 파라미터 지정
  • 제네릭은 두 개 이상의 타입 파라미터를 가질 수 있고, 각 타입은 콤마, 로 구분

 

타입 파라미터

  • 대문자 알파벳 한 문자 사용
  • E: Element
  • K: Key
  • N: Number
  • T: Type
  • V: Value
  • S, U, V: 2nd, 3rd, 4th types

 

제한된 타입 파라미터

  • 파라미터로 전달되는 타입의 종류 제한하는 기능
  • extends 키워드로 사용
  • ex) <타입파라미터 extends 상위클래스>

 

제네릭 메소드 호출 형식

  • 1) 메소드 호출 형식
    • 명시적으로 파라미터 지정
    • ex) 리턴타입 변수 = 객체명.<명시적타입>메소드명(매개변수);
  • 2) 매개변수값 타입으로 타입 파라미터 지정
    • ex) 리턴타입 변수 = 객체명.메소드명(매개변수값);

 

 

제네릭 예제

  • generic 타입을 명시하지 않으면 Object로 처리되고 오류는 아니나, 경고. 쓰지말 것
public class Ex03 {

	public static void main(String[] args) {
		Test3<String> t = new Test3<>();
		t.setValue("서울");

		String s = t.getValue(); // 캐스팅을 하지 않는다.
		System.out.println(s+" : "+s.length());
		
		// t.setValue(10); // 컴오류
		
		Test3<Integer> t2 = new Test3<Integer>();
		t2.setValue(30);
		int n = t2.getValue();
		System.out.println(n);
		
		/*
		Test3 t3 = new Test3(); // generic 타입 명시하지 않으면 Object으로 처리하며 경고!
		t3.setValue(10);
		t3.setValue("서울");
		*/
		
	}

}

class Test3<T> {
	private T value;

	public T getValue() {
		return value;
	}

	public void setValue(T value) {
		this.value = value;
	}
	
}

 

  • getClass().getName(): 클래스의 이름을 문자열로 출력
  • T: java.lang.String,  U: java.lang.Integer
public class Ex04 {
    public static void main(String[] args) {
		Test4<String, Integer> t = new Test4<>();
		
		t.set("서울", 950);
		t.disp();
	}
}

// 두 개의 타입 파라미터
class Test4<T, U> {
	private T t;
	private U u;
	
	public void set(T t, U u) {
		this.t = t;
		this.u = u;
	}
	
	public void disp() {
		System.out.println("T: "+t.getClass().getName() + ", "+t);
		System.out.println("U: "+u.getClass().getName() + ", "+u);
	}
}

 

  • 제너릭은 일반적으로 다운캐스팅 하지 않는다. 배열과 같은 어쩔 수 없이 해야하는 상황이라면, 경고를 없애주기.
  • @SuppressWarnings("unchecked"): 경고 없애기
  • 어노테이션 관련 개념은 어노테이션 포스팅 참고.
@SuppressWarnings("unchecked")
	public E get(int index) {
		if(index >= count) {
			throw new ArrayIndexOutOfBoundsException("없다");
		}
		
		return (E)data[index];
	}

 

제네릭과 상속

  • 제네릭 클래스에선 상속관계 성립X
  • 타입 매개변수의 상속 관계는 성립O
  • Number > Integer  에서, Integer은 Number를 상속 받음.
public class Ex01 {

	public static void main(String[] args) {
		Test1<Number> t1 = new Test1<>();
		Integer i = 30;
		t1.set(i); // 타입 매개변수의 상속 관계는 성립
			// Number > Integer. Integer는 Number를 상속 받음.
		Number n = t1.get();
		System.out.println(n);
		
		// Integer i2 = t1.get(); // 컴오류
		Integer i2 = (Integer)t1.get();
		System.out.println(i2);
		
		// Number n2 = i; // up-casting. 가능
		Test1<Integer> t2 = new Test1<>();
		t2.set(10);
		System.out.println(t2.get());
		
		// Test1<Number> t3 = t2; // 컴파일오류. 제네릭은 상속 관계 안됨.
	}

}

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

 

제네릭의 와일드카드 <?>

  • 제네릭은 컴파일 단계에서 타입 안정성을 위해 지원하므로, 다형성 갖지 않음
  • 제네릭에 다형성 갖돌고 하기 위해선 와일드 카드를 사용
  • 제네릭 타입 클래스를 파라미터로 사용할 때 구체적인 타입 정해지지 않는 경우에 사용
    • 종류
    • 제네릭타입<?>  (비한정적 와일드카드)
      • ?에는 모든 클래스나 인터페이스 타입 올 수 있음
    • 제네릭타입<? extends 객체타입>
      • 상위 클래스 제한
      • ?에는 명시된 객체타입 또는 하위타입 가능
    • 제네릭타입<? super 객체타입>
      • 하위 클래스 제한
      • ?에는 명시된 객체타입 또는 상위타입 가능

 

  • 비한정적 와일드 카드 <?>
    • 모든 클래스나 인터페이스 타입 가능 (아래의 경우 모두 가능)
      • Object클래스의 기능 사용하여 구현할 수 있는 메소드 작성하는 경우
      • 형식 매개 변수에 종속되지 않는 제네릭 클래스 메소드 사용하는 경우 (List의 size(),clear(),Class<T>의 대부분 메소드가 T에 의존하지 않으므로 Class<?> 로 자주 사용됨)
    • List<Object> != List<?>
      • Object 또는 Object의 하위 유형을 List<Object>에 삽입은 가능하나, List<?>는 null만 삽입 가능
      • List<Object>는 String 객체 추가 가능
      • 컴파일 시 형 안정성 보장X, 런타임에러 발생 가능성 있음
      • List<?>는 컴파일 시 타입정보 알 수 없으므로 String 추가(add) 불가능하나, contains(Object o), remove(Object o) 같이 형인자 사용 않는 메소드는 그대로 사용 가능.
    • 컴파일 시간에 형 안정성 보장하여 형인자에 대한 정보 없으므로 형인자 갖는 부분 제외한 메소드만 사용 가능
public class Ex02 {

	public static void main(String[] args) {
		Test2<String> obj = new Test2<>();
		obj.set("서울");
		disp(obj);
		System.out.println();
		
		Test2<Integer> obj2 = new Test2<>();
		obj2.set(950);
		disp(obj2);
		
		
		
	}
	
	/*
	 제네릭타입<?>
	 	: 제한 없음. 모든 클래스나 인터페이스가 가능
	 	: 제네릭 타입에 의존적이지 않는 메소드등을 호출할 때 사용
	 	: 읽기 전용으로 값을 변경할 수는 없음
	 */
	public static void disp(Test2<?> t) {
		// t.set(20); // 컴오류. 자료형이 결정되지 않았으므로 값 바꿀 수 없음.
		t.print();
	} 

}

class Test2<T> {
	private T t;
	
	public void set(T t) {
		this.t = t;
	}
	
	public T get() {
		return t;
	}
	
	public void print() {
		System.out.println(t);
	}
}