[JAVA]제네릭(Generic)

보람찬하루·2023년 11월 7일
3

제네릭이란 ?

:데이터의 타입을 외부에서 사용자에 의해 지정되는 것



List<Integer> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();

ㄴ 여기서 처럼 꺽쇠 안에 클래스 타입이 명시된 패턴


선언방법

1. 클래스에 제네릭파라미터 선언


class Sample<T> {
   private T anonyTypeData;
 }

  2. 메소드에 제네릭 파라미터 선언


/**
  *
  * @param supplier java8의 함수형 인터페이스중 하나로 구현시점에 리턴값을 결정하며 타입이 정의된다.
  * @param <T> test2메소드 호출시 전달받을 타입 파라미터로 supplier의 반환타입이자 test2의 반환타입으로 정의한다.
  * @return
  */
 public <T> T test2(Supplier<T> supplier){
   System.out.println("supplier 인터페이스의 반환타입에 따라서 test2의 반환타입이 결졍된다.");
   return supplier.get();
 }
```


사용하는 이유(장점)

  1. 파라미터 타입이나 리턴 타입에 대한 정의를 외부로 미룬다

    	>> 타입 안정성 제공 ( 컴파일러가 타입 확인후 검증 가능 >> 오류 방지 )
    	   유연성&코드 재사용성 :  다양한 상황에서 코드 재사용 가능

    //제네릭 파라미터 사용 코드
    // @param <T> 클래스 초기화 시 한 가지의 클래스 타입을 제네릭 파라미터로 받는다
    class Sample<T> {
    
        private T data; // 데이터의 타입은 제네릭 T
        //@param data 파라미터 타입은 클래스 초기화 시 지정한 타입과 동일하다.
        public void setData(T data){
            this.data = data;
        }
         // @return 리턴 타입은 클래스 초기화 시 지정한 타입과 동일하다.
        public T getData(){
            return data;
        }
    }
    

    : 제네릭 타입으로 어떤 클래스를 전달했냐에 따라서 메소드의 파라미터, 혹은 리턴타입이 제네릭 파라미터로 전달받은 클래스 타입으로 유연하게 변함

    setData메소드나 getData메소드에 여러가지 타입을 이용할 수 있지만, 제네릭 파라미터에 의해 타입이 고정되기 때문에 안정성이 확보

    //제네릭 안썼으면
    class AnotherSample {
        private Object data;
        // @param data 모든 타입을 파라미터로 받기위해 파라미터 타입을 최상위 객체인 Object로 정의한다.
        public void setData(Object data){
            this.data = data;
        }
        public Object getData(){
            return data;
        }
    }
    
    vvvvvvvvvvvvvvvvvvvvvvvvvvvv코드 호출 이렇게 정신없고 신경써야하는 코드탄생
    Sample<String> sample = new Sample<>();
    sample.setData("test");
    String s = sample.getData();
    
    AnotherSample integerSample = new AnotherSample();
    integerSample.setData(1);
    int a = (int) integerSample.getData();
    
    AnotherSample stringSample = new AnotherSample();
    stringSample.setData("test");
    String b = (String) integerSample.getData();
  2. 런타임 환경에 아무런 영향이 없는 컴파일 시점의 전처리 기술

          >> 런타임 타입 에러를 컴파일 과정에 검출가능


특징

  1. 클래스 혹은 메소드에 선언 가능 (아래 선언방법 참고)
  2. 동시에 여러 타입 선언 가능
    public <P, R> R test(P p, Function<P, R> function){
        return function.apply(p);
    }
    
    class AnonyMap<K, V> implements Map<K, V>{
        ....
    }
    /*
     * @param p Function 메소드에서 소비될 P타입의 인자이다.
     * @param function Function 제네릭 인자의 첫번째 타입의 파라미터를 소비하여 두번째 타입의 리턴값을 반환한다.
     * @param <P> Function 메소드의 소비 파라미터 타입으로 정의한다.
     * @param <R> Function 메소드의 리턴 타입으로 정의한다. test메소드의 리턴타입으로 정의한다.
     * @return
     */
  3. 와일드 카드를 이용하여 타입에 대한 유연한 처리 가능: 자바 컴파일러는 대입연산 수행시 left-value 제네릭타입과 right-value 제네릭타입이 일치하지 않는 경우 컴파일 에러가 발생하는데 와일드카드를 사용한다면 컴파일러가 유연하게 대처가능
    • 와일드 카드란 ? 제네릭에서 사용되는 문법적 요소 중 하나로 제네릭 타입에 대해 특정 타입을 지정하지 않고 더 유연하게 다양한 타입을 다룰 수 있도록 하는 기능<?>

      @Test
      public void test(){
        List<String> example = new ArrayList<>();
        method1(example); // 제네릭 타입이 일치하지 않기 때문에 컴파일 에러 발생
        method2(example); // 모든 제네릭 타입을 허용하기 때문에 컴파일 에러 없음
      }
      
      public void method1(List<Object> param){ // List의 제네릭타입으로 Object만 허용한다.
        // ...
      }
      
      public void method2(List**<?>** param){ // List의 제네릭타입으로 모든 타입을 허용한다.
        // ...
      }
  4. 타입의 상속 관계 지정 가능 : 제네릭을 사용하는 것이 무조건 좋은 것은 아님, 다양한 타입이 들어오며 데이터의 정체성 훼손 가능 >> 제약으로 상한선과 하한선 설정
    1. 타입 파라미터 : 상한선 - extends

      <T extends 상위타입> : 제네릭 클래스/메소드 생성시 타입의 상한선 지정

    2. 와일드카드<?> : 상한선 - extends , 하한선 super

      : A 자식 클래스까지만 가능 : B 조상 클래스만 가능
profile
를 만들어 가자

2개의 댓글

comment-user-thumbnail
2023년 11월 12일

Swift 에서도 Generic 이 있는데 비슷한 역할을 하는군요!
Java 언어를 잘 모르지만 그래도 잘 읽을 수 있었습니다! 다음에도 좋은 글 부탁드려요~

답글 달기
comment-user-thumbnail
2023년 11월 12일

제네릭 타입을 사용했을때의 경우와 안 했을때의 경우를 코드로 보여줘서 이해가 빨랐던 거 같아요.
제네릭에 대한 자세한 설명과 장단점 정리 감사합니다!! 다시 한번 공부하게 되네요!!

답글 달기