强制类字段为相同的泛型类型,而不指定类类型参数

时间:2018-03-12 09:05:32

标签: java generics

考虑一个对象,该对象生成另一个对象使用的数据以生成结果。该过程封装在一个类中,中间数据不相关。

在下面的示例中,该过程在构造时发生,并且没有问题。构造函数上的type参数确保兼容的使用者/生产者。

public class ProduceAndConsume {
    public interface Producer<T> {
        T produce();
    }
    public interface Consumer<V> {
        void consume(V data);
    }

    public <IntermediateType> ProduceAndConsume(Producer<? extends IntermediateType> producer, Consumer<IntermediateType> consumer) {
        consumer.consume(producer.produce());
    }

    ...

}

如果我希望存储对生产者/消费者的引用并稍后进行处理,则代码变为:

public class ProduceAndConsume<IntermediateType> {
    public interface Producer<T> {
        T produce();
    }
    public interface Consumer<V> {
        void consume(V data);
    }

    private Producer<? extends IntermediateType> producer;
    private Consumer<IntermediateType> consumer;

    public ProduceAndConsume(Producer<? extends IntermediateType> producer, Consumer<IntermediateType> consumer) {
        this.producer = producer;
        this.consumer = consumer;
    }

    ...

    private void doLater() {
        consumer.consume(producer.produce());
    }
 }

这会向类引入泛型类型参数,并强制在任何实现中指定它。

我的问题是,有什么方法可以避免这种情况吗?中间数据类型不由此类生成,存储或使用,并且与类的用户无关。假设可以在某处指定IntermediateType类型参数,编译器具有强制类型一致性所需的所有信息。

请注意,这是一个简化的示例,实际的类在构造后的某个时间内异步并定期运行处理。

3 个答案:

答案 0 :(得分:5)

将生产者和消费者存储在具有类型变量的内部类中:

public class ProduceAndConsume {
  private class Inner<IntermediateType> {
    private Producer<? extends IntermediateType> producer;
    private Consumer<IntermediateType> consumer;

    // Constructor omitted.

    private void doLater() {
      consumer.consume(producer.produce());
    }
  }

  private final Inner<?> inner;

  public <IntermediateType> ProduceAndConsume(Producer<? extends IntermediateType> producer, Consumer<IntermediateType> consumer) {
    this.inner = new Inner<>(producer, consumer);
  }

  private void doLater() { // Or just invoke inner.doLater() directly.
    inner.doLater();  
  }
}

通过这种方式,您可以强制生产者和消费者在以后需要使用它们时相关,但之后您不需要ProduceAndConsume实例中的类型信息。

Producer<String> stringProducer = ...;
Consumer<Object> objConsumer = ...;

// No externally-visible type variables.
ProduceAndConsume pac1 = new ProduceAndConsume(stringProducer, objConsumer);

但是编译器强制生成者和消费者的兼容性:

Consumer<Integer> integerConsumer = ...;
// Compiler error.
ProduceAndConsume pac2 = new ProduceAndConsume(stringProducer, integerConsumer);

答案 1 :(得分:1)

虽然Andy上面的解决方案很优雅并且解决了这个问题,但我最终迁移到以下解决方案,只是为了减少样板代码的数量。

如果有人发现自己有类似的要求,我会在此处发布。

public class ProduceAndConsume {
    public interface Producer<T> {
        T produce();
    }
    public interface Consumer<V> {
        void consume(V data);
    }

    private Runnable producerConsumer;

    public <IntermediateType> ProduceAndConsume(Producer<? extends IntermediateType> producer, Consumer<IntermediateType> consumer) {
        producerConsumer = () -> consumer.consume(producer.produce());
    }

    ...

    private void doLater() {
        producerConsumer.run();
    }
}

修改 或者在需要完成一些中间工作的情况下:

public <IntermediateType> ProduceAndConsume(Producer<? extends IntermediateType> producer, Consumer<IntermediateType> consumer) {
    producerConsumer = () -> {
        IntermediateType data = producer.produce();
        // Do intermediate work
        consumer.consume(data);
    };
}

答案 2 :(得分:0)

我认为Andy Turner和pwrex的答案都很棒!

我想补充说,可以在外部维护类型安全接口,同时在内部使用未经检查的强制转换删除类型。应该避免使用未经检查的强制转换,但一旦有理由使用它。

这避免了对嵌套类或lambda的需要。例如,如果不总是同时调用生产者和消费者,也可能需要它。

public class ProduceAndConsume {
    public interface Producer<T> {
        T produce();
    }

    public interface Consumer<V> {
        void consume(V data);
    }

    private Producer<?> producer;
    private Consumer<Object> consumer;

    @SuppressWarnings("unchecked")
    public <T> ProduceAndConsume(Producer<? extends T> producer, Consumer<T> consumer) {
        this.producer = producer;
        // Unchecked cast, but it's only visible internally in this class 
        // and we'll be careful to use the types correctly
        this.consumer = (Consumer<Object>) consumer;
    }

    private void doLater() {
        consumer.consume(producer);
    }
}