ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java]STEP2 - 2) 제네릭 메소드(Generic Method), 와일드카드, 제네릭 타입의 상속과 구현
    개발 공부/Java 2020. 11. 24. 19:43

     

    본 게시글은 도서 "이것이 자바다" 동영상 강의를 듣고 기록하는 TIL(Today I Learned) 입니다.
    모든 저작권은 출판사 한빛미디어와 신용권님께 있음을 알립니다.👍

     

     

     

    이번 게시물은 저번 게시물에 이어서 제네릭에 대해서 더 알아보겠습니다.

    제네릭 메소드란 무엇이고, 와일드 카드의 개념, 제네릭 타입의 상속과 구현에 대해 알아보겠습니다.

     

     

     

    ◈ 제네릭 메소드 (Generic Method)


    제네릭 메소드(Generic Method)매개 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드를 말한다.

     

    다음과 같이 선언 할 수 있다.

    // 제네릭 메소드 선언
    // 매개 변수 타입: T
    // 리턴 타입: Box<T>
    public <T> Box<T> boxing(T t) {
        Box<T> box = new Box<T>();
        box.set(t);
        return box;
    }

    먼저 리턴 타입(int, boolean, String, Box<T> 등등)앞에 <T>와 같이 타입 파라미터를 기술한다.

    그 다음 매개 변수에 제네릭 타입 파라미터를 기입하면 된다.

     

    호출은 다음과 같이 할 수 있다.

    public class BoxingMethodExample {
    	public static void main(String[] args) {
    		Box<Integer> box1 = <Integer>boxing(100);
    		int intValue = box1.get();
    		
    		Box<String> box2 = boxing("홍길동");
    		String strValue = box2.get();
    	}
    }

    메소드를 호출 할 때, <Integer>boxing(100); 처럼 타입 파라미터를 구체적으로 명시하여도 되고,

    단순히 boxing(100);으로 작성하여도 타입 파라미터를 Integer로 추정하여 대입하게 된다.

    둘 중 어느 방법을 사용하더라도 상관 없다.

     

     

    이러한 제네릭 메소드에 들어가는 타입 파라미터를 구체적으로 제한할 경우도 있을 수 있다.

     

    예를 들면 <T>에 들어가는 타입이 여러 가지가 들어갔으면 좋겠지만, Byte Short Integer Long Double과 같은 숫자 타입만 넣고 싶은 경우이다. 그럴 경우에는 extends 를 통해 타입 파라미터를 제한할 수 있다.

    // extends를 이용해 타입 제한
    public static <T extends Number> int compare(T t1, T t2) {
    		double v1 = t1.doubleValue(); 
    		double v2 = t2.doubleValue();
    
    		return Double.compare(v1, v2);
    }

    위 제네릭 메소드에서 int 앞에 <T extends Number>라는 것이 보일 것이다.

    즉 Number 클래스 또는 Number 클래스의 하위 타입 클래스만 사용할 수 있음을 의미한다.

    그러므로 이 compare 제네릭 메소드에 String이나 기타 다른 타입이 삽입 되는 것을 제한 할 수 있게 된다.

     

     

     단! 주의할 점이 있는데 두 가지가 있다!!! 

     

    1) 상위 타입은 클래스뿐만 아니라 인터페이스도 가능하다. 하지만 그렇다고 extends를 implements라고 쓰진 않는다.

    예를 들면 AAA라는 인터페이스의 하위 구현 객체들만 compare 메소드에서 사용하고 싶다고 한다면

          보통 <T implements AAA>라고 생각하기 쉬우나, <T extends AAA> 라고 써야한다는 것이다.

          클래스이든 인터페이스이든 상관 없이 제네릭 메소드의 타입 제한에서는 extends만을 사용한다.

     

    2) 제네릭 메소드의 중괄호{} 안에서 타입 파라미터 변수로 사용 가능한 것은 상위 타입의 멤버(필드, 메소드)로 제한한다.

    즉 <T extends Number> 라고 제네릭 메소드의 타입을 제한했다면, 메소드의 실행 부분에서는 Number 타입의

          필드와 메소드만 사용할 수 있다는 뜻이다. Integer에는 있고 Number에는 없는 필드나 메소드는 사용할 수 없다.

     


     

    코드에서 ?를 일반적으로 와일드카드(wildcard)라고 부른다.

     

    제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 구체적인 타입 대신에 와일드카드를 이용하여

    다음과 같이 세 가지 형태로 사용할 수 있다.

     

    1) <?> : 모든 클래스나 인터페이스 타입이 올 수 있다.

    public static void registerCourse(Course<?> course) {
    	System.out.println(course.getName() + " 수강생: " + Arrays.toString(course.getStudents()));
    }

    <?>가 사용되었으므로 Course에 모든 클래스나 인터페이스 타입이 올 수 있다.

     

     

    2) <? extends 상위타입> : 상위 타입이나 하위 타입만 올 수 있다. (상위 클래스 제한)

    public static void registerCourseStudent(Course<? extends Student> course) {
    	System.out.println(course.getName() + " 수강생: " + Arrays.toString(course.getStudents()) );
    }

    <? extends Student>가 사용되었으므로 Course에는 Student 타입이나 혹은 Student의 하위 타입만 가능하다.

    즉, Student의 상위 타입은 불가능하다는 것이기 때문에 "상위 클래스 제한"하는 것이다.

     

     

    3) <? super 하위타입> : 하위 타입이나 상위 타입이 올 수 있다. (하위 클래스 제한)

    public static void registerCourseWorker(Course<? super Worker> course) {
    	System.out.println(course.getName() + " 수강생: " + Arrays.toString(course.getStudents()));
    }

    <? super Worker>가 사용되었으므로 Course에는 Worker 타입이나 혹은 Worker의 상위 타입만 가능하다.

    즉, Worker의 하위 타입이나 Worker와 동등한 타입은 올 수 없다는 것이므로, "하위 클래스 제한"하는 것이다.

     


     

    ◈ 제네릭 타입의 상속과 구현


    제네릭 타입도 다른 타입과 마찬가지로 부모 클래스가 될 수 있다. 예를 들면 다음과 같이 선언할 수 있다.

    // 제네릭 타입의 상속
    public class ChildProduct<T, M, C> extends Product<T, M> {...}

    ChildeProduct는 Product를 상속한 자식 클래스이다. 자식 클래스는 추가적으로 타입 파라미터를 가질 수 있다.

    위 코드에서는 ChildProduct가 기존 T, M 타입 이외에 C를 가지고 있는 것을 볼 수 있다.

     

    조금 더 상세히 선언을 해보면 아래와 같다.

    // Product 부모 클래스
    public class Product<T, M> {
    	private T kind;
    	private M model;
    	
    	public T getKind() { return this.kind; }
    	public M getModel() { return this.model; }
    	
    	public void setKind(T kind) { this.kind = kind; }
    	public void setModel(M model) { this.model = model; }
    }
    // ChildProduct 자식 클래스
    public class ChildProduct<T, M, C> extends Product<T, M> {
    	private C company;
    	public C getCompany() { return this.company; }
    	public void setCompany(C company) { this.company = company; }
    }

    ChildProduct 클래스에서는 부모 클래스인 Product 클래스의 kind, model 뿐만 아니라 company 까지 사용할 수 있게 된다.

     

     

    제네릭 인터페이스를 구현한 클래스도 제네릭 타입이 된다.

    // 제네릭 인터페이스
    public interface Storage<T> {
    	public void add(T item, int index);
    	public T get(int index);
    }

    현재 Storage<T> 라고 하는 제네릭 인터페이스에는 add와 get 메소드가 선언되어있다.

     

    // 제네릭 구현 클래스
    public class StorageImpl<T> implements Storage<T> {
    	private T[] array;
    	
    	public StorageImpl(int capacity) {
    		this.array = (T[]) (new Object[capacity]);
    	}
    	
    	@Override
    	public void add(T item, int index) {
    		array[index] = item;
    	}
    
    	@Override
    	public T get(int index) {
    		return array[index];
    	}
    }

    StorageImpl<T>는 Storage<T>를 구현한 제네릭 구현 클래스이다. add와 get 메소드를 오버라이딩 하였다.

     

     

     

    이번 게시물은 여기서 마치겠습니다.

    긴 글 읽어주셔서 감사합니다~~!!

    댓글

Designed by Tistory.