Java - Generic

murphytklee·2023년 4월 24일
0
post-thumbnail

1. Generic

1.1 Generic 알아보기

제네릭의 효용

  • 첫 번째로 제네릭의 효용은 타입 언어에서 “중복되거나 필요없는 코드를 줄여주는 것”
  • 두 번째 제네릭의 효용은 그러면서도 타입 안정성을 해치지 않는 것 이다.
  • Step 1. 타입 언어에서의 중복되거나 필요없는 코드?
    • 하지만 우리는 타입을 지정해줘야 하는 언어를 사용한다.
    • 그래서 똑같은 로직을 수행하는 함수를 타입을 지정해야 한다는 이유로 세 차례나 구현해야한다.
     public class Generic {
         public String plusReturnFunction(int a, int b) { ... }

         public String plusReturnFunction(int a, long b) { ... }

         public String plusReturnFunction(int a, String b) { ... }
     }

  • Step 2. 타입 안정성을 해치지 않는 것?
        public class Generic {
            public Object plusReturnFunction(Object a,Object b) { ... }
        }
  • 자바의 “거의 모든 것”은 객체이고, 객체라는 것은 Object 클래스를 상속한다.
  • Object를 상속받기 때문에 위의 코드와 같이 작성을 한다면, 타입과 상관 없이 메서드 안에 두 파라미터를 전달하는 것은 가능 할 것 이다.
  • 하지만 이런 경우 타입 안정성이 침해받게 된다.
  • 직접 메서드를 구현했기 때문에 { … } 블럭 안에 모든 경우의 수를 대비해야 한다.
  • 또한 우리가 구현한 메서드 내부는 아직도 타입에 지배받고 있다.
  • 결론적으로는 다음과 같은 문제들이 발생한다.
    • 예를 들어 a 객체와 b 객체에 단항 연산자를 사용 할 수 있을까?
    • 또는 두 타입이 다르다면 연산자를 사용하기 위해 같은 타입으로 맞춰줘야 하는데, 어떠한 타입으로 맞출 수 있을까? int? long?
    • 마지막으로 순서는 어떻게 할까? int long을 형변환 해서 처리하는 로직의 코드를 작성했는데, long과 int로 들어오면 어떻게 할까?

1.2 제네릭 용어

  1. 제네릭은 클래스 또는 메서드에 사용 가능하다.
  2. <>안에 들어가야할 타입을 명시 → 보통 T를 컨벤션으로써 사용 → 자주사용되는건 T, U, V, E, …
  3. T가 String이면 return 도 String
  4. Generic의 클래스처럼, 제네릭을 사용한 클래스를 제네릭 클래스라고 한다.
  5. 제네릭에서 <>사이에 들어가는 변수명 T는 타입 변수라고 한다
  6. Generic 클래스를 원시 타입 이라고 한다.
// 1.
public class Generic<T> {
		// 2.
    private T t;
    // 3.
    public T get() {
        return this.t;
    }

    public void set(T t) {
        this.t = t;
    }

    public static void main(String[] args) {
				// 4.
        Generic<String> stringGeneric = new Generic<>();
				// 5. 
        stringGeneric.set("Hello World");
				
        String tValueTurnOutWithString = stringGeneric.get();

        System.out.println(tValueTurnOutWithString);
    }
}
  • 제네릭의 제한
  1. 객체의 static 멤버에 사용 할 수 없다.
        static T get() { ... } // 에러
        
        static void set(T t) { ... } // 에러
  • 타입 변수는 인스턴스 변수로 간주되고, Static은 인스턴스화를 시키지 않아도 사용할 수 있는, 즉 모든 객체에 동일하게 동작해야하는 static 필드 특성상 사용할 수 없다.

2. 제네릭 배열을 생성 할 수 없다.



1.3 제네릭의 문법

  1. 다수의 타입변수를 사용 할 수 있다.

    public class Generic<T, U, E> {
        public E multiTypeMethod(T t, U u) { ... }
    }
    
    Generic<Long, Integer, String> instance = new Generic();
    instance.multiTypeMethod(longVal, intVal);
  2. 다형성 즉 상속과 타입의 관계는 그대로 적용된다.
    a. 대표적으로 부모 클래스로 제네릭 타입변수를 지정하고, 그 안에 자식클래스를 넘기는 것은 잘 동작한다.

  3. 와일드 카드를 통해 제네릭의 제한을 구체적으로 정할 수 있다.

    public class ParkingLot<T extends Car> { ... }
    
    ParkingLot<BMW> bmwParkingLot = new ParkingLot();
    ParkingLot<Iphone> iphoneParkingLot = new ParkingLog(); // error!
    1. : T와 그 자손들만 사용 가능
    2. : T와 그 조상들만 가능
    3. : 제한 없음
  4. 메서드를 스코프로 제네릭을 별도로 선언 할 수 있다.
    메서드 자체만으로도 제네릭으로 만들 수 있다.

public class Generic<T, U, E> {
		// Generic<T,U,E> 의 T와 아래의 T는 이름만 같을뿐 다른 변수
		// class 를 인스턴스화 할때 T는 예를 들어 String이면
		// 메서드를 호출할 때 리턴타입 T는 다르게 할 수 있기 때문에 헷갈리지 않기
    static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
}

1.4 과제에서 제네릭 해석

// StatusMessageDto<T>
@Data
@AllArgsConstructor(staticName = "add")
public class StatusMessageDto<T> {

    private int statusCode;
    private String message;
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private T data;

    public static <T> StatusMessageDto<T> setSuccess(int statusCode, String message, T data){
        return StatusMessageDto.add(statusCode, message, data);
    }

    public static <T> StatusMessageDto<T> setFail(int statusCode, String message){
        return StatusMessageDto.add(statusCode, message, null);
    }

}

// BoardService
// 게시글 삭제
    public ResponseEntity<StatusMessageDto> deleteBoard(BoardRequestDto.Delete deleteDto, HttpServletRequest request) {
        User user = checktoken(request);
        Board board = boardRepository.findByBoardIdAndUsername(deleteDto.getBoardId(), user.getUsername()).orElseThrow(
                () -> new NoSuchElementException("본인의 글만 삭제할 수 있습니다.")
        );
        boardRepository.delete(board);
        StatusMessageDto statusMessageDto = StatusMessageDto.setSuccess(StatusEnum.OK.getStatusCode(), "게시글 삭제 완료", *null*);
        return new ResponseEntity(statusMessageDto, HttpStatus.OK);
    }
  • (staticName = "add") 쓰지 않는 경우 ? -> new StatusMessageDto<>
    package com.sparta.spring_lv1_assignment.dto;
    
    import com.fasterxml.jackson.annotation.JsonInclude;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    
    @Data
    @AllArgsConstructor
    public class StatusMessageDto<T> {
    
        private int statusCode;
        private String message;
        @JsonInclude(JsonInclude.Include.NON_NULL)
        private T data;
    
        public static <T> StatusMessageDto<T> setSuccess(int statusCode, String message, T data){
            return new StatusMessageDto<>(statusCode, message, data);
        }
    
        public static <T> StatusMessageDto<T> setFail(int statusCode, String message){
            return new StatusMessageDto<>(statusCode, message, null);
        }
    
    }

0개의 댓글