【问题标题】:How can I use generics to provide a "universal getter method"?如何使用泛型提供“通用 getter 方法”?
【发布时间】:2021-12-02 12:27:55
【问题描述】:

这个问题更具理论性(我想做的更复杂,但这是我坚持的部分),所以为这个人为的例子道歉,这可能没有多大意义。

假设我有一些类具有以不同形式返回其值的方法:

public class MyObject {
    public String getAsString() {...}
    public int getAsInt() {...}
    // and so on
}

我正在尝试创建一个方法,以允许我通过其参数指定要调用的 MyObject 方法。比如:

public <T> T getValue(MyObject obj, Class<T> c) {
    if (c == String.class) {
        return obj.getAsString();
    } else if (c == Integer.class) {
        return obj.getAsInt();
    } // and so on
}

那么我想这样调用这个方法,假设 obj 是一个 MyObject:

String s = getValue(obj, String.class);
int i = getValue(obj, Integer.class);
// and so on

我在 getValue 方法中收到编译错误“类型不匹配:无法从字符串转换为 T”(对于整数也是如此)。显然我只是没有完全理解泛型,但我认为这是泛型背后的一般思想——在这里我通过参数 c 指定(或至少试图指定)T 的真实类型。我做错了什么?

【问题讨论】:

  • 试试return c.cast(obj.getAsString()),对你有用吗
  • 你在getAsStringgetAsInt做什么?转换价值观?或者您只是将Object-typed 值转换为方法返回类型?
  • @ernest_k - 与我遇到的错误有关吗?这是一个非常人为的示例,旨在真正只关注泛型类型和我遇到的错误(抱歉),所以我们假设 getAsString 返回“hello”而 getAsInt 返回 42。
  • @ProGu 是的!至少编译。为什么?
  • @Jer 我正要发布一个答案,假设您的 API 合同假设调用者知道他们获得的值的数据类型。如果您不转换值,则可以完全避免所有这些转换。从这个意义上说,无论您是在转换还是只是在铸造很重要。 getValue() API 相当容易简化。

标签: java generics


【解决方案1】:

如果您想创建一个具有真正安全转换的单一方法 - 那么我建议在预期的 type 与其各自的 getter 之间设置一个映射。

给定MyObject 类定义为:

public class MyObject {

    public int getIntValue() {
        return 42;
    }

    public String getStringValue() {
        return "Answer";
    }
}

因此“访问器”类可以如下所示(如果需要,可以进一步概括):

public class MyObjectAccessor {    
    private final Map<Class<?>, Function<MyObject, ?>> registry = new HashMap<>();
    
    public Accessor() {
        registerGetter(Integer.class, MyObject::getIntValue);
        registerGetter(String.class, MyObject::getStringValue);
    }
    
    private <T> void registerGetter(Class<T> type, Function<MyObject, T> getter) {
        registry.put(type, getter);
    }
    
    @SuppressWarnings("unchecked")
    public <T> Optional<T> getValue(MyObject obj, Class<T> type) {
        return (Optional<T>) ofNullable(registry.get(type)).map(getter -> getter.apply(obj));
    }
}

这将使您可以通过控制意外/丢失的映射来使行为更加可预测。 (这里它返回一个Optional,但你也可以抛出异常或提供默认值或做其他事情)


请注意getValue 中的转换实际上是一个安全检查转换(即使它被标记为@SuppressWarnings),因为这里的“安全”证明有点超出当前 javac 静态代码的能力分析。

【讨论】:

    【解决方案2】:

    首先,if getAsStringgetAsInt 没有进行任何转换(例如,如果您的所有值都存储为字符串),您可能可以减少您的方法对此:

    public <T> T getValue(MyObject obj) {
        return (T) obj.value;
    }
    

    这将有一个未经检查的强制转换警告,但这并不比将打字决定留给你的调用者更糟糕(所以我只是 @SuppressWarnings("unchecked") 它)。如果你的调用者使用了错误的目标类型,他们将在运行时得到一个ClassCastException,我认为这与你当前的合同很相配。但是如果你想在你自己的方法中引发异常,你可以保留c.cast(obj.getAsX())

    使用上述内容,您的调用者只需使用:

    String s = getValue(obj);
    int i = getValue(obj);
    

    但是,如果您实际上是在 getAs... 方法中转换数据,那么在分派到正确的 getAsX 方法后,您需要在通用 getter 中强制转换,至少为 ProGu suggested(即,return c.cast(obj.getAsX()) 在每个分支)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-08-17
      • 1970-01-01
      • 2011-05-18
      • 2013-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-01
      相关资源
      最近更新 更多