Alex Oscherov
Alex Oscherov

Reputation: 31

Avoiding warnings with generics in Java

I have snippet of code with generics which is stripped to its core looks something like this. There is trivial generic interface

public interface Doubler<T> {
    T doubling (T param);
}

and there is factory method that returns different instances of this interface

public class DoublerFactory {

    public static void main(String[] args) {
        Doubler<String> input = produceDoubler("String");
        System.out.println(input.doubling(args[0]));
    }

    public static <T> Doubler<T> produceDoubler(String type) {
        Doubler result;
        switch (type) {
            case "Integer" : result =  new IntDoubler(); break;
            case "Float" : result = new FloatDoubler(); break;
            case "String" : result = new StringDoubler(); break;
            default: result = null;
        }
        return result;
    }


    static class IntDoubler implements Doubler<Integer> {
        @Override
        public Integer doubling(Integer param) {
            return param*2;
        }
    }

    static class FloatDoubler implements Doubler<Float> {
        @Override
        public Float doubling(Float param) {
            return param*2;
        }
    }

    static class StringDoubler implements Doubler<String> {
        @Override
        public String doubling(String param) {
            return param + param;
        }
    }
}

Everything works fine except that last line in factory method "produceDoubler" produces "Unchecked assignment" warning. I actually understand why - what I can't figure out is how to write this snippet so that Java compiler is fully satisfied with assignments.

Upvotes: 2

Views: 96

Answers (2)

Javier Mart&#237;n
Javier Mart&#237;n

Reputation: 2605

A possible signature for your factory method would ask for the Class of the desired type:

public class DoublerFactory() {
    public <T> Doubler<T> produceDoubler(Class<? extends T> cls) {
        // This accepts both Class objects designating the primitive type and its object
        // wrapper, although obviously the result always uses the Object-derived wrapper
        if (Integer.TYPE.equals(cls) || Integer.class.equals(cls))
            return new IntDoubler();
        if (Float.TYPE.equals(cls) || Float.class.equals(cls))
            return new FloatDoubler();
        if (String.class.equals(cls))
            return new StringDoubler();
        throw new IllegalArgumentException();
    }
}

// User code
DoublerFactory df = //...
Doubler<Float>  flt = df.produceDoubler(Float.class);
Doubler<String> str = df.produceDoubler(String.class);

This way, you can maintain the compile-time type safety of the code, while still proving a factory interface.

A differnt implementation could use a map of Class<?> keys to Supplier<? extends Doubler<?>>, allowing you to add/remove mappings at runtime. You would once again have an unchecked cast, but this time it can be safely suppressed:

public class DoublerFactory() {
    private Map<Class<?>, Supplier<? extends Doubler<?>> doublerMakers;

    public DoublerFactory() {
        doublerMakers = new HashMap<>();
        addReg(Integer.TYPE, IntDoubler::new);
        addReg(Integer.class, IntDoubler::new);
        addReg(Float.TYPE, FloatDoubler::new);
        addReg(Float.class, FloatDoubler::new);
        addReg(String.class, StringDoubler::new);
    }

    // Using this function instead of directly calling Map.put in the constructor
    // allows the compiler to check that the concordancy in T is respected
    private <T> void addReg(Class<? extends T> cls, Supplier<? extends Doubler<T>> maker) {
        doublerMakers.put(cls, maker);
    }

    public <T> Doubler<T> produceDoubler(Class<? extends T> cls) {
        Supplier<? extends Doubler<?>> maker = doublerMakers.get(cls);
        if (maker == null) throw new IllegalArgumentException();
        // The cast here is unavoidable, but we know that if all mappings are inserted 
        // using the addReg() method, then the result is type safe.
        @SuppressWarnings("unchecked")
        Doubler<T> ret = (Doubler<T>)(Doubler)maker.get();
        return ret;
    }
}

Upvotes: 0

Louis Wasserman
Louis Wasserman

Reputation: 198103

public static <T> Doubler<T> produceDoubler(String type) {

What this signature says is that someone can call produceDoubler for any type T the caller wants, completely irrespective of type, and produceDoubler can return the appropriate Doubler. Of course, it can't. But that could do something silly like

Doubler<Color> doubler = produceDoubler("Integer");

which of course makes no sense. There's no sensical way to double a color, and there's no relationship between the type and the string.

The only way to make the compiler happy would be to return a Doubler<?>, but this would of course force you to do unsafe casts when you tried to use the Doubler.

There isn't actually a typesafe way to do this -- to connect arbitrary Strings to different types. The only typesafe alternative would be to lift out the switch -- to do at the top level something like

switch (type) {
   case "Double": {
      Doubler<Double> doubler = new DoubleDoubler();
      // use the doubler for everything you need to use it for
   }
   ...
}

...without trying to store a doubler of a non-concrete type at any time.

Upvotes: 3

Related Questions