Java 문법 12. 제네릭(Generics)

작성일     업데이트:

카테고리:

태그:

제네릭(Generics)

제네릭이란

  • jdk 1.5부터 제공되는 기능
  • 다양한 타입의 객체들을 다루는 메소드나 클래스의 타입을 미리 명시해서 저장할 객체(클래스 타입)을 제한하는 기능이다

  • 사용 이유

    • 컴파일 단계에서 잘못된 타입을 사용할 수 있는 문제를 제거한다
    • 컬렉션에 저장된 여러 종류의 객체를 꺼내서 사용할 때, 종류에 따라 매번 강제 형변환을 해야하는 상황을 방지한다
      => 강제형변환은 성능에 좋지 못한 영향을 끼치기 때문에 최대한 방지하면 좋음
    • 컬렉션, 람다식(함수적 인터페이스), 스트림, NIO에서 널리 사용한다
    • 제네릭을 모르면 API Document 해석이 어렵기 때문에 학습에 제한이 생길 수 있다
  • 사용 이점
    • 컴파일 시 강한 타입 체크가 가능하다
      => 의도하지 않은 타입의 객체가 저장되는 것을 막는다 => 저장된 객체를 의도하지 않은 타입으로 사용하는 것을 막는다
    • 형변환용의 복잡한 코드 제거 및 성능향상이 가능하다


제네릭 선언

  • 제네릭은 static변수에 사용할 수 없다
    => ex) public class Student<T> { static T name; }
    => static은 상위 클래스가 인스턴스가 되기 전에 메모리에 올라가는데 이때 타입이 결정되어있지 않기 때문에 사용할 수 없음

  • static 메소드에도 제네릭을 사용할 수 없다
    => ex) static T getName(T name) { return name; }
    => 위의 static 변수와 마찬가지 이유로 타입이 정해지지 않았는데 메모리에 올리기 때문

  • 하지만 제네릭 메소드는 static이 가능하다
    => ex) ` static T getOneStudent(T id) { return id; }` => 제너릭 메소드는 호출 시에 매개 타입을 지정하기 때문에 static이 가능하다

  • 이 외에 클래스 , 일반 메소드에도 제네릭 사용 가능


[ 제네릭 클래스 선언 ]

// [접근제한자] class [클래스이름] < 영문자 > { }
 public class Box<T> { }
  • 위에서 영문자 위치에 제네릭을 사용한다
  • 일반적으로 대문자로 표기
  • 클래스타입이 미정일 경우, 객체 생성시 정해지는 클래스 타입을 영문자가 받아서 영문자 사용위치에 적용하는 식으로 동작한다
    • ex) Box<String> box = new Box<String>();
      => public class Box<String> { }
  • 만약 두 개 이상의 타입으로 제네릭 타입 변수를 사용하고 싶다면 콤마로 구분하여 다른이름으로 타입 변수를 지정해주면 된다


[ 제네릭 메소드 선언 ]

 public class Box<T> {

  // [접근제한자] <Type1, Type2, … > [리턴타입] [메소드이름](매개변수, …) { … }
  public <T> T method(T d) { return box; }

}
  • 제네릭 메소드란 메소드의 선언부에 적은 제네릭으로 리턴타입, 파라미터의 타입이 정해지는 메소드이다
  • 타입 변수를 명시적으로 지정할 수도 있고 컴파일러가 매개값의 타입을 보고 추정하도록 할 수도 있다
  • 위의 예시의 경우 <T>는 제네릭타입을 명시해준것이고 Box<T>는 리턴타입을 명시해 준것이다
  • 다만 해당 메소드가 속한 클래스 Box<T><T>와 제너릭 메소드에 붙은 <T>는 같은 T를 사용하더라도 전혀 별개로 취급해야한다
    => 클래스에 표시하는 <T>는 인스턴스 변수와도 같이 인스턴스가 생성될 때 마다 지정되기 때문 => 제네릭 메소드에 붙은 <T>는 지역변수를 선언한 것과 같다고 할 수 있다 -


제네릭 제한하기

class Triangle<T extends Point> { }
  • 제네릭 타입 변수에 extends 키워드를 사용하면 타입의 종류를 제한할 수 있다
    => 일반적인 방법으로 제네릭을 이용했을 경우라면 타입에 제한이 없음
  • extends 뒤에 명시된 타입의 자손들만 타입 변수에 대입할 수 있게 된다
  • 사용 이유
    • 제네릭을 사용하면 타입 파라미터로 모든 클래스를 넣어줄 수 있다
      => 다양한 타입이 들어오면서 데이터의 정체성을 훼손시킬 수 있음 => 숫자타입만 원했는데 문자열 클래스가 들어가도 오류가 나지 않는 식으로 동작할 수 있음
    • 위와같은 이유때문에 제네릭에 제약을 걸어 상한선(extends)과 하한선(super)을 설정해주는 것이다


와일드카드

<?> // 제한 없음
<? extends 상위타입> // 상위 클래스 제한
<? super 하위타입> // 하위 클래스 제한
  • 타입 변수를 매개변수나 리턴 타입으로 사용할 때 구체적인 타입 대신 와일드 카드를 사용 가능하다
  • 와일드 카드 이용 3가지 방법
    • 타입 변수를 대치할 수 있는 구체적인 타입으로 제한을 두지 않기
    • extends 키워드를 이용해 해당 타입과 자손 타입들만 가능하도록 제한하기
    • super 키워드를 이용해 해당 타입과 부모 타입들만 가능하도록 제한하기


제네릭 상속

public class ChildProduct<T, K, S> extends Product<T, K> {  }
  • 제네릭 클래스도 부모 클래스가 될 수 있다
  • 부모 클래스의 제네릭 타입을 상속해서 자식 틀래스에서도 제네릭 타입을 상속 할 수 있다
  • 자식 제네릭 클래스는 부모의 것 외에도 추가적인 타입 변수를 가질 수 있다
    • 다만 부모클래스와 같은 제네릭 타입은 필수적으로 가지고 있어야 한다

댓글남기기