问题 如何在运行时解析泛型变量时避免未经检查的强制转换?


我有一个参数化值,在运行时解决:

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;
        }
    }
}

有没有办法避免 未经检查的演员 在上面的代码中(标有长注释)?


3617
2017-11-02 12:20


起源

不是直接的。 (Java不支持流类型。)您可以在此处使用访问者模式。 - aioobe
看看这个问题: stackoverflow.com/questions/1129795/... - stevecross
@aioobe如果你用一个例子详细说明,我会很高兴。 - Denis Kulagin
@aioobe很酷的东西!它只是类型实际编码通过 什么方法来打电话 在运行之前。 - Denis Kulagin
问题是为什么你需要收到一个 Field<T> 在你的 foo() 方法而不仅仅是一个 Field<?>。 - Federico Peralta Schaffner


答案:


通常,没有办法,因为类型参数被绑定 宣言。您要做的是根据运行时值更改静态声明。

但是,您可以通过声明添加类型参数的参数化方法来最小化未选中的强制转换区域

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

然后使用

Field<Integer> intField = GenericsMain.<Integer> asParameterized(field);

6
2017-11-02 12:59



请阅读堆污染 docs.oracle.com/javase/tutorial/java/generics/...。 - Federico Peralta Schaffner
同意,这就是编译器发出警告的原因,但这种情况看起来很安全 - 对于什么类型的参数应该有确切的'秘密'知识。 - Vasily Liaskovsky
好吧,如果你相信你的'秘密'知识稳健性,那么我认为这是好的。但是你可能会度过糟糕的一天并且做得很好 Field<Integer> intField = asParameterized(field) 同 field 拿着双...不太容易修复和调试...... - Federico Peralta Schaffner


也许。您可以使用对类型信息进行编码的类型,而不是哑字符串标记。看到这篇博文: 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;
    }
}

加上改变的构造函数 Field 至 public Field(FieldKey<T> key, T value)

您仍然必须进行转换,但编译时间检查将确保它们永远不会失败。


3
2017-11-02 13:38



那么如何对FieldKey实例进行正确的参数化声明呢? :)我很确定,某些地方的东西应该被取消选中以打破循环 - Vasily Liaskovsky
请查看我博客文章中的代码;你可以看到演员阵容 get(TypedMapKey) 方法。您还可以将演员表移动到关键类: public T get(Field<?> f)。正如我所说,我的方法的主要优点是你可以在赋值和读取时使用类型,因此确保转换不会失败。 - Aaron Digulla


您可以使用注释来执行此操作。使用以下注释:

@SuppressWarnings("unchecked")

2
2017-11-02 12:26



这可行,但我的问题更多的是关于代码设计以及如何在架构上避免警告。 - Denis Kulagin
你不能直接在java中做到这一点。 - Vivek Singh


所有的答案都很好,但我认为没有足够的重点 为什么 你正在收到警告并正在投掷。

你是 明确规避 通过执行未经检查的强制转换来输入类型系统。通过说“我有关于此类型的信息,编译器无法使用” - 您告诉编译器您更了解。

这当然是一个可能和合理的用例:否则这些演员阵容是不允许的,但警告是好的,因为它表明你应该是  确定类型是什么。

这很有道理。事实上,如果您检查像GSON这样的库,它们会进行序列化 充满了这些警告和压抑

不要担心你的代码 - 一切都很好。如果有一种方法可以“欺骗”编译器不发出本来会出现严重问题的警告:)


1
2017-11-02 19:23