>  기사  >  Java  >  Java 제네릭과 제네릭 와일드카드를 함께 분석해 보겠습니다.

Java 제네릭과 제네릭 와일드카드를 함께 분석해 보겠습니다.

WBOY
WBOY앞으로
2022-10-10 15:55:551909검색

이 기사에서는 제네릭 및 제네릭 와일드카드와 관련된 문제를 주로 소개하는 java에 대한 관련 지식을 제공합니다. 제네릭 지원은 컴파일러에서 지원되고 이때 바이트코드는 가상 머신에 로드되기 때문입니다. 정보가 지워졌으므로 제네릭은 일부 런타임 기능을 지원하지 않습니다. 모두에게 도움이 되기를 바랍니다.

Java 제네릭과 제네릭 와일드카드를 함께 분석해 보겠습니다.

추천 학습: "java 비디오 튜토리얼"

Generics는 런타임 기능이 아닙니다

여기서 여전히 Open JDK에 대해 이야기하고 있습니다

왜냐하면 generic 지원은 컴파일러에서 지원되고 바이트코드는 다음과 같기 때문입니다. 가상 머신을 실행할 때 일반 정보가 지워졌기 때문에 일반은 일부 런타임 기능을 지원하지 않습니다. 따라서 new와 같은 일부 작성 방법은 컴파일되지 않는다는 점에 유의하세요.

아래와 같이 Plate8742468051c85b06f0a0af9e3e506b5c 클래스는 아래와 같이 제네릭이 포함된 클래스입니다.

new Plate(...)
new Plate<T>(...)
class Plate<T> {
    T item;
    public Plate(T t) {
        new T();//是错误的,因为T是一个不被虚拟机所识别的类型,最终会被编译器擦除转为Object类给到虚拟机
        item = t;
    }
    public void set(T t) {
        item = t;
    }
    public T get() {
        return item;
    }
}

T는 가상 머신에서 인식되지 않는 유형이므로 제네릭 T는 새 것일 수 없습니다.

일반 와일드카드

와일드카드를 사용하는 일반 변수 표현식에는 다음과 같은 세 가지 형태가 있습니다.

  • b262cb231476023a78f271f818d3822b: Cb262cb231476023a78f271f818d3822b c의 요소 유형은 모두 A 또는 하위 클래스입니다. A

  • 04d82237e1b46a16a337cbfb82dc5df5:C04d82237e1b46a16a337cbfb82dc5df5 c의 요소 유형은 B 또는 B

  • 6b3d0130bba23ae47fe2b8e8cddf0195:C6b3d0130bba23ae47fe2b8e8cddf0195 , c의 요소 유형은 불확실합니다

정확히 무엇을 의미하고 어떻게 사용하는지 살펴보겠습니다~

상한 와일드카드

객체 지향 프로그래밍 분야에서는 기본 클래스가 기본 클래스라고 생각합니다 기본은 최상위 수준에 있습니다. 상속 트리의 관점에서 보면 Object 클래스가 맨 위에 있습니다.

그래서 우리는 이 표현을 84ae8caa7f538c9ddbae94fbfaae402c

3144ec2db68ad18cb0a34217f6025805는 T 또는 T 유형을 상속하는 모든 일반 유형을 나타냅니다.

먼저 다음 예제를 보세요.

Sping Webmvc의 RequestBodyAdvice

public interface RequestBodyAdvice {
   /**
    * Invoked first to determine if this interceptor applies.
    * @param methodParameter the method parameter
    * @param targetType the target type, not necessarily the same as the method
    * parameter type, e.g. for {@code HttpEntity<String>}.
    * @param converterType the selected converter type
    * @return whether this interceptor should be invoked or not
    */
   boolean supports(MethodParameter methodParameter, Type targetType,
         Class<? extends HttpMessageConverter<?>> converterType);
   ...
}

ping Webmvc에서 RequestBodyAdvice는 http 요청의 본문을 처리하는 데 사용되며, 지원은 특정 매개변수 유형이 다음과 같은지 여부를 결정하는 데 사용됩니다. HttpMessage 요청에서 지원됩니다.

HttpMessageConverter는 Json 형식의 Body를 지원하는 JsonViewRequestBodyAdvice 클래스와 같은 인터페이스입니다. 구현은 다음과 같습니다.

@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
      Class<? extends HttpMessageConverter<?>> converterType) {
   return (AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType) &&
         methodParameter.getParameterAnnotation(JsonView.class) != null);
}

JsonView를 처리하려면 AbstractJackson2HttpMessageConverter를 사용하세요. Jackson2 라이브러리는 널리 사용되는 Java JSON 구문 분석 라이브러리 중 하나입니다. Springboot와 함께 제공되는 HttpMessageConverter.

다양한 사용자가 다양한 유형의 Advice를 직접 정의하여 xml과 같은 많은 매개변수 유형을 지원할 수 있습니다. 그러면 sping-webmvc의 기능이 더욱 유연해지고 다양해질 수 있습니다. 다른 HttpMessageConverters를 통해 다른 HttpInputMessage로 변환됩니다. 아래와 같이

@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter,
      Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
   for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
      if (advice.supports(parameter, targetType, converterType)) {
         request = advice.beforeBodyRead(request, parameter, targetType, converterType);
      }
   }
   return request;
}

는 getMatchingAdvice(parameter, RequestBodyAdvice.class)를 통해 일치하는 조언 목록을 얻고, 이 목록을 탐색하고, 매개변수를 지원하는 조언을 구문 분석하고, HttpInputMessage 유형의 요청을 얻습니다.

상한 와일드카드의 표현식을 더 이상 설정할 수 없습니다

이전 와일드카드의 표현식은 더 이상 일반 필드를 설정할 수 없습니다. 실제 의미는 상한 와일드카드가 이미 설정된 일반 유형을 변경할 수 없다는 것입니다. . 이 데모를 살펴보겠습니다.

    @Test
    void genericTest() {
       
        Plate<Apple> p = new Plate<Apple>(new Apple());
        p.set(new Apple());//可以set
          Apple apple = p.get();
          
        Plate<? extends Fruit> q = new Plate<Apple>(new Apple());
       
        Fruit fruit = q.get();
      
         q.set(new Fruit());//将编译错误
    }

Platea1c2a0dec5aed8c144879933ac7026eb 이 표현은 Java 컴파일 중에 Fruit과 해당 파생 클래스가 컨테이너에 저장되어 있다는 것만 알고 있음을 의미합니다. 장치에 p 값이 할당된 후 플레이트는 "Apple"로 표시되지 않지만 자리 표시자 "CAP#1"(javap으로 바이트코드를 디컴파일하면 심각할 수 있음)로 표시되어 과일 캡처를 나타냅니다. 또는 Fruit 의 하위 클래스입니다.

그러나 와일드카드로 작성되었는지 여부에 관계없이 제네릭은 결국 특정 유형을 참조하고 컴파일러는 특수 "CAP#1"을 사용하므로 더 이상 이 필드를 재설정할 수 없습니다. 그렇지 않으면 일관성이 없는 것으로 나타납니다. 유형 컴파일 오류입니다.

그러나 이 기능은 사용을 방해하지 않습니다. 프레임워크는 유연한 확장을 달성하기 위해 상한 와일드카드 패러다임을 사용합니다.

하한 와일드카드

다음으로 하한 와일드카드를 살펴보겠습니다. 117c5a0bdb71ea9a9d0c2b99b03abe3e는 모든 유형의 T 또는 T의 상위 클래스를 나타내며 하한 유형은 T입니다.

언어의 함정

우리는 Fruit이나 Fruit의 기본 클래스만 설정할 수 있다고 생각하여 이해의 함정에 쉽게 빠지게 됩니다. 실제로 Fruit 및 Fruit 하위 클래스만 설정할 수 있습니다. 확인하기 위해 단위 테스트를 작성해 보겠습니다.

@Test
void genericSuperTest() {
    Plate<? super Fruit> p = new Plate<Fruit>(new Fruit());
    p.set(new Apple()); //ok,存取的时候可以存任意可以转为T的类或T
    p.set(new Object()); //not ok,无法 set Object
    Object object = p.get();//ok
    Fruit object = p.get();//not ok,super Fruit不是Fruit的子类
}

접속시 T로 변환 가능한 클래스나 T, 즉 Fruit이나 Fruit의 서브클래스를 설정할 수 있는 클래스를 저장할 수 있습니다.

단, 사용시 반드시 참고할 개체를 사용해야 합니다.

spring-kafka의 비동기 콜백

이제 실제 예제를 살펴보겠습니다.

SettableListenableFuture是spring 并发框架的一个类,继承自Future8742468051c85b06f0a0af9e3e506b5c,我们知道Future表示异步执行的结果,T表示返回结果的类型。ListenableFuture可以支持设置回调函数,如果成功了怎么处理,如果异常又如何处理。

在spring-kafka包里使用了SettableListenableFuture来设置异步回调的结果,kafka客户端调用 doSend发送消息到kafka队列之后,我们可以异步的判断是否发送成功。

public class SettableListenableFuture<T> implements ListenableFuture<T> {
  ...
   @Override
   public void addCallback(ListenableFutureCallback<? super T> callback) {
      this.settableTask.addCallback(callback);
   }
   @Override
   public void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback) {
      this.settableTask.addCallback(successCallback, failureCallback);
   }
 ...

SettableListenableFuture有重载的addCallback函数,支持添加ListenableFutureCallback117c5a0bdb71ea9a9d0c2b99b03abe3e callback和SuccessCallback117c5a0bdb71ea9a9d0c2b99b03abe3e successCallback;当调用的异步方法成功结束的时候使用notifySuccess来触发onSuccess的执行,这个时候将实际异步执行的结果变成参数给callback调用。

private void notifySuccess(SuccessCallback<? super T> callback) {
   try {
      callback.onSuccess((T) this.result);
   }
   catch (Throwable ex) {
      // Ignore
   }
}

SuccessCallback是一个函数式接口,从设计模式的角度来看是一个消费者,消费8742468051c85b06f0a0af9e3e506b5c类型的result。ListenableFutureCallback同理。

public interface SuccessCallback<T> {
   /**
    * Called when the {@link ListenableFuture} completes with success.
    * <p>Note that Exceptions raised by this method are ignored.
    * @param result the result
    */
   void onSuccess(@Nullable T result);
}

为什么要用notifySuccess(SuccessCallback117c5a0bdb71ea9a9d0c2b99b03abe3e callback)呢?

这是因为super能支持的范围更多,虽然实际产生了某一个具体类型的结果,比如kafka的send函数产生的结果类型为SendResult,其他的客户端可能使用其他的Result类型,但是不管是什么类型,我们在使用Spring的时候,可以对异步的结果统一使用Object来处理。

比如下面的这段代码,虽然是针对kafka客户端的。但对于其他的使用了Spring SettableListenableFuture的客户端,我们也可以在addCallback函数里使用Object来统一处理异常。

 @SneakyThrows
    public int kafkaSendAndCallback(IMessage message) {
        String msg = new ObjectMapper().writeValueAsString(message);
        log.debug("msg is {}. ", msg);
        ListenableFuture send = kafkaTemplate.send("test", msg);
        addCallback(message, send);
        return 0;
    }
    private void addCallback(IMessage msg, ListenableFuture<SendResult<String, String>> listenableFuture) {
        listenableFuture.addCallback(
                new SuccessCallback<Object>() {
                    @Override
                    public void onSuccess(Object o) {
                        log.info("success send object = " + msg.getContentType() + msg.getId());
                    }
                },
                new FailureCallback() {
                    @Override
                    public void onFailure(Throwable throwable) {
                        log.error("{}发送到kafka异常", msg.getContentType() + msg.getId(), throwable.getCause());
                    }
                });
    }
}

声明某个条件的任意类型?

比如 Collection1a4db2c2c2313771e5742b6debf617a1类的这个函数,

@Override
public boolean removeAll(Collection<?> collection) {
  return delegate().removeAll(collection);
}

Collection的removeAll函数移除原集合中的一些元素,因为最终使用equals函数比较要移除的元素是否在集合内,所以这个元素的类型并不在意。

我们再看一个例子,LoggerFactory

public class LoggerFactory {
    public static Logger getLogger(Class<?> clazz) {
        return new Logger(clazz.getName());
    }
}

LoggerFactory可以为任意Class根据它的名字生成一个实例。

总结:设计模式PECS

PECS是producer extends consumer super的缩写。

也是对我们上面的分析的一个总结

意思是extends用于生产者模式,而super用于消费者模式。

  • 消费者模式:比如上面的callback结果是为了消费;这些结果被消费处理。

  • 生产者模式:比如那些Converter,我们要处理特定格式的http请求,需要生产不同的转换器Converter。

推荐学习:《java视频教程

위 내용은 Java 제네릭과 제네릭 와일드카드를 함께 분석해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.im에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제