考虑一个对象,该对象生成另一个对象使用的数据以生成结果。该过程封装在一个类中,中间数据不相关。
在下面的示例中,该过程在构造时发生,并且没有问题。构造函数上的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
类型参数,编译器具有强制类型一致性所需的所有信息。
请注意,这是一个简化的示例,实际的类在构造后的某个时间内异步并定期运行处理。
答案 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);
}
}