首页 > 解决方案 > Java如何强制泛型类型

问题描述

下面是我正在处理的代码示例。

interface KafkaRecord {}

class Book implements KafkaRecord { 
    //some properties here with getter setter
}

class BookDeserializer implements Deserializer<Book> {
    //Deserialiazing bytes to Book 
}


// props is Properties which having key value pair for configuration. 
Consumer<String, KafkaRecord> consumer = new KafkaConsumer<>(props);
ConsumerRecords<String, KafkaRecord> records = 
        consumer.poll(Duration.ofMillis(getTimeout()));

records.foreach(bean -> SomeComponent.process((Book) bean));

这项工作符合预期。我创建了另一个 bean Pencil,忘记实现 KafkaRecord,下面的代码仍然有效。

class Pencil {
  //some getters and setters
}

class PencilDeserializer implements Deserializer<Pencil> {
  //Deserialiazing bytes to Pencil
}

// props is Properties which having key value pair for configuration.
Consumer<String, KafkaRecord> consumer = new KafkaConsumer<>(props); 

// Pencil does not implement KafkaRecord
ConsumerRecords<String, KafkaRecord> records =
        consumer.poll(Duration.ofMillis(getTimeout()));

records.foreach(bean -> SomeOtherComponent.process((Pencil) bean));

ConsumerRecords 定义(kafka-clients-2.4.0.jar):

public class ConsumerRecords<K, V> implements Iterable<ConsumerRecord<K, V>> {
    //Behavior and getter/setters
}

由于通用擦除,似乎正在发生以下问题。我该如何解决这个问题?

经过更多的挖掘 -

AbstractConfig.java 的内部工作 - 用于设置 deserilizer -

  1. 它的调用 getClass(key) //key 来自配置
  2. 然后它创建从步骤 1 获得的实例。
  3. 然后它转换为 t 类(其中 t 是 Deserializer,在我们的例子中 T 应该是 KafkaRecord)

我相信,由于运行时类型不存在,它允许Deserializer<Pencil>首先设置。所以它能够反序列化它并能够将它分配给ConsumerRecords<String, KafkaRecord> records,因为类型不存在。

示例 GIT - https://github.com/jitendraVishnoi/kafka

编辑 - 我在 CustomExecutor.java 中添加了另一个方法 -

private void playWithRecord(KafkaRecord record) {
    System.out.println("Record class: " + record.getClass()); // Record class: class kafka.beans.Pencil
    System.out.println("record instanceof KafkaRecord: " + (record instanceof KafkaRecord)); // record instanceof KafkaRecord: false
    System.out.println("record instanceof Pencil: " + (record instanceof Pencil)); // record instanceof Pencil: true
    System.out.println("toString() :" + record); // toString() :Pencil{color='Red'}
  }

并且被称为-

ConsumerRecords<String, KafkaRecord> pencilRecords = pencilConsumer.poll(Duration.ofMillis(1000));
pencilRecords.forEach(bean -> playWithRecord(bean.value()));

第一行有效,但第二行用 ClassCastException 换行。

Exception in thread "Thread-0" java.lang.ClassCastException: kafka.beans.Pencil cannot be cast to kafka.beans.KafkaRecord
        at CustomExecutor.lambda$run$0(CustomExecutor.java:28)
        at java.lang.Iterable.forEach(Unknown Source)
        at CustomExecutor.run(CustomExecutor.java:28)
        at java.lang.Thread.run(Unknown Source)

如果我们更新 playWithRecord() 以接受 Object 类型的参数而不是 KafkaRecord 它可以工作。

在我看来它正在发生,因为 Type 已被删除 -

ConsumerRecords<String, KafkaRecord> pencilRecords = pencilConsumer.poll(Duration.ofMillis(1000));

标签: javagenerics

解决方案


实际上,这根本不是通用类型/擦除问题。

考虑一下:

SomeOtherComponent.process((Pencil) bean))

在编译时,问题是:bean我们知道的 ... 可以实现KafakaRecord... 可能是?Pencil的子类或某个子类Pencil

现在我们知道它不能是 的实例Pencil,因为Pencil没有实现KafakaRecord。但假设我声明以下内容:

class ColoredPencil extends Pencil implements KafkaRecord {
    ...
}

If our records collection at runtime consisted of KafkaRecord instances that were ColouredPencil objects, then they would be instances of Pencil, so the type cast should not be a compile time error.

In general, a cast from an interface type to a class type is NOT a compile time error, unless the class type is final and the class does not directly or indirectly implement the interface.


Is there any way I can report this issue while writing code. That Pencil must implement KafkaRecord.

Not with the code as written.

You could try this approach:

interface <T extends KafkaRecord> KafkaDeserializer extends Deserializer<T> {}

class PencilDeserializer implements KafkaDeserializer<Pencil> {
     // Deserialiazing bytes to Pencil
}

The latter will give a compilation error if Pencil hasn't been declared as a subtype of KafkaRecord. That might be sufficient for your needs, especially if you can ensure that all of your deserializers implement KafkaDeserializer rather than Deserializer. (I don't know enough about Kafka to know if this actually makes sense.)

And another approach would be to simply declare Pencil as final.


推荐阅读