제네릭(Generic) - 제네릭 메서드

Jackson·2024년 11월 6일
0

제네릭 도입과 실패

앞서 배운 제네릭을 도입해서 코드 재사용성은 늘리고, 타입 안정성 문제를 해결해보자.

  • 제네릭 타입을 선언하면 자바 컴파일러 입장에서 T에 어떤 값이 들어올지 예측할 수 없다.
  • T에는 타입 인자로 Integer, Dog, Object 등의 타입이 들어올 수 있기 때문에 예측이 불가능하다.
  • Animal 타입의 자식이 들어오기를 기대했지만, 이 코드에는 Animal에 대한 정보가 없다.
  • 자바 컴파일러는 어떤 타입이 들어올지 알 수 없기 때문에, T를 어떤 타입이든 받을 수 있는 모든 객체의 최종 부모인 Object 타입으로 가정한다.
  • 따라서 Object가 제공하는 메서드만 호출할 수 있던 것이다.
  • Animal 타입이 제공하는 기능들이 필요한데, 이대로는 이 기능을 모두 사용할 수 없게된다.
  • 추가적으로, AnimalHospital(동물병원)에 Integer, Object 같은 동물과 전혀 관계 없는 타입을 인자로 전달할 수 있다는 문제점이 발생한다.

최소한 Animal 이나 그 자식을 타입 인자로 제한하려면 어떻게 해야할까?

타입 매개변수 제한

  • <T extends Animal>
    • TAnimal과 그 자식(Dog, Cat)만 받을 수 있도록 제한
    • T의 상한이 Animal이 된다.
    • 타입 인자로 들어올 수 있는 값은 아래 3가지로만 제한한다.
      • Animal
      • Dog
      • Cat

이제 자바 컴파일러는 T에 입력될 수 있는 값의 범위를 예측가능 !!
= Animal이 제공하는 메서드 사용 가능 !

문제 해결
1. 동물과 전혀 관계없는 타입 인자를 컴파일 시점에 막는다.
2. 제네릭 크래스 안에서 Animal의 기능을 사용할 수 있다.
3. 타입 안정성 문제 해결
4. 제네릭에서 타입 변수를 사용했을 때 어떤 타입이든 허용했던 문제
5. Object의 기능만 사용할 수 있었던 문제

🐰정리🐰

제네릭에 타입 매개변수 상한을 사용해서
1. 타입 안정성을 지키면서
2. 상위 타입의 원하는 기능까지 사용
3. 코드 재사용과 타입 안정성이라는 두 마리 토끼를 동시에 잡았다 !

제네릭 메서드

특정 메서드에 제네릭을 적용하는 방법

제네릭 타입과 제네릭 메서드비교

  • 제네릭 타입

    • 정의: GenericClass<T>
    • 타입 인자 전달: 객체를 생성하는 시점
      • ex) new GenericClass<String>
  • 제네릭 메서드

    • 정의: <T> T GenericMethod(T t)
    • 타입 인자 전달: 메서드를 호출하는 시점
      • ex) GenericMethod.<Integer>genericMethod(i)
    • 제네릭 메서드는 클래스 전체가 아니라 특정 메서드 단위로 제네릭을 도입할 때 사용한다.
    • 제네릭 메서드를 정의할 때는 메서드의 반환 타입 왼쪽에 다이아몬드를 사용해서 <T>와 같이 타입 매개변수를 적어준다.
    • 제네릭 메서드는 메서드를 실제 호출하는 시점에 다이아몬드를 사용해서 <Integer>와 같이 타입을 정하고 호출한다.

제네릭 메서드의 핵심은
메서드를 호출하는 시점에 타입 인자를 전달해서 타입을 지정하는 것.
= 타입을 지정하면서 메서드를 호출한다.

인스턴스 메서드, static 메서드

제네릭 메서드는 인스턴스 메서드와 static 메서드 모두 적용할 수 있다.

class Box<T> { //제네릭 타입 

  static <V> V staticMethod2(V t) {} //static 메서드에 제네릭 메서드 도입

  <Z> Z instanceMethod2(Z z) {} //인스턴스 메서드에 제네릭 메서드 도입 가능 
}
class Box<T> { 
  
  T instanceMethod(T t) {} //가능
  
  static T staticMethod1(T t) {} //제네릭 타입의 T 사용 불가능 
}

제네릭 타입은 static 메서드에 타입 매개변수를 사용할 수 없다.
제네릭 타입은 객체를 생성하는 시점에 타입이 정해지는데, static 메서드는 인스턴스 단위가 아니라 클래스 단위로 작동하기 때문에 제네릭 타입과는 무관하다.
따라서 static 메서드에 제네릭을 도입하려면 제네릭 메서드를 사용해야 한다.

헷갈리는 부분

T instanceMethod1(T t) {
  return t;
}

<T> T instanceMethod2(T t) {
  return t;
}

이 두 메서드의 차이점이 뭘까?

  1. T instanceMethod1(T t) {...} 메서드는 "클래스"의 제네릭 타입과 파라미터 T를 사용하고 있다.
    즉, 이 메서드가 포함된 클래스가 T라는 제네릭 타입 파라미터를 정의하고 있어야만 사용할 수 있다.
class MyClass<T> {
    T instanceMethod1(T t) {
        return t;
    }
}
MyClass<String> myClass = new MyClass<>();
String result = myClass.instanceMethod1("Hello"); // T는 String으로 지정됨

이 경우, T는 클래스가 인스턴스화될 때 결정되므로, 메서드 instanceMethod1은 클래스의 타입 파라미터에 종속적이다.

  1. <T> T instanceMethod2(T t) {...} 메서드는 자체적으로 메서드 내부에서 제네릭 타입 T를 선언하고 있는 것이다.
    클래스의 타입과는 독립적으로 동작한다.
    즉, 클래스가 제네릭 타입을 사용하지 않거나 다른 제네릭 타입을 사용하더라도 이 메서드는 자체적으로 제네릭 타입을 사용할 수 있다.
class MyClass {
    <T> T instanceMethod2(T t) {
        return t;
    }
}
MyClass myClass = new MyClass();
String result = myClass.instanceMethod2("Hello"); // T는 String으로 지정됨
Integer number = myClass.instanceMethod2(123); // T는 Integer로 지정됨

이 메서드는 매 호출마다 다른 타입으로 사용될 수 있고, 클래스의 타입 파라미터의 영향을 받지 않는다.

정리

instanceMethod1(T t) {...}: 클래스의 제네릭 타입에 의존하여, 클래스가 어떤 타입으로 선언되었느냐에 따라 T가 결정된다.

<T t>instanceMethod2(T t): 클래스의 제네릭 타입과 무관하게 메서드 자체가 제네릭을 선언하여, 호출할 때마다 타입을 개별적으로 지정할 수 있다.

profile
훌륭한 화가는 자기 그림이 마음에 들 때까지 붓을 놓지 않는다.

0개의 댓글

관련 채용 정보