14

I have a parametrized value, that is resolved at runtime:

public class GenericsMain {
    public static void main(String... args) {
        final String tag = "INT";

        Field field = resolve(tag);

        if (tag.equals("INT")) {
            /*
                In here I am using the "secret knowledge" that if tag equals INT, then
                field could be casted to Field<Integer>. But at the same time I see an unchecked cast
                warning at here.

                Is there a way to refactor the code to be warning-free?
             */
            Field<Integer> integerField = (Field<Integer>) field;

            foo(integerField);
        }
    }

    public static Field resolve(String tag) {
        switch (tag) {
            case "INT":
                return new Field<>(1);
            case "DOUBLE":
                return new Field<>(1.0d);
            default:
                return null;
        }
    }

    public static <T> void foo(Field<T> param) {
        System.out.println(param.value);
    }

    static class Field<T> {
        public final T value;

        public Field(T value) {
            this.value = value;
        }
    }
}

Is there a way to avoid unchecked cast in the code above (marked with a long comment)?

Denis Kulagin
  • 8,472
  • 17
  • 60
  • 129
  • 2
    Not directly. (Java does not support flow types.) You could use a visitor pattern here though. – aioobe Nov 02 '15 at 12:26
  • 1
    Take a look at this question: http://stackoverflow.com/questions/1129795/what-is-suppresswarnings-unchecked-in-java – stevecross Nov 02 '15 at 12:26
  • @aioobe I would be really nice if you elaborate that with an example. – Denis Kulagin Nov 02 '15 at 12:32
  • I'm not sure what the code is trying to achieve. Can you explain the problem statement? – rajuGT Nov 02 '15 at 12:39
  • @DenisKulagin, have a look at [this q/a](http://stackoverflow.com/questions/3930808/how-to-avoid-large-if-statements-and-instanceof). Your `Field` would correspond to an `Animal`. – aioobe Nov 02 '15 at 12:39
  • 1
    @aioobe Cool thing! It's just type is actually encoded via *what method to call* prior to runtime. – Denis Kulagin Nov 02 '15 at 12:43
  • 1
    The question is why you need to receive a `Field` in your `foo()` method instead of just a `Field>`. – fps Nov 02 '15 at 13:35
  • @FedericoPeraltaSchaffner Good question. Actually it receives two arguments in full version of the code (of types): *Field* and *T*. That's why. – Denis Kulagin Nov 02 '15 at 13:47

4 Answers4

7

Generally, no way, since type parameter is bound to declaration. And what you want to do is to change static declaration based on the runtime value.

However, you can minimize area of unchecked cast by declaring parameterized method that adds type parameter

@SuppressWarnings("unchecked")
private static <T> Field<T> asParameterized(Field<?> field) {
    return (Field<T>) field;
}

and then use

Field<Integer> intField = GenericsMain.<Integer> asParameterized(field);
Vasily Liaskovsky
  • 2,248
  • 1
  • 17
  • 32
  • 2
    Please read about heap pollution https://docs.oracle.com/javase/tutorial/java/generics/nonReifiableVarargsType.html#heap_pollution. – fps Nov 02 '15 at 13:28
  • Agreed, that's why compiler emits the warning, but this very case looks safe - there is exact 'secret' knowledge of what type parameter should be. – Vasily Liaskovsky Nov 02 '15 at 13:37
  • Well, if you trust on your 'secret' knowledge robustness, then I think it's allright. But you might have a bad day and perfectly do `Field intField = asParameterized(field)` with `field` holding a double... Not very easy to fix and debug... – fps Nov 02 '15 at 13:42
3

You can use the annotation to do so. Use below annotation:

@SuppressWarnings("unchecked")
Vivek Singh
  • 2,047
  • 11
  • 24
3

Maybe. Instead of dumb String tags, you can use a type which encodes the type information. See this blog post: http://blog.pdark.de/2010/05/28/type-safe-object-map/

public class FieldKey<T> {
    private String name;

    public FieldKey(String name) {
        this.name = name;
    }

    public String name() {
        return name;
    }
}

plus changing the constructor of Field to public Field(FieldKey<T> key, T value).

You still have to cast but compile time checks will make sure that they never fail.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • 3
    And how are you going to make correctly parameterized declarations of FieldKey instances? :) I'm pretty sure, that something somewhere should be cast unchecked to break the loop – Vasily Liaskovsky Nov 02 '15 at 13:43
  • Please have a look at the code in my blog post; you can see the casts in the `get(TypedMapKey)` method. You can also move the cast into the key class: `public T get(Field> f)`. As I said, the main advantage of my approach is that you can use the type both on assignment and read and therefore make sure that the cast cannot fail. – Aaron Digulla Nov 03 '15 at 16:19
3

All the answers are fine, but I think there is not enough emphasis on why you're getting a warning and are casting.

You are explicitly circumventing the type system by performing an unchecked cast. By saying "I have information about this type that is not available to the compiler" - you are telling the compiler you know better.

That is of course a possible and reasonable use case: otherwise these casts would not be allowed, but a warning is good since it indicates you should be really sure what the type is.

This makes perfect sense. In fact, if you check libraries like GSON that do serialization they are full of these warnings and supressions.

Don't worry about your code - it's all fine. If there was a way to "trick" the compiler to not emit the warning that would have been a serious problem on the other hand :)

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504