728x90
반응형
SMALL

스트링 REST Client

스프링 부트가 REST 클라이언트 관련해서 직접적으로 기능을 제공하는 것은 아니다. REST 클라이언트는 Spring Framework에서 제공하는 것이고, 부트는 그걸 쉽게 사용할 수 있도록 빈을 등록해준다.

주의 할 것은 RestTemplate과 WebClient 두 타입의 빈을 등록해주는 것이 아니라, 빌더를 등록해준다. 그래서 우리는 빌더를 주입받아서 필요할 때마다 REST 클라이언트를 빌드해서 사용해야한다.

RESTful 웹 서비스를 소비하는 데 사용할 수 있는 몇 가지 방법이 있습니다. 주로 사용되는 두 가지 주요 도구는 **RestTemplate**과 **WebClient**이다.

1. RestTemplate:

  • 이것은 Spring이 제공하는 동기식 HTTP 클라이언트이다.
  • RESTful 서비스를 호출하는 데 사용되며, HTTP 메서드에 대한 템플릿 메서드를 제공한다.
  • 예를 들어, GET, POST, PUT, DELETE 등의 메서드를 지원한다.
  • 그러나 Spring 5.0 이후로는 RestTemplate이 deprecated되었으며, 대신 WebClient를 사용하는 것이 권장된다.
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(url, String.class);
  • Blocking I/O 기반의 Synchronous API
  • RestTemplateAutoConfiguration
  • 프로젝트에 spring-web 모듈이 있다면 RestTemplateBuilder를 빈으로 등록

예제로 이해하기

1) @RestController 구현

  • 3초를 sleep하는 GetMapping
  • 5초를 sleep하는 GetMapping

2) REST Client요청은 ApplicationRunner 구현 클래스에서 보내보자

@RestController
public class SampleController {

    @GetMapping("/hello")
    public String hello() throws InterruptedException {
    	Thread.sleep(3000L);
        return "hello";
    }

    @GetMapping("/sample")
    public String sample() throws InterruptedException {
    	Thread.sleep(5000L);
        return "sample";
    }
}

방법1. RestTemplateBuilder를 주입받아 RestTemplate 생성

  • getForObject로 get요청 보내기
  • blocking i/o : 호출된 함수가 자신의 작업을 모두 마칠 때까지 호출한 함수에게 제어권을 넘겨주지 않고 대기하게 만듬
  • sync : 호출된 함수의 작업 완료 여부를 신경쓰기 때문에 작업을 마칠 때까지 기다린다.
@Component
public class RestRunner implements ApplicationRunner {

	@Autowired
	public RestTemplateBuilder restTemplateBuilder;

	@Override
	public void run(ApplicationArguments args) throws Exception {
          RestTemplate restTemplate = restTemplateBuilder.build();

          StopWatch stopWatch = new StopWatch();
          stopWatch.start();

          String helloResult = restTemplate.getForObject("<http://localhost:8080/hello>", String.class);

          String sampleResult = restTemplate.getForObject("<http://localhost:8080/sample>", String.class);

          stopWatch.stop();
          System.out.println(stopWatch.prettyPrint());
    }
}

해당 코드의 결과

  • 5초 째에 "hello"를 출력하고, 8초 째에 "sample"을 출력한다.
  • sync-blocking : /hello GET요청이 끝날 때까지 기다리고, /sample GET요청이 끝날 때까지 기다림
  • 따라서 두 요청이 모두 끝나려면 8초가 걸린다.

2. WebClient

  • Spring 5에서 도입된 비동기, non-blocking, reactive 웹 클라이언트이다.
  • Project Reactor를 기반으로 하며, 비동기 처리와 백프레셔(Backpressure)를 지원한다.
  • RestTemplate보다 더 효율적인 리소스 사용이 가능하며, 대량의 요청을 처리하는데 적합하다.
  • RestTemplate이 deprecated된 이후, WebClient가 RESTful 서비스를 호출하는 주요 도구가 되었다.
WebClient webClient = WebClient.create();
Mono<String> result = webClient.get()
        .uri(url)
        .retrieve()
        .bodyToMono(String.class);
  • Non-Blocking I/O 기반의 Asynchronous API
  • WebClientAutoConfiguration
  • 프로젝트에 spring-webflux 모듈이 있다면 WebClient.Builder를 빈으로 등록

방법2. WebClient.Builder를 주입받아 WebClient 생성

  • get().uri().retrieve().bodyToMono(String.class)로 GET요청 보내기
  • Stream API를 사용하기 때문에 subscribe()로 결과를 반환해야 함
  • non-blocking i/o : 호출된 함수가 바로 결과를 반환하여, 호출한 함수에게 제어권을 바로 넘겨준다.
  • async : 호출된 함수의 작업 완료 여부를 신경쓰지 않기 때문에, 작업 완료 시 호출된 함수는 전달받은 콜백을 실행하기만 한다.

cf) subscribe()는 Javascript의 .then()을 생각하면 될 듯!

Promise 객체가 fulfilled되도 then을 쓰지 않으면 resolve()의 인자값을 꺼낼 수 없는 것처럼

내가 보냈던 요청에 대한 응답 이벤트를 받을 때, 어떤 행동을 수행할 것인지 이벤트 핸들러를 정의해주는 일이다.

@Component
public class RestRunner implements ApplicationRunner {

    @Autowired
    WebClient.Builder webClientBuilder;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        WebClient webClient = webClientBuilder.build();

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        Mono<String> helloResult = webClient.get().uri("<http://localhost:8080/hello>")
                                        .retrieve()
                                        .bodyToMono(String.class);
        helloResult.subscribe(result -> {
            System.out.println(result);

            if (stopWatch.isRunning()){
                stopWatch.stop();
            }

            System.out.println(stopWatch.prettyPrint());
            stopWatch.start();
        });

        Mono<String> worldResult =  webClient.get().uri("<http://localhost:8080/sample>")
                                        .retrieve()
                                        .bodyToMono(String.class);
        worldResult.subscribe(result -> {
            System.out.println(result);

            if (stopWatch.isRunning()){
                stopWatch.stop();
            }

            System.out.println(stopWatch.prettyPrint());
            stopWatch.start();
        });
    }
}
  • 3초 째에 "hello"를 출력하고, 5초 째에 "sample"을 출력한다.
  • async-nonblocking : /hello GET요청과 /sample GET요청이 병렬적으로 수행된다.
  • 따라서 두 요청이 모두 끝나려면 5초가 걸린다.

스프링 REST Client 커스터마이징

WebClient 커스터마이징

  1. webClientBuilder.build()로 빌드하기 전에 필요한 설정 가능
  2. 글로벌 WebClient 객체 사용하기
    • WebClientCustomizer 빈 재정의
    • ApplicationContext에서 전역적으로 적용한다.
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.run(args);
    }

    @Bean
    public WebClientCustomizer webClientCustomizer(){
        return new WebClientCustomizer() {
            @Override
            public void customize(WebClient.Builder webClientBuilder) {
                webClientBuilder.baseUrl("<http://localhost:8080>");
            }
        };
    }
}

RestTemplate 커스터마이징

  1. RestTemplateBuilder 빈 재정의
    • org.apache.httpcomponents:httpclient 의존성 추가 필요
    • 마찬가지로 ApplicationContext에서 전역적으로 정의
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.run(args);
    }

    @Bean
    public RestTemplateCustomizer restTemplateCustomizer() {
        return new RestTemplateCustomizer() {
            @Override
            public void customize(RestTemplate restTemplate) {
                restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
            }
        };
    }
}

728x90
반응형
LIST
728x90
반응형
SMALL

스프링 컨테이너(Spring Container)에 대해 설명해주세요.

스프링 컨테이너(Spring Container)

스프링 컨테이너는 스프링 프레임워크의 핵심 컴포넌트이다.

스프링 컨테이너는 자바 객체의 생명 주기를 관리하며, 생성된 자바 객체들에게 추가적인 기능을 제공한다.

스프링에서는 자바 객체를 빈(Bean)이라 한다.

즉, 스프링 컨테이너는 내부에 존재하는 빈의 생명주기를 관리(빈의 생성, 관리, 제거 등)하며, 생성된 빈에게 추가적인 기능을 제공하는 것이다.

스프링 컨테이너는 XML, 어노테이션 기반의 자바 설정 클래스로 만들 수 있다.

스프링 부트(Spring Boot)를 사용하기 이전에는 xml을 통해 직접적으로 설정해 주어야 했지만, 스프링 부트가 등장하면서 대부분 사용하지 않게 되었다.


스프링 컨테이너의 종류

스프링 컨테이너는 Beanfactory와 ApplicationContext 두 종류의 인터페이스로 구현되어 있다.

빈 팩토리는 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트이고, 빈 팩토리를 좀 더 확장한 것이 애플리케이션 컨텍스트이다.

애플리케이션 컨텍스트는 IoC 방식을 따라 만들어진 일종의 빈 팩토리로 둘 다 동일한 개념이라 생각하면 된다.

주로 사용되는 스프링 컨테이너는 애플리케이션 컨텍스트이다.

BeanFactory

가장 기본적인 컨테이너로, 빈의 생성과 관리, 빈 간의 의존성 해결 등을 담당

빈 팩토리(BeanFactory)는 스프링 컨테이너의 최상위 인터페이스이다.

BeanFactory는 빈을 등록, 생성, 조회 등의 빈을 관리하는 역할을 하며, getBean() 메서드를 통해 빈을 인스턴스화 할 수 있다.

@Bean 어노테이션이 붙은 메서드의 이름을 스프링 빈의 이름으로 사용하여 빈 등록을 한다.

ApplicationContext

애플리케이션 컨텍스트(ApplicationContext)는 BeanFactory의 기능을 상속받아 제공한다.

BeanFactory의 모든 기능을 포함하며, 이외에도 메시지 소스 처리(국제화 지원), 이벤트 발행, 애플리케이션 계층에 특화된 다양한 기능을 제공

따라서, 빈을 관리하고 검색하는 기능을 BeanFactory가 제공하고, 그 외의 부가 기능을 제공한다.

  • 부가 기능
    • MessageSource : 메시지 다국화를 위한 인터페이스
    • EnvironmentCapable : 개발, 운영, 환경변수 등으로 나누어 처리하고, 애플리케이션 구동 시 필요한 정보들을 관리하기 위한 인터페이스
    • ApplicationEventPublisher : 이벤트 관련 기능을 제공하는 인터페이스
    • ResourceLoader : 파일, 클래스 패스, 외부 등 리소스를 편리하게 조회

스프링 컨테이너의 기능

빈의 생성 및 관리: 인스턴스화, 구성, 전체 생명 주기 및 제거까지 관리한다.

  • 스프링 컨테이너는 설정 파일이나 애노테이션을 통해 지정된 빈을 생성하고 관리합니다.
  • 컨테이너는 개발자가 정의한 빈을 객체로 만들어 관리하고 개발자가 필요로 할 때 제공한다.

의존성 주입(Dependency Injection, DI): 애플리케이션의 컴포넌트를 관리할 수 있다.

  • 컨테이너는 각 빈 간의 의존성을 관리하며, 필요에 따라 의존성 주입을 수행합니다.

스프링 컨테이너 : 스프링 컨테이너를 통해 원하는 만큼 많은 객체를 가질 수 있다.

  • 서로 다른 빈을 연결하여 애플리케이션 빈을 연결하는 역할을 한다.
  • 개발자는 모듈 간에 의존 및 결합으로 인해 발생하는 문제로부터 자유로울 수 있다.
  • 메서드가 언제 어디서 호출되어야 하는지, 메서드를 호출하기 위해 필요한 매개 변수를 준비해서 전달하지 않는다.

생명주기 관리: 스프링 컨테이너는 빈의 생명주기를 관리하며, 초기화 및 소멸과 같은 라이프사이클 이벤트에 대한 콜백 인터페이스를 제공합니다.

프록시 및 AOP 지원: 스프링 컨테이너는 프록시 생성 및 AOP(Aspect-Oriented Programming) 기능을 지원하여 관점 지향 프로그래밍을 가능하게 합니다.


스프링 컨테이너를 사용하는 이유

먼저, 객체를 생성하기 위해서는 new 생성자를 사용해야 한다. 그로 인해 애플리케이션에서는 수많은 객체가 존재하고 서로를 참조하게 된다.

객체 간의 참조가 많으면 많을수록 의존성이 높아지게 된다.

이는 낮은 결합도와 높은 캡슐화를 지향하는 객체지향 프로그래밍의 핵심과는 먼 방식이다.

따라서, 객체 간의 의존성을 낮추어(느슨한 결합) 결합도는 낮추고, 높은 캡슐화를 위해 스프링 컨테이너가 사용된다.

또한, 기존의 방식으로는 새로운 기능이 생기게 되면 변경 사항들을 수작업으로 수정해야 한다.

프로젝트가 커질수록 의존도는 높아질 것이고, 그에 따라 코드의 변경도 많아질 것이다.

하지만, 스프링 컨테이너를 사용하면 구현 클래스에 있는 의존성을 제거하고 인터페이스에만 의존하도록 설계할 수 있다.


스프링 컨테이너의 생성 과정

주로 사용하는 설정 방식은 Java의 어노테이션 기능을 통해 설정하게 된다.

하지만 기존 방식인 XML 방식에 대해서도 알아야 한다.

기본적으로 스프링 컨테이너는 Configuration Metadata를 사용한다.

또한, 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용하여 스프링 빈을 등록한다.

스프링 컨테이너를 만드는 다양한 방법은 ApplicationContext 인터페이스의 구현체이다.

AppConfig.class 등의 구성 정보를 지정하여 스프링 컨테이너를 생성할 수 있다.

애플리케이션 클래스는 구성 메타데이터와 결합되어 ApplicationContext를 생성하고 초기화된다.

이후 실행 가능한 시스템 또는 애플리케이션을 갖게 된다.

만약, 스프링 빈 조회에서 상속관계가 있을 시에는 부모 타입으로 조회하면 자식 타입도 함께 조회된다. 즉, Object 타입으로 조회하게 되면 모든 스프링 빈을 조회할 수 있다.


스프링 컨테이너 생성과 등록

아래 코드는 자바 어노테이션을 기반으로 컨테이너를 구성하고 스프링 컨테이너에 등록하는 예시이다.

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

아래 코드는 어노테이션 기반으로 위 코드에서 구성한 스프링 컨테이너를 생성하는 방법이다.

public class MemberApp {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean("memberService", MemberService.class)
    }
}

@ApplicationContext(AppConfig.class)

ApplicationContext를 스프링 컨테이너라고 하며, 인터페이스로 구현되어 있다.

  • 다형성이 적용이 되어 있으며, 다양한 구현체가 존재

AnnotationConfigApplicationContext는 ApplicationContext 인터페이스의 구현체 중 하나이다.

@Configuration

스프링 컨테이너가 해당 어노테이션이 붙은 클래스를 설정 정보로 사용한다.

클래스 내부에 @Bean 어노테이션이 적힌 메서드를 모두 호출하여 얻은 객체를 스프링 컨테이너에 등록하게 된다.

스프링 컨테이너에 등록된 객체를 **스프링 빈(Bean)**이라 한다.

applicationContext.getBean(”이름”, 타입);

스프링 빈은 getBean() 메서드를 이용하여 얻을 수 있다.


ApplicationContext 인터페이스의 구현체

ApplicationContext 인터페이스에는 수많은 구현체가 존재한다.

이러한 다양한 구현체를 통해 스프링 컨테이너를 다양한 방법으로 생성할 수 있다.

 

728x90
반응형
LIST
728x90
반응형
SMALL

제네릭에 대해서 설명하고, 컬렉션 클래스에서 왜 제네릭을 사용하는 지 설명해주세요.

제네릭(generic)이란?

자바에서 제네릭(generic)이란 데이터의 타입(data type)을 일반화한다(generalize)는 것을 의미한다.

클래스나 메서드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법이다. 이를 통해 타입 안전성을 보장하고, 타입 변환에 따른 불필요한 코드를 줄일 수 있다.

예를 들어, Java의 ArrayList 클래스는 객체를 저장할 때 어떤 타입의 객체를 저장할 것인지 미리 지정할 수 있다. 이런 방식으로 사용하면, 컴파일러는 이 리스트에 잘못된 타입의 객체가 추가되는 것을 방지하며, 객체를 꺼낼 때 타입 변환을 수행하지 않아도 된다.

ArrayList<String> list = new ArrayList<String>();
list.add("hello"); // 올바른 사용
list.add(new Integer(5)); // 컴파일 오류
String s = list.get(0); // 타입 변환 불필요

제네릭을 사용하는 주요 이유는 다음과 같습니다:

  1. 타입 안전성(Type Safety): 제네릭을 사용하면 컴파일 시점에 타입 체크를 수행할 수 있으므로, 실행 시점에 발생할 수 있는 타입 변환 오류를 방지할 수 있다.
  2. 코드 간결성(Boilerplate code reduction): 제네릭을 사용하면, 개발자가 명시적으로 타입 변환을 수행하는 코드를 작성할 필요가 없어진다. 이로 인해 코드가 더 간결하고 읽기 쉬워진다.

따라서 컬렉션 클래스에서 제네릭을 사용하면, 컴파일 시점의 타입 체크와 함께 코드의 간결성과 가독성을 향상시킬 수 있다.


제네릭의 장점

1. 제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있다.

2. 클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없다. 즉, 관리하기가 편하다.

3. 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아진다.


제네릭의 사용방법

보통 제네릭은 아래 표의 타입들이 많이 쓰인다.

타입 설명

<T> Type
<E> Element
<K> Key
<V> Value
<N> Number

물론 반드시 한 글자일 필요는 없다. 또한 설명과 반드시 일치해야 할 필요도 없다. 예로들어 <Ele>라고 해도 전혀 무방하다. 다만 대중적으로 통하는 통상적인 선언이 가장 편하기 때문에 위와같은 암묵적(?)인 규칙이 있을 뿐이다.


제네릭의 선언 및 생성

자바에서 제네릭은 클래스와 메소드에만 다음과 같은 방법으로 선언할 수 있다.

class MyArray<T> {
	T element;
	void setElement(T element) { this.element = element; }
	T getElement() { return element; }
}

위의 예제에서 사용된 'T'를 타입 변수(type variable)라고 하며, 임의의 참조형 타입을 의미한다.

꼭 'T'뿐만 아니라 어떠한 문자를 사용해도 상관없으며, 여러 개의 타입 변수는 쉼표(,)로 구분하여 명시할 수 있다.

타입 변수는 클래스에서뿐만 아니라 메소드의 매개변수나 반환값으로도 사용할 수 있다.

위와 같이 선언된 제네릭 클래스(generic class)를 생성할 때에는 타입 변수 자리에 사용할 실제 타입을 명시해야 한다.


제한된 제네릭과 와일드 카드

<K extends T>	// T와 T의 자손 타입만 가능 (K는 들어오는 타입으로 지정 됨)
<K super T>	// T와 T의 부모(조상) 타입만 가능 (K는 들어오는 타입으로 지정 됨)
 
<? extends T>	// T와 T의 자손 타입만 가능
<? super T>	// T와 T의 부모(조상) 타입만 가능
<?>		// 모든 타입 가능. <? extends Object>랑 같은 의미

보통 이해하기 쉽게 다음과 같이 부른다.

extends T : 상한 경계

? super T : 하한 경계

<?> : 와일드 카드(Wild card)

이 때 주의해야 할 게 있다. K extends T와 ? extends T는 비슷한 구조지만 차이점이 있다.

'유형 경계를 지정'하는 것은 같으나 경계가 지정되고 K는 특정 타입으로 지정이 되지만, ?는 타입이 지정되지 않는다는 의미다.


1. <K extends T>, <? extends T>

이 것은 T 타입을 포함한 자식(자손) 타입만 가능하다는 의미다. 즉, 다음과 같은 경우들이 있다.

<T extends B>	// B와 C타입만 올 수 있음<T extends E>	// E타입만 올 수 있음<T extends A>
// A, B, C, D, E 타입이 올 수 있음<? extends B>	// B와 C타입만 올 수 있음<? extends E>	
// E타입만 올 수 있음<? extends A>	// A, B, C, D, E 타입이 올 수 있음

주석에 썼듯이 보면 알겠지만, 상한 한계. 즉 extends 뒤에 오는 타입이 최상위 타입으로 한계가 정해지는 것이다.

대표적인 예로는 제네릭 클래스에서 수를 표현하는 클래스만 받고 싶은 경우가 있다. 대표적인 Integer, Long, Byte, Double, Float, Short 같은 래퍼 클래스들은 Number 클래스를 상속 받는다.

즉,  Integer, Long, Byte, Double, Float, Short 같은 수를 표현하는 래퍼 클래스만으로 제한하고 싶은 경우 다음과 같이 쓸 수 있다.

public class ClassName <K extends Number> { ... }

이렇게 특정 타입 및 그 하위 타입만 제한 하고 싶을 경우 쓰면 된다. 좀 더 구체적으로 예로 들자면, 다음과 같다. Integer는 Number 클래스를 상속받는 클래스라 가능하지만, String은 Number클래스와는 완전 별개의 클래스이기 때문에 에러(Bound mismatch)를 띄운다.

public class ClassName <K extends Number> {	...}
	public class Main {	
		public static void main(String[] args) {
			ClassName<Double> a1 = new ClassName<Double>();	
// OK!		ClassName<String> a2 = new ClassName<String>();	// error!	
	}
}

2. <K super T>, <? super T>

이 것은 T 타입의 부모(조상) 타입만 가능하다는 의미다. 즉, 다음과 같은 경우들이 있다.

<K super B>	
// B와 A타입만 올 수 있음<K super E>	
// E, D, A타입만 올 수 있음<K super A>	
// A타입만 올 수 있음<? super B>	
// B와 A타입만 올 수 있음<? super E>	
// E, D, A타입만 올 수 있음<? super A>	
// A타입만 올 수 있음

주석에 썼듯이 보면 알겠지만, 하한 한계. 즉 super 뒤에 오는 타입이 최하위 타입으로 한계가 정해지는 것이다.

대표적으로는 해당 객체가 업캐스팅(Up Casting)이 될 필요가 있을 때 사용한다.

예로들어 '과일'이라는 클래스가 있고 이 클래스를 각각 상속받는 '사과'클래스와 '딸기'클래스가 있다고 가정해보자.

이 때 각각의 사과와 딸기는 종류가 다르지만, 둘 다 '과일'로 보고 자료를 조작해야 할 수도 있다. (예로들면 과일 목록을 뽑는다거나 등등..) 그럴 때 '사과'를 '과일'로 캐스팅 해야 하는데, 과일이 상위 타입이므로 업캐스팅을 해야한다. 이럴 때 쓸 수 있는 것이 바로 super라는 것이다.

조금 더 현실성 있는 예제라면 제네릭 타입에 대한 객체비교가 있다.

public class ClassName <E extends Comparable<? super E>> { ... }

특히 PriorityQueue(우선순위 큐), TreeSet, TreeMap 같이 값을 정렬하는 클래스 만약 여러분이 특정 제네릭에 대한 자기 참조 비교를 하고싶을 경우 대부분 공통적으로 위와 같은 형식을 취한다.

extends는 앞서 말했듯 extends 뒤에오는 타입이 최상위 타입이 되고, 해당 타입과 그에 대한 하위 타입이라고 했다. 그럼 역으로 생각해보면 이렇다. E 객체는 반드시 Comparable을 구현해야한다는 의미 

public class SaltClass <E extends Comparable<E>> { ... }
	public class Student implements Comparable<Student> {	
		@Override	
		public int compareTo(Person o) { ... };}

public class Main {	
	public static void main(String[] args) {		
		SaltClass<Student> a = new SaltClass<Student>();	}}

이렇게만 쓴다면 E extends Comparable<E> 까지만 써도 무방하다.

즉, SaltClass의 E 는 Student 이 되어야 하는데, Comparable<Student> 의 하위 타입이어야 하므로 거꾸로 말해 Comparable을 구현해야한다는 의미인 것이다.

그러면 왜 Comparable<E> 가 아닌 <? super E> 일까?

잠깐 설명했지만, super E는 E를 포함한 상위 타입 객체들이 올 수 있다고 했다.

만약에 위의 예제에서 학생보다 더 큰 범주의 클래스인 사람(Person)클래스를 둔다면 어떻게 될까? 한마디로 아래와 같다면?

public class SaltClass <E extends Comparable<E>> { ... }	
// Error가능성 있음
public class SaltClass <E extends Comparable<? super E>> { ... }	
// 안전성이 높음
public class Person {...}
public class Student extends Person implements Comparable<Person> {	
@Override	
public int compareTo(Person o) { ... };}
public class Main {	
public static void main(String[] args) {		
SaltClass<Student> a = new SaltClass<Student>();	}}

쉽게 말하면 Person을 상속받고 Comparable 구현부인 comparTo에서 Person 타입으로 업캐스팅(Up-Casting) 한다면 어떻게 될까?

만약 <E extends Comparable<E>>라면 SaltClass<Student> a 객체가 타입 파라미터로 Student를 주지만, Comparable에서는 그보다 상위 타입인 Person으로 비교하기 때문에 Comparable<E>의 E인 Student보다 상위 타입 객체이기 때문에 제대로 정렬이 안되거나 에러가 날 수 있다.

그렇기 때문에 E 객체의 상위 타입, 즉 <? super E> 을 해줌으로써 위와같은 불상사를 방지할 수가 있는 것이다.

즉, <E extends Comparable<? super E>> 는 쉽게 말하자면 E 타입 또는 E 타입의 슈퍼클래스가 Comparable을 의무적으로 구현해야한다는 뜻으로 슈퍼클래스타입으로 Up Casting이 발생하더라도 안정성을 보장받을 수 있다.

이 부분은 중요한 것이 이후 필자가 PriorityQueue와 TreeSet 자료구조를 구현할 것인데, 이 부분을 이해하고 있어야 가능하기 때문에 조금은 어렵더라도 미리 언급하고 가려한다.

<E extends Comparable<? super E>> 에 대해 설명이 조금 길었다. 이 긴 내용을 한 마디로 정의하자면 이렇다.

"E 자기 자신 및 조상 타입과 비교할 수 있는 E"

3. <?> (와일드 카드 : Wild Card)

마지막으로 와일드 카드다.

이 와일드 카드 <?> 은 <? extends Object> 와 마찬가지라고 했다. Object는 자바에서의 모든 API 및 사용자 클래스의 최상위 타입이다. 한마디로 다음과 같은 의미나 마찬가지다.

public class ClassName { ... }public class ClassName extends Object { ... }

public class ClassName extends Object {} 를 묵시적으로 상속받는 것이나 다름이 없다.

한마디로 <?>은 무엇이냐. 어떤 타입이든 상관 없다는 의미다. 당신이 String을 받던 어떤 타입을 리턴 받던 알빠 아니라는 조금 과격한 얘기..

이는 보통 데이터가 아닌 '기능'의 사용에만 관심이 있는 경우에 <?>로 사용할 수 있다.


제네릭의 제거 시기

자바 코드에서 선언되고 사용된 제네릭 타입은 컴파일 시 컴파일러에 의해 자동으로 검사되어 타입 변환된다.

그리고서 코드 내의 모든 제네릭 타입은 제거되어, 컴파일된 class 파일에는 어떠한 제네릭 타입도 포함되지 않게 된다.

이런 식으로 동작하는 이유는 제네릭을 사용하지 않는 코드와의 호환성을 유지하기 위해서이다.

728x90
반응형
LIST

+ Recent posts