Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enforce class fields to be same generic type without specifying a class type parameter

Tags:

java

generics

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.

like image 704
hendalst Avatar asked Mar 12 '18 09:03

hendalst


2 Answers

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);
like image 128
Andy Turner Avatar answered Nov 05 '22 14:11

Andy Turner


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);
    };
}
like image 32
hendalst Avatar answered Nov 05 '22 12:11

hendalst