Consider an object which produces data that is consumed by another object to generate a result. The process is encapsulated in a class and the intermediate data is not relevant.
In the example below, the process takes place on construction and there is no issue. The type parameter on the constructor ensures compatible consumer/producers.
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());
}
...
}
If I wish to store references to the producer/consumer and do the processing later, then the code becomes:
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());
}
}
This introduces a generic type parameter to the class and forces it to be specified in any implementation.
My question is, is there any way to avoid this? The intermediate data type is not produced, stored, or consumed by this class and is not relevant to the user of the class. The compiler has all the information it requires to enforce type consistency, assuming the IntermediateType
type param can be specified somewhere.
Note that this is a simplified example and the actual class runs the processing asynchronously and periodically some time after construction.
Store the producer and consumer in an inner class with the type variable:
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();
}
}
In this way, you enforce that the producer and consumer are related for when you need to use them later, but you don't need that type information afterwards in the ProduceAndConsume
instance.
Producer<String> stringProducer = ...;
Consumer<Object> objConsumer = ...;
// No externally-visible type variables.
ProduceAndConsume pac1 = new ProduceAndConsume(stringProducer, objConsumer);
But the compiler enforces compatibility of the producer and consumer:
Consumer<Integer> integerConsumer = ...;
// Compiler error.
ProduceAndConsume pac2 = new ProduceAndConsume(stringProducer, integerConsumer);
While Andy's solution above is elegant and solves the issue, I ended up migrating to the below solution, simply to reduce the amount of boilerplate code.
I post it here as an alternative in case anyone finds themselves with similar requirements.
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();
}
}
Edit: Or in cases where some intermediate work is to be done:
public <IntermediateType> ProduceAndConsume(Producer<? extends IntermediateType> producer, Consumer<IntermediateType> consumer) {
producerConsumer = () -> {
IntermediateType data = producer.produce();
// Do intermediate work
consumer.consume(data);
};
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With