返回动态对象类型的泛型方法

2022-09-01 16:06:23

这可能是一个以前问过的问题,但像往常一样,当你提到“通用”这个词的那一秒,你会得到一千个解释类型擦除的答案。我很久以前就经历过那个阶段,现在对泛型及其使用了解很多,但这种情况稍微微妙一些。

我有一个容器,表示电子表格中的数据单元格,它实际上以两种格式存储数据:作为字符串显示,但也以另一种格式显示,取决于数据(存储为对象)。该单元还包含一个在类型之间转换的转换器,并且还对类型进行有效性检查(例如,IntegerTransformer检查字符串是否为有效整数,如果是,则返回要存储的整数,反之亦然)。

单元格本身不是键入的,因为我希望能够更改格式(例如,将辅助格式更改为浮点型而不是整数,或原始字符串),而不必使用新类型重建单元格对象。之前的尝试确实使用了泛型类型,但是一旦定义了编码就无法更改类型,并且经过大量反射,代码变得非常庞大。

问题是:如何以类型化的方式将数据从我的单元格中取出?我进行了实验,发现即使没有定义约束,也可以使用方法使用泛型类型。

public class Cell {
    private String stringVal;
    private Object valVal;
    private Transformer<?> trans;
    private Class<?> valClass;

    public String getStringVal(){
        return stringVal;
    }

    public boolean setStringVal(){
        //this not only set the value, but checks it with the transformer that it meets constraints and updates valVal too
    }

    public <T> T getValVal(){
        return (T) valVal;
        //This works, but I don't understand why
    }
}

让我失望的是:那就是?它不能投射任何东西,没有T类型的输入来约束它与任何东西匹配,实际上它并没有真正在任何地方说任何东西。具有 Object 的返回类型只会在任何地方都带来转换复杂性。

在我的测试中,我设置了一个Double值,它存储Double(作为一个对象),当我做Double testdou = testCell.getValVal()时;它立即工作,甚至没有未经检查的演员警告。然而,当我做String teststr = testCell.getValVal()时,我得到了一个ClassCastException。真的不奇怪。

对此,我看到两种观点:

第一:使用未定义的 Cast 似乎只不过是一种将 Cast 放在方法内部而不是返回后放在外部的笨拙方法。从用户的角度来看,它非常整洁,但是该方法的用户必须担心使用正确的调用:所有这一切都是隐藏复杂的警告和检查,直到运行时,但似乎有效。

第二种观点是:我不喜欢这段代码:它不干净,它不是我通常以编写为荣的那种代码质量。代码应该是正确的,而不仅仅是工作。错误应该被捕获和处理,并且预期,接口应该是万无一失的,即使唯一的期待用户是我自己,我总是更喜欢灵活的通用和可重用技术,而不是尴尬的技术。问题是:有没有正常的方法来做到这一点?这是一种实现无类型,全部接受ArrayList的偷偷摸摸的方式吗,它返回任何你想要的东西而不强制转换?还是我在这里错过了什么。有些东西告诉我,我不应该相信这个代码!

也许比我预期的更像是一个哲学问题,但我想这就是我要问的。

编辑:进一步测试。

我尝试了以下两个有趣的片段:

public <T> T getTypedElem() {
    T output = (T) this.typedElem;
    System.out.println(output.getClass());
    return output;
}

public <T> T getTypedElem() {
    T output = null;
    try {
        output = (T) this.typedElem;
        System.out.println(output.getClass());
    } catch (ClassCastException e) {
        System.out.println("class cast caught");
        return null;
    }
    return output;
}

当为 typedElem 分配一个 double 并尝试将其放入 String 中时,我得到一个异常,不是在强制转换为 上,而是在返回时,并且第二个代码段不保护。getClass 的输出是 java.lang.Double,这表明它是从 typedElem 动态推断出来的,但编译器级别的类型检查只是被迫的。

作为辩论的注释:还有一个用于获取valClass的函数,这意味着可以在运行时进行可分配性检查。

编辑2:结果

在考虑了选项之后,我选择了两种解决方案:一种是轻量级解决方案,但将函数注释为@depreciated,另一种是解决方案,您可以在其中传递要尝试将其转换为的类。这样,这是一个根据情况的选择。


答案 1

您可以尝试键入标记:

public <T> T getValue(Class<T> cls) {
    if (valVal == null) return null;
    else {
        if (cls.isInstance(valVal)) return cls.cast(valVal);
        return null;
    }
}

请注意,这不会执行任何转换(即,如果 是 或 的实例,则不能使用此方法提取 。DoublevalValFloatInteger

顺便说一句,您应该收到有关 .这是因为无法在运行时检查强制转换(Java泛型通过“擦除”工作,这实质上意味着泛型类型参数在编译后被遗忘),因此生成的代码更像是:getValVal

public Object getValVal() {
    return valVal;
}

答案 2

正如你所发现的,使用Java的类型系统可以表达的内容是有限制的,即使使用泛型也是如此。有时,您希望使用类型声明断言某些值的类型之间存在关系,但您不能(或者也许可以,以过多的复杂性和冗长的冗长代码为代价)。我认为这篇文章中的示例代码(问题和答案)就是一个很好的例证。

在这种情况下,如果您将对象/字符串表示形式存储在“转换器”中,Java编译器可以执行更多的类型检查。(也许你必须重新思考它是什么:也许它不仅仅是一个“变形金刚”。在基类上放置一个泛型绑定,并使该绑定成为“对象”的类型。Transformer

单元格中获取值而言,编译器类型检查无法为您提供帮助,因为值可以是不同类型的(并且您在编译时不知道给定单元格中将存储哪种类型的对象)。

我相信你也可以做类似的事情:

public <T> void setObject(Transformer<T> transformer, T object) {}

如果设置转换器和对象的唯一方法是通过该方法,则对参数进行编译器类型检查将防止不兼容的转换器/对象对进入单元。

如果我理解你在做什么,你使用的类型完全取决于细胞所持有的物体的类型,对吗?如果是这样,我不会将转换器/对象设置在一起,而是仅为对象提供一个 setter,并进行哈希查找以查找适当的转换器(使用对象类型作为键)。可以在每次设置值时或将其转换为字符串时进行哈希查找。无论哪种方式都可以。Transformer

这自然会使错误类型的 无法传递。Transformer