Search code examples
javasum-type

Best way to create a Union type for ImmutablePairs in Java


In a Java program that I need to enhance I have a data structure

List<ImmutablePair<Integer, String>> params;

using ImmutablePair from org.apache.commons.lang3.tuple.

Going forward the String parameter needs to be able to hold either a String or a Double. For this I would like to create something like a union or Sum type of the two basic types.

How do I define this in Java for a list of ImmutablePairs?


Solution

  • I'm not clear what your question has to do with ImmutablePair.

    I would just do something like this:

    import java.util.function.Function;
    
    public interface StringOrDouble {
        <T> T get(Function<String,T> stringFunc, Function<Double,T> doubleFunc);
    
        class StringVersion implements StringOrDouble {
            private final String string;
    
            public StringVersion(String string) {
                this.string = string;
            }
    
            @Override
            public <T> T get(Function<String, T> stringFunc, Function<Double, T> doubleFunc) {
                return stringFunc.apply(string);
            }
        }
    
        class DoubleVersion implements StringOrDouble {
            private final Double doubleValue;
    
            public DoubleVersion(Double doubleValue) {
                this.doubleValue = doubleValue;
            }
    
            @Override
            public <T> T get(Function<String, T> stringFunc, Function<Double, T> doubleFunc) {
                return doubleFunc.apply(doubleValue);
            }
        }
    
        static StringOrDouble ofString(String string) {
            return new StringVersion(string);
        }
        static StringOrDouble ofDouble(Double doubleValue) {
            return new DoubleVersion(doubleValue);
        }
    
        public static void main(String[] args) {
            StringOrDouble[] vs = {ofString("hello"),ofDouble(101.0)};
            for (StringOrDouble v : vs) {
                System.out.println(v.get(String::toUpperCase, Double::toHexString));
            }
        }
    }
    

    And then use:

    List<ImmutablePair<Integer, StringOrDouble>> params;
    

    Or a generic version:

    import java.util.List;
    import java.util.function.Function;
    
    public interface Union<A, B> {
    
        <T> T get(Function<A, T> aFunc, Function<B, T> bFunc);
    
        class AVersion<A, B> implements Union<A, B> {
            private final A a;
    
            public AVersion(A a) {
                this.a = a;
            }
    
            @Override
            public <T> T get(Function<A, T> aFunc, Function<B, T> bFunc) {
                return aFunc.apply(a);
            }
        }
    
        class BVersion<A, B> implements Union<A, B> {
            private final B b;
    
            public BVersion(B b) {
                this.b = b;
            }
    
            @Override
            public <T> T get(Function<A, T> aFunc, Function<B, T> bFunc) {
                return bFunc.apply(b);
            }
        }
    
        static <A, B> Union<A, B> ofA(A a) {
            return new AVersion<>(a);
        }
    
        static <A, B> Union<A, B> ofB(B b) {
            return new BVersion<>(b);
        }
    
        public static void main(String[] args) {
            List<Union<String, Double>> vs = List.of(ofA("hello"), ofB(101.0));
            for (Union<String, Double> v : vs) {
                System.out.println(v.get(String::toUpperCase, Double::toHexString));
            }
        }
    }
    

    We can also add a method to Union:

        default void use(Consumer<A> aConsumer, Consumer<B> bConsumer) {
            get(a -> {aConsumer.accept(a); return null;}, b -> {bConsumer.accept(b); return null;});
        }
    

    Which allows us to pass void methods, so if we have a bean like:

    class Foo {
            private String s;
            private double d;
    
            public String getS() {
                return s;
            }
    
            public void setS(String s) {
                this.s = s;
            }
    
            public double getD() {
                return d;
            }
    
            public void setD(double d) {
                this.d = d;
            }
        }
    

    we can do:

            Union<String, Double> u = ofA("a string");
            Foo f = new Foo();
            u.use(f::setS, f::setD);
    

    And the appropriate setter will be called.