[JAVA] 제네릭

msung99·2022년 5월 30일
0
post-thumbnail

제네릭

  • C++ 템플릿과 유사
    • C++ 템플릿 : 타입을 다른 이름으로 치환하는 것

제네릭의 특징

컴파일 시 타입체크를 한다. (컴파일 단계에서 타입이 결정된다.)

List list = new ArrayList();  
list.add("Hello");
String str = (String) list.get(0); 

  • List 데이터 타입은 내부에서 객체를 Object 타입으로 받는다.
  • list.get()은 list 객체가 안에 지닌 객체를 리턴하는 것인데, 리턴되는 객체는 Object 타입이다.
  • Object 타입은 자신의 안에 정의된 기능만 사용가능하다.
  • 그러나 실제로 내가 쓰려고 하는 것은 String 타입이다. 이를 위해 다운캐스팅을 한다.

=> 따라서 아래처럼 모든 타입을 받지않고, String 타입만 받겠다고 명시하면 좋다.

List<String> list = new ArrayList<String> ();
list.add("hello");
String str = list.get(0);

제네릭 타입

  • 타입을 파라미터로 가지는 클래스와 인터페이스
  • 이름뒤에 "< >" 를 붙임
  • "< >" 사이에는 타입 파라미터를 명시한다. ex. < T > , < K >
  • main 메소드에서 미리 해당 클래스의 데이터 타입을 지정해준다.
// 이렇게 클래스명 옆에 <T> 타입 파라미터를 명시하면 된다.

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


Box<String> box = new Box<String>(); // 타입 파라미터에 들어갈 타입을 명시해준다! (String 타입으로 명시) 
box.set("hello");
String str = box.get();

만일 제네릭 타입을 사용하지 않는다면?

  • 제네릭 타입대신에 Object 타입을 사용해야해서, 빈번한 타입 변환 발생

예제

Box 클래스는 모든 멤버들이 Object 타입이다. 따라서 Box 클래스의 멤버들을 사용시에 데이터 타입을 바꿔주고 사용해야한다.

특징1) 객체 생성시 제네릭 타입을 지정한다

  • 제네릭 타입을 사용하는 경우 애초에 미리 데이터 타입을 지정하고 들어간다.
  • 객체 생성시 제네릭 타입을 지정해준다.
    • 예시처럼 String, Integer 등의 타입을 지정해주면 된다.

특징2) 제네릭 타입은 2개 이상의 타입 파라미터 사용 가능

  • 각 타입 파라미터는 콤마 "," 로 구분

ex) 클래스 선언부 : class Box <K, V, ... > { ... }
객체 생성부 : Box<TV, String> box1 = new Box<TV, String> ();

  • 아래 예시의 경우 T는 TV 타입으로, M 은 String 타입으로 치환된다.


제네릭 메소드

  • 앞서 클래스를 만드는 방법을 배웠고 이제 메소드를 만드는 방법을 알아보자.

선언방법

리턴 타입 앞에 "<>" 기호를 추가하고 타입 파라미터를 기술할 것!
=> 형태 : public <타입파라미터> 리턴타입 메소드명(매개변수, ...) { ... }

ex)
메소드 선언시에,
public Box< T > func1(T t) { ... (메소드 정의부) }

형태가 아니라
public < T > Box < T > func1(T t) { ... (메소드 정의부) }

와 같이 메소드의 리턴타입 ( Box < T > ) 앞에 반드시!! 현재 사용되는 타입 파라미터 ( = 여기서는 < T > )를 명시해줘야한다!

메소드 호출방법

형태1 : 리턴타입 변수 = <변환할 타입> 메소드명(매개값);

  • ex) Box< Integer > box1 = < Integer >boxing(100);

형태2 : 리턴타입 변수 = 메소드명(매개값);

  • ex) Box< Integer > box1 = boxing(100);

예제

public class Util{
  public static <T> box<T> boxing(T t){   // 제네릭 메소드 boxing
    Box<T> box = new Box<T>();
    box.set(t);
    return box;
  }
}

main 메소드
Box<Integer> box1 = Util.<Intger>boxing(100);  // 제네릭 메소드를 호출시 명시적으로 호출
// cf. 이때 Wrapper 클래스 중 하나인 Integer 형으로 타입을 지정.
// 지정된 타입이 Integer객체 타입인데, 매개변수로 그냥 정수 100을 넘김. 이러면 자동으로 int 타입 100이  Integer 클래스 안으로 들어감!

int intValue = box1.get();   // 리턴되는 타입이 Integer 객체인데, 이를 그냥 int 형으로 받을수있다.

Box<String> box2 = Util.boxing("홍길동");  // 제네릭 메소드를 호출시 이처럼 <변환할 타입> 부분을 생략하고 호춣해도 된다.
Stirng strValue = box2.get();


Box<int> box2 = Util.<Integer>boxing(2);   => 에러발생!!!! 
// 제네릭에서 타입 파리미터는 객체 타입만 할당가능. 기본 타입이 불가능하다!
// 이런 경우를 위해 Wrapper 클래스를 지원하는 것!

제네릭에서 타입 파리미터는 객체 타입만 할당가능. 기본 타입이 불가능하다!
=> 이런 경우를 위해 Wrapper 클래스를 지원하는 것!


예제


타입 파라미터 지정의 다양한 방법

  • 타입 파리미터로 구체적인 타입(ex. Integer) 외에도 상속 및 구현 관계를 이용해 타입 명시가능

  • 타입 파라미터에 지정될 타입이 제한된다.

    • 지정된 타입은 반드시 우리가 적어준 부모클래스를 상속받은 타입이여야한다.

형태 : public <T extends 부모클래스타입(or 인터페이스 타입)> 리턴타입 메소드(매개변수) {...(메소드 정의부)}

  • ex) public < T extends Box > int compare(T t1, T t2){ ... }
  • 주의!!! 인터페이스라고 해서 implements 가 들어가는 것이 아니다!!!
    extends 가 들어감!

예시

  • 타입 제한기능 : 해당 클래스를 상속받을 수 있는 타입만 지정가능.

    ex) 상속받을 클래스로 Number 클래스를 지정한경우

    => public < T extends Number > int compare(T t1, T t2){...}

  • 타입 T 는 반드시 Number 클래스를 상속받은 타입 or Number 클래스 타입이여야한다.

    • cf. Number 클래스를 상속받은 타입 : Integer 객체, Double 객체, ...
      • 즉, Integer 객체, Double 객체, ... 등은 모두 Number 클래스의 메소드를 안에 포함하고 있다.

    이런 함수에서 T 타입을 Number 클래스를 상속받은 타입으로 지정안하면 Number 클래스의 메소드인 doubleValue() 메소드를 가지고 있지 않기 떄문에 에러가 발생!

    • 즉, 타입 T 는 즉, Integer 객체, Double 객체, .. 타입 중에 하나여야 한다.

와일드타입

  • 제네릭 메소드에서 상속관계로 타입을 제한하듯이, 와일드타입은 제네릭 메소드말고 일반 메소드에서 타입을 제한할 때 사용

  • 일반 메소드에 활용되므로, 메소드 안에 T, V 같은 변수없고 대신에 물음표 ? 사용

  • 내가 매개변수로 받는 타입이 제네릭 타입일때 사용

일반 메소드( <?> ) {

}

해당 제네릭 타입이 (클래스로 만들어진 제네릭 타입) ( ex. class < T > )
타입 파라미터( T ) 에 어떤 타입을 할당해서 만들어진 타입이든 간에,
클래스를 활용해 만들어진 제네릭 타입이기만 하면,
일반 메소드의 매개변수 에서 매개변수 타입으로 무조건 다 받아들이겠다는 의미!

예를들어, box < T > 라는 제네릭 타입라는 타입은 클래스 box 를 이용해서 만들어진 타입이다. 이렇게 클래스를 활용해 만들어지는 타입은 box< Integer >, box< Character >, box< Float > , ... 등이 있는데 이렇게 모든 타입을 다 받겠다는 의미이다!

형태1 : 제네릭타입<?>

  • 클래스를 활용한 제네릭 타입이기만 하면 일반 메소드의 매개변수 타입으로 지정해준다!

형태2 : 제네릭타입<? extends 부모타입> (
=> 상위 클래스 제한

  • 타입 파라미터를 대치하는 구체적인 타입이 부모타입을 상속받은 타입이기만 하면, 일반 메소드의 매개변수 타입으로 지정해준다!

형태3 : 제네릭타입<? super (자식타입)>
=> 타입 파라미터를 대치하는 구체적인 타입이 자식타입의 부모타입이기만 하면, 일반 메소드의 매개변수 타입으로 지정해준다!

예제

우선 위와 같은 상속구조가 있다.
Worker, Student 클래스는 Person 을 상속받았고, HighStudent 는 Student 를 상속받았다.

또, 위와 같인 Course 라는 클래스를 활용한 제네릭 타입이 있다.

cf. 타입파라미터를 사용해서 배열을 생성할때, Object 타입을 사용해서 배열을 만들고, 만든 배열을 타입파라미터 타입으로 변환해줘야 한다.

stduents = (T[])( (new Object[capacity]); // Object 타입으로 생성한 배열의 타입을 T[] 타입으로 형변환 

그리고 위와 같은 실행코드가 있다.

살펴보니까, 일반 메소드 registerCourse, registerCourseStudent, registerCourseWorker 가 있다.

1.registerCourse 메소드

  • 매개변수에 할당되는 타입이 클래스를 활용한 제네릭 타입인 Course 라는 제네릭 타입에 대한 타입이라면,
    제네릭 타입 Course 가 어떻게 만들었는지 상관하지 않고 매개변수 타입으로 설정해준다!
    • 즉, Course< Person >, Course< Worker >, Course < Student >, Course < HighStudent >, .... 등의 타입들이 모두 다 매개변수 타입이 될 수 있다!

2.registerCourseStudent 메소드

  • Course 라는 제네릭 타입을 가지고 있는 녀석을 매개변수로 받긴 하되,
    그 Course 라는 클래스를 활용한 제네릭 타입을 만들때 활용한 타입파라미터 타입(T) 가 Student 또는 Student 를 상속받은 제네릭 타입이여야지 매개변수 타입으로 설정해줄 수 있다.
    다시말해서 Course< Person >, Course< Worker >, Course < Student >, Course < HighStudent > 타입중에 일부 타입만 받을 수 있도록 제한한다.

  • 즉, Student 타입과, Student 를 상속받은 타입인 HighStudent 타입에 대해서 만들어진 제네릭 타입만 받을 수 있다!

    • 또 다시말해, Course< Student> , Course < HighStudent > 타입이 매개변수 타입이 될 수 있다.
  1. registerCourseWorker 메소드
  • Worker 와 Person 에 대한 제네릭 타입만 받을 수 있다!

 // 1.일반 메소드 registerCourse 의 매개변수 타입은
// Course 라는 제네릭 타입인데, 어떤 타입이든지 다 받을 수 있다!
// 따라서 아래 메소드는 모두 다 실행된다

registerCourse(personCourse); 
registerCourse(workerCourse);  
registerCourse(studentCourse);
registerCourse(highStudentCourse);
// 2.Course 라는 제네릭 타입을 받을 수 있되, 
// Student 타입 또는 Student를 상속받은 타입만 받을 수있도록 제한한다!

// registerCourseStudent(personCourse);
// registerCourseStudent(workerCourse);
registerCourseStudent(studentCourse);
registerCourseStudent(highStudentCourse);

제네릭 타입의 상속과 구현

  • 제네릭 타입을 부모 클래스로 사용할 경우
    => 타입 파라미터는 자식 클래스에도 명시해야함
public class ChildProduct< T, M > extends Product< T, M > {...}
  • 자식은 추가적인 타입 파라미터를 가질 수 있음
public class ChildProduct< T, M, C > extends Product< T, M > {...}
profile
블로그 이전 : https://haon.blog

0개의 댓글