Search code examples
javagenericsenums

Generic enum function call


I have an enum named Partitioning. I want each member of Partitioning to have its own partitioning algorithm. For example, the following would invoke the partitioning algorithm for BLAH:

Partitioning.BLAH.partition(myData, value);

The problem is that the objects I am manipulating all use generics, and I cannot get the generics to work correctly.

These are the classes involved.

public class MyData<T extends Comparable<? super T>> implements Comparable<MyData<T>> {
    public List<MyData<T>> partition(T value, Partitioning partitioning) {
        return partitioning.partition(this, value);
    }

    private interface PartitioningAlgorithm<U extends Comparable<? super U>>  {
        List<MyData<U>> partition(MyData<U> myData, U value);
    }

    private static class MyAlgorithm<U extends Comparable<? super U>> implements PartitioningAlgorithm<U> {
        public MyAlgorithm() {
            // Nothing to do
        }
        @Override
        public List<MyData<U>> partition(MyData<U> myData, U value) {
            return null; // TODO: Stubbed out for now
        }
    }

    public enum Partitioning {
        BLAH(new MyAlgorithm());

        private final PartitioningAlgorithm<? extends Comparable<?>> algorithm;

        <U extends Comparable<? super U>> Partitioning(PartitioningAlgorithm<U> algorithm) {
            this.algorithm = algorithm;
        }

        private <U extends Comparable<? super U>> List<MyData<U>> partition(MyData<U> myData, U value) {
            return algorithm.partition(myData, value);
        }
    }
}

In the enum's partition method definition, for the line:

return algorithm.partition(myData, value);

I get the following compile error:

java: incompatible types: MyData<U> cannot be converted to MyData<capture#1 of ? extends java.lang.Comparable<?>>

I assume that value will give a similar error.

Any help would be greatly appreciated.


Solution

  • If all the partitioning algorithms associated with each enum constant is a "general" partitioning algorithm that can partition every type of object, PartioningAlgorithm should not be generic. Instead, make its partition method generic.

    private interface PartitioningAlgorithm  {
        <U extends Comparable<? super U>> List<MyData<U>> partition(MyData<U> myData, U value);
    }
    
    private static class MyAlgorithm implements PartitioningAlgorithm {
        public MyAlgorithm() {
            // Nothing to do
        }
        @Override
        public <U extends Comparable<? super U>> List<MyData<U>> partition(MyData<U> myData, U value) {
            return null; // TODO: Stubbed out for now
        }
    }
    
    public enum Partitioning {
        BLAH(new MyAlgorithm());
    
        private final PartitioningAlgorithm algorithm;
    
        Partitioning(PartitioningAlgorithm algorithm) {
            this.algorithm = algorithm;
        }
    
        private <U extends Comparable<? super U>> List<MyData<U>> partition(MyData<U> myData, U value) {
            return algorithm.partition(myData, value);
        }
    }
    

    If some partitioning algorithms can only partition a specific type of object, then this design is inherently not type-safe. The best you can do is to throw an exception at runtime when a partitioning algorithm is used to partition something that it cannot partition.

    Add a canPartition method to PartitioningAlgorithm:

    private interface PartitioningAlgorithm  {
        <U extends Comparable<? super U>> List<MyData<U>> partition(MyData<U> myData, U value);
        
        boolean canPartition(Class<?> type);
    }
    
    private static class MyAlgorithm implements PartitioningAlgorithm {
        public MyAlgorithm() {
            // Nothing to do
        }
        @Override
        public <U extends Comparable<? super U>> List<MyData<U>> partition(MyData<U> myData, U value) {
            return null; // TODO: Stubbed out for now
        }
    
        @Override
        public boolean canPartition(Class<?> type) {
            return true; // can partition everything
        }
    }
    

    Here is an example of an implementation that only partitions strings:

    private static class OnlyPartitionStrings implements PartitioningAlgorithm {
        @Override
        public <U extends Comparable<? super U>> List<MyData<U>> partition(MyData<U> myData, U value) {
            return null; // TODO: Stubbed out for now
            // here ytou should cast to String whenever needed
        }
    
        @Override
        public boolean canPartition(Class<?> type) {
            return String.class.isAssignableFrom(type);
        }
    }
    

    In the enum, you would first check canPartition and potentially throw an exception.

    private <U extends Comparable<? super U>> List<MyData<U>> partition(MyData<U> myData, U value) {
        if (algorithm.canPartition(value.getClass())) {
            throw new UnsupportedOperationException(algorithm + " cannot partition " + value.getClass());
        }
        return algorithm.partition(myData, value);
    }