13-Generics



generics

제네릭은 클래스와 인터페이스, 그리고 메소드를 정의할 때 타입을 파라미터로 사용할 수 있도록 한다. 그리고 타입 파라미터는 코드 작성시 구체적인 타입으로 대체되어 다양한 코드를 생성하도록 해준다.

  • 미리 사용 데이터 타입을 알려주고, 컴파일러가 타입을 체크해서 잘못된 형변환을 실행시 줄여준다.

     Arraylist<A>
     HashMap<B, D>
     HashSet<C>
       // < > 꺽쇠 안의 타입만 쓰겠다는 선언
    


제네릭 타입(class, interface)

  • object객체는 최상위 객체로, 모든 자바객체는 object 타입으로 자동 타입 변환이 가능하다.
  • 타입 파라미터는 일반적으로 대문자 알파벳 한 글자로 표현한다.
public calss Box { 
  private Object object;
  public void set(Object object) { this.object = object; }
  public Object get() { return object; }
}

Box box = new Box();
box.set("hello");
String str = (String)box.get(); // Object 타입을 String 타입으로 강제 변환해서 값을 얻음

Object 타입을 사용하면 모든 종류의 자바객체 저장이 가능하지만, 젖아할 때 타입 변환이 발생, 읽을 때도 타입 변환이 발생한다. 다음은 제네릭을 이용해 수정한 결과이다.

//Object 타입을 모두 T로 대체
public class Box<T> {
  privat T t;
  public T get() { return t; }
  public void set(T t) { this.t = t; }
}

//T는 Box 클래스로 객체를 생성할 때 구체적인 타입으로 변경된다.
Box<String> box = new Box<String>();

*/ 결과: 
 public class Box<String> {
  privat String t;
  public String get() { return t; }
  public void set(String t) { this.t = t; }
 }
/*


  • generic에는 참조형 변수만 올 수 있다. 즉 기본형 변수는 올 수 없다.

    • 기본형 변수는 wrapper class를 이용: ex)int - Integer, double - Double

    • int나 double 타입의 경우 Integer와 Double이 온다.

      EmployeeInfo e = new EmployeeInfo(1);
      Integer i = new Integer(10);
      Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
      ---- // 그러나 이는 다음과 같이 짧아질 수 있다. 
        person p1 = new person(e, i);
      


example

package generic;

class Apple{
	String origin = "대구";
}
class Paper{
    String size = "A4";
}
// < > 는 어떤 타입이든 저장 가능, 호출시 지정 가능. 
class Box<T>{
	T o;
	Box(T o){
		this.o = o;
	}
	public T getO() {
		return o;
	}
	public void setO(T o) {
		this.o = o;
	}
	
}


public class GenericTest {

	public static void main(String[] args) {
		Apple a = new Apple();
		Paper p = new Paper();
		Box<Apple> appleBox = new Box<Apple>(a);
		Box<Paper> paperBox = new Box<Paper>(p);
		System.out.println(appleBox.getO().origin);
		System.out.println(paperBox.getO().size);
    // 대구, 사과 출력

	}

}


  • generic은 클래스 뿐 아니라 메소드에서도 쓸 수 있다.

    public <U> void printInfo(U info){
      System.out.println(info);
    }
    

    example

package generic1;

class Apple{
	String origin = "대구";
}
class Paper{
    String size = "A4";
}
class Box<T, K>{  
	T t1;
	K k1;
	String name = "상자";
	Box(T t1, K k1){ // 어떤 타입이든 저장 가능! 
		this.t1 = t1;
		this.k1 = k1;
	}
	public T gett1() {
		return t1;
	}
	public K getk1() {
		return k1;
	}
	public void setT1(T t1) {
		this.t1 = t1;
	}
	public void setK1(K k1) {
		this.k1 = k1;
	}
	
}

class BoxManager{
	// 제네릭 메소드
	public <P1, P2> Box<P1, P2> test(P1 a, P2 p) {
	//Box<Apple, Paper> box2 = new Box<Apple, Paper>(a, p); // 타입파라미터가 없으면 엉뚱한 데이터가 와도 체킹이 안 됨. 
    Box<P1, P2> box2 = new Box<P1, P2>(a, p);
		return box2;
	}
}
public class GenericTest {

	public static void main(String[] args) {
		Apple a = new Apple();
		Paper p = new Paper();
		Box<Apple, Paper> box = new Box<Apple, Paper>(a, p);
	
		System.out.println(box.gett1().origin);
		System.out.println(box.getk1().size);

		BoxManager manager = new BoxManager();
		Box<Apple, Paper> box2 = manager.test(a, p);

	}

}



제한된 타입 파라미터

  • generic을 사용하며 object를 활용하게 되면, 모든 객체가 오기 때문에 제어의 필요성이 있다.

바로 이런 경우, extends를 활용하면 < >에 올 수 있는 변수로 사용 가능한 것은 상위 타입의 멤버(필드, 메소드)와 상위 타입의 자식들로 제한된다. 객체 데이터를 제한할 수 있다.


와일드카드 타입 ?

  • 제네릭타입<?> : 제한 없음
    • 타입 파라미터를 대치하는 구체적인 타입으로 모든 클래스나 인터페이스 타입이 올 수 있다.
  • 제네릭타입<? extends 상위타입>: 상위 클래스 제한
    • 타입 파라미터를 대치하는 구체적인 타입으로 상위 타입이나 하위 타입만 올 수 있다.
  • 제네릭타입<? extends 하위타입>: 하위 클래스 제한
    • 타입 파라미터를 대치하는 구체적인 타입으로 하위 타입이나 상위 타입만 올 수 있다.
package generic2;
class Fruit {
    String name = "과일";
    
}
class Apple extends Fruit{
	String origin;
	Apple(String name, String origin){
		this.name = name;
		this.origin = origin;
	}
}
class Orange extends Fruit{
	String imported;
	Orange(String name, String imported){
		this.name = name;
		this.imported = imported;
	}
}
class Paper {
	String size = "A4";
}
class Box<T extends Fruit> {
	//멤버변수 생성자 메소드 순서상관 없음! 그러나, 박스 안에 있어야 함.
	T o;
	Box(T o){
		this.o = o;
	}
	public T getO() {
		return o;
	}
	public void setO(T o) {
		this.o = o;
	}
}
class BoxManager{
	public void test(Box<? extends Fruit> b){ //전달 
		System.out.println(b.getO().name);
	}
}
public class GenericTest {

	public static void main(String[] args) {
	
      Fruit f= new Fruit();
      Apple a = new Apple("사과", "대구");
      Orange o = new Orange("오렌지", "미국");
      Paper p = new Paper();
      Box<Apple> box1 = new Box<Apple>(a);
      Box<Orange> box2 = new Box<Orange>(o);
      Box<Fruit> box3 = new Box<Fruit>(f);
   // extends 제한에 걸림  Box<Paper> box4 = new Box<Paper>(p);
      
     BoxManager m = new BoxManager();
     m.test(box1);
     m.test(box2);
     m.test(box3);
    		 

	}

}