Generic_제네릭

aquarius1997·2020년 3월 13일
0

제네릭 추가 : Java 5 ~

제네릭 타입

  • 제네릭 타입 : 타입을 파라미터로 가지는 클래스(class< T >) 와 인터페이스(interface < T >)를 의미
  • 작성법 : 클래스/인터페이스이름<타입파라미터>
public class Person<T> { }
public interface Animal<T> { }
  • 명명규칙 : 대문자 알파벳 한 글자
  • 사용 : 타입 파라미터에 구체적인 타입을 지정
  • 타입 파라미터를 사용하는 이유 : 클래스 설계 시 구체적인 타입을 명시할 필요가 없어 타입 변환을 최소화 시킨다.

이에 대하여 타입 파라미터를 사용하지 않을 경우와 비교하며 알아보자

1) 타입파라미터를 사용하지 않을 경우
모든 자바 클래스의 최상위 조상 클래스인 Object 타입으로 필드를 선언해, 다양한 타입의 객체를 set하고 get 하는 클래스가 있다.

public class Person {
    private Object object;
    public void set(Object object) {
    	this.object = object;
    }
    public Object get() {
   	return object;
    }
}

이를 인스턴스로 생성하여 다음과 같이 String 타입으로 저장한다고 하자.
자바는 자식 타입이 부모 타입으로 자동 타입 변환이 가능하므로 set메소드를 실행하면 Object 필드에 "My name is"가 저장될 것이다.
이후 get() 메소드로 저장된 객체를 가져올 때, 필드에 저장된 원래 타입으로 객체를 얻으려면 강제 타입 변환을 해야한다.

Person person = new Person();
person.set("My name is");
String str = (String)person.get();	//casting

이러한 타입 변환이 빈번해지면 전체 프로그램 성능에 좋지 않다.
따라서 이를 개선할 수 있는 방법이 제네릭이다.

2) 타입파라미터를 사용(제네릭 사용)

위의 Person 클래스를 제네릭을 사용한다면 다음과 같이 수정될 것이다.

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

그리고 이후 String이라는 구체적인 타입으로 객체를 생성하면

Person<String> person = new Person<String>();

타입 파라미터 T가 String 타입으로 변경돼 자동으로 클래스 내부가 재구성된다.

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

따라서 set() 이후 get()으로 저장된 필드를 읽어올 때 타입 변환이 필요없다.

Person<String> person = new Person<String>();
person.set("My name is");
String str = person.get();	//casting 필요 없음.

+ 멀티 타입 파라미터

제네릭 타입은 두 개 이상의 멀티 타입 파라미터를 사용할 수 있다.

  • 작성법 : 클래스/인터페이스이름<타입파라미터1, 타입파라미터2, ..>
public class Person<T, M> { }
public interface Animal<T, M, K> { }
  • 제네릭 타입의 변수를 선언하는 것과 객체 생성을 동시에 할 때, 자바7 부터 제네릭 타입 파라미터의 중복 기술을 줄이기 위해 다이아몬드 연산자(<>)를 제공한다
Person<String, Int> person = new Person<String, Int>();	//~Java 6
Person<String, Int> person = new Person<>();	//Java 7~

제네릭 메소드

  • 작성법 : 매개 타입 또는 리턴 타입에 제네릭을 선언한다. 이때, 메소드의 리턴타입 앞에 제네릭을 동일하게 선언해야한다.
public <T> Person<T> naming(T t) { }
  • 호출 방법 : 구체적 타입을 명시하는 방법과 컴파일러가 매개값 타입을 보고 구체적인 타입을 추정하도록 하는 방법이 있다.
//첫번째 방법 : 제네릭메소드의리턴타입 변수명 = <구체적인타입> 제네릭메소드명(파라미터)
Person<String> resultMethod = <String>naming("My Name is..");
//두번째 방법 : 제네릭메소드의리턴타입 변수명 = 제네릭메소드명(파라미터)
Person<String> resultMethod = naming("My Name is...");
  • 제네릭 메소드 또한 두개 이상의 타입 파라미터를 가질 수 있다.

Bounded type parameter (제한된 타입 파라미터)

  • 작성법 : public <T extends 상위타입> 리턴타입 메소드명(파라미터, ..) { }
public <T extends Number> int ageCompare(T t1, T t2) {
}

위와 같이 선언할 경우 메소드의 파라미터로 상위타입 또는 상위타입의 하위 클래스 타입의 인스턴스만 가질 수 있다. 상위 타입은 클래스와 인터페이스 모두 가능하다.

  • 주의점 : 타입 파라미터에 지정되는 구체적인 타입은 선언한 상위타입이거나 상위타입의 하위/구현 클래스이어야만 한다. 또한 중괄호{} 안의 타입 파라미터 변수로 사용 가능한 것은 상위 타입만의 필드와 메소드로 제한된다.

즉, Number 타입으로 상위 타입을 선언했을 경우, 메소드 사용 시 t1/t2에 Number, Byte, Short, Integer, Long, Double의 인스턴스를 사용할 수 있다. 그리고 메소드 중괄호 블럭{} 안에서 t1, t2가 사용할 수 있는 메소드로는 Number의 메소드인 doubleValue()가 있을 것이다.

public <T extends Number> int ageCompare(T t1, T t2) {
    double v1 = t1.doubleValue();	//Number의 doubleValue() 메소드
    double v2 = t2.doubleValue();	//Number의 doubleValue() 메소드
    if(v1 > v2) {
    	return 1;
    } else {
    	return -1;
    }
}

와일드카드 타입

1. Unbounded Wildcards(제한 없음)

  • 작성 법 :
제네릭타입 <?>
  • 타입 파라미터에 오는 구체적인 타입은 모든 클래스나 인터페이스 타입이 가능하다.

2. Upper Bounded Wildcards(상위 클래스 제한)

  • 작성 법 :
제네릭타입 <? extends 상위타입>
  • 타입 파라미터에 오는 구체적 타입은 상위타입과 상위타입의 하위타입만 올 수 있다.

3. Lower Bounded Wildcards(하위 클래스 제한)

  • 작성 법 :
제네릭타입 <? super 하위타입>
  • 타입 파라미터에 오는 구체적 타입은 하위타입과 하위타입의 상위타입만 올 수 있다.

제네릭 타입의 상속, 구현

1) 상속

제네릭 타입도 부모 클래스가 될 수 있고, 자식 제네릭 타입은 추가적으로 타입 파라미터를 가질 수 있다.

public class ChildAnimal<T, M, C> extends Animal<T, M> { }

2) 구현

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

public interface Service<T> { }
public class ServiceImpl<T> implements Service<T> { }

장점(특징) 정리

  1. 컴파일 시에 타입을 강하게 체크해서 실행 시 타입 에러가 나는 것을 방지한다
  2. Casting(타입 변환)을 제거하여 프로그램 성능을 향상시킨다
profile
안녕하세요😀😀

0개의 댓글