【问题标题】:Tell compiler an <Object> is equivalent to the <?> it wants告诉编译器一个 <Object> 等价于它想要的 <?>
【发布时间】:2021-01-04 04:17:11
【问题描述】:

我有一些对象可以预先生成一些配置,以便它们以后可以更快地处理计算(可能是几次)。我正在尝试对其进行通用化以避免将配置作为Object 传递并每次都进行转换。

interface IComputable<T> {
    T configure(); // Generate configuration object
    int compute(T conf); // Run the computation based on the pre-generated configuration
    float precision(T conf); // Make the compute() computation finer-grained
    ...
}
class ComputableInfo {
    IComputable<?> computable;
    Object config; // Real type is <?>
    int result;

    ComputableInfo(String id) {
        computable = ComputableFactory.createFrom(id);
        config = computable.configure();
        result = computable.compute(config); // <<<--- The method compute(capture#3-of ?) in the type TestInterface.IComputable<capture#3-of ?> is not applicable for the arguments (Object)
    }
}

我收到编译错误:

TestInterface.IComputable 类型中的方法 compute(capture#3-of ?) 不适用于参数 (Object)

当然,我可以将int compute(T conf) 替换为int compute(Object conf),但我必须将其显式转换为适当的T。这不是什么大问题,但它使代码不那么明显。

我也可以将ComputableInfo 设为通用

interface ComputableInfo<T> {
    IComputable<T> computable;
    T config;
    ...

但这会在其他一些地方产生编译问题(主要是“原始类型”警告),我想避免比以前的解决方法更多的地方(使用Object而不是T)。

有没有办法做到这一点?我什至愿意在编译器设置中将此类问题从错误变为警告,或者可能有一个额外的私有方法可以在单个对象中返回 configresult

编辑:如果我将ComputableInfo 设为通用,则添加“进一步的编译问题”:我在接口中有另一个方法(参见已编辑),它通过ComputableInfo 调用:

ComputableInfo<?> info = getInfo(id);
info.computable.precision(info.config); // <<<--- (same kind of error)

问题是ComputableInfo 无法知道Computable&lt;T&gt;T 类型(或者我不知道),因为它来自一个从配置文件构建它的工厂。

【问题讨论】:

  • 使ComputableInfo 类型泛型 正确的解决方案。修复其他警告。
  • 你得到编译错误的原因是因为IComputable&lt;?&gt;是一个IComputable,它包含一些未知类型,你不能将任意Object传递给它,因为编译器没有办法检查该对象的类型是否正确。与您无法添加到 List&lt;?&gt; 的原因相同,在 SO 上已多次询问和回答。您可以将您的 IComputable&lt;?&gt; 转换为 IComputable&lt;Object&gt;,但这是一个丑陋且类型不安全的 hack。
  • @chrylis-cautiouslyoptimistic- 我已编辑以进一步解释:我得到的“原始类型”警告可以通过使用&lt;?&gt; 对其进行参数化来删除,这会删除类型并创建错误而不是警告。不过,我仍在调查这条路线……
  • @Jesper 确实我理解编译器为什么抱怨它。我已经详细介绍了 Factoy 创建 Computables、擦除参数化...
  • IComputable&lt;? super Object&gt; computable; 怎么样?

标签: java generics java-8 type-conversion wildcard


【解决方案1】:

从通配符类型中获取对象并将其传递回同一对象,这是泛型类型系统的一个已知限制。例如,当你有

List<?> list = …

您可能希望将一个元素从一个索引复制到另一个索引

Object o = list.get(0);
list.set(1, o);

但它不起作用,即使你避免使用不可表示类型的局部变量。换句话说,即使以下内容也无法编译:

list.set(1, list.get(0));

但是您可以添加一个通用的辅助方法来执行该操作,方法是允许在操作期间捕获类型参数中的通配符类型:

static <T> void copyFromTo(List<T> l, int from, int to) {
    l.set(to, l.get(from));
}
List<?> list = …
copyFromTo(list, 0, 1); // now works

您也可以将此模式应用于您的案例:

class ComputableInfo {
    IComputable<?> computable;
    Object config; // Real type is <?>
    int result;

    ComputableInfo(String id) {
        computable = ComputableFactory.createFrom(id);
        configureAndCompute(computable);
    }

    private <T> void configureAndCompute(IComputable<T> computable) {
        T typedConfig = computable.configure();
        this.config = typedConfig;
        this.result = computable.compute(typedConfig);
    }
}

这有效,不需要将ComputableInfo 设为通用。

如果您需要捕获类型的时间超过单个方法,例如如果要多次使用创建的config,可以使用封装:

class ComputableInfo {
    static final class CompState<T> {
        IComputable<T> computable;
        T config;

        CompState(IComputable<T> c) {
            computable = c;
        }
        private void configure() {
            config = computable.configure();
        }
        private int compute() {
            return computable.compute(config);
        }
    }
    CompState<?> state;
    int result;

    ComputableInfo(String id) {
        state = new CompState<>(ComputableFactory.createFrom(id));
        state.configure();
        result = state.compute();
    }
}

这样,您仍然可以避免将类型参数导出给ComputableInfo 的用户。

【讨论】:

  • 我最终使用了 State&lt;T&gt; 类“技巧”:确实,&lt;T&gt; 对 Computable 有含义,我们需要将逻辑移到泛型中。
  • @Holger ... 或使用原始类型,如 Collections::reverse 所做的那样,这很难看,恕我直言。
  • @Eugene Collections::swap 是我的想法;我只是为这个答案简化了它。这两种方法甚至都包含一个注释(在 OpenJDK 版本中),提到“补充私有方法”将是使用原始类型的替代方法。我想,对于在初始化过程中冷代码路径中使用的核心类,避免这样的委托是可以的,但这没什么,应用程序代码应该复制。
【解决方案2】:

您需要使用下限通配符。 Object 本身不符合通配符 ?

class ComputableInfo {
    IComputable<? super Object> computable;
    Object config;
    int result;

    ComputableInfo(String id) {
         computable = null;
         config = computable.configure();
         result = computable.compute(config);
    }
}

一个下限表明IComputable 将是Object 的一个实例或某个对象的一个​​实例是Object 的超类(实际上该对象是所有Objects 的父类)。为了更好的理解,让我们使用而不是Number

IComputable<Integer> computableInteger = ...;
IComputable<Number> computableNumber = ...;
IComputable<Object> computableObject = ...;

IComputable<? super Number> computableSuperNumber = ...;
computableSuperNumber = computableInteger;                  // doesn't compile
computableSuperNumber = computableNumber;                   // ok
computableSuperNumber = computableObject;                   // ok

但是,将IntegerDouble 传递给该实例的方法是安全的。在computableSuperObject 下面的sn-p 中引用了一个IComputable,它可能是以下之一:

  • IComputable&lt;Number&gt;
  • IComputable&lt;Object&gt;

因为引用可能IComputable&lt;Number&gt;,只要Object 可以是ex,只要不适合Object 计算就是非法的。 String.

IComputable<? super Number> computableSuperNumber = ...;

Integer integer = 1;
Double d = 1d;
Number number = 1;
Object object = 1;                     // the Object can be also "string", see below
Object objectString = "string";
String string = "string";

computableSuperNumber.compute(integer);                     // ok
computableSuperNumber.compute(d);                           // ok
computableSuperNumber.compute(number);                      // ok
computableSuperNumber.compute(object);                      // doesn't compile
computableSuperNumber.compute(objectString);                // doesn't compile
computableSuperNumber.compute(string);                      // doesn't compile

【讨论】:

  • 感谢您的解释。但是,在您的第一个示例中,您声明 computableSuperNumber = computableInteger 不能编译,但 computableSuperNumber = computableObject 可以。不是反过来吗?
  • sn-p是正确的(自己编译试试)。下限/上限通配符概念乍一看令人困惑,需要一段时间才能理解。 IComputable&lt;? super Number&gt;IComputable&lt;Number&gt;IComputable&lt;Object&gt;,因此 computableSuperNumber = computableInteger; 无法编译。当您调用带有通配符参数的方法时,您需要确保传递的类型是兼容的,因此传递 IntegerDoubleNumber 的子类型)和 Number 本身是好的,而 StringObject 不是。
  • 好的,谢谢。我需要在它进入之前将它包裹在我的头上一段时间;)
  • 当你有一个IComputable&lt;? super Number&gt; 时,你可以将Number 或它的任何超类型传递给compute 方法,但另一方面,你不能指望得到@987654356 @从configure回来。 IComputable&lt;? super Object&gt; 的构造更奇怪,它确实有效,因为只有一种实际的兼容类型可以实现,IComputable&lt;Object&gt;(或者你知道Object 的其他超类型),所以它基本上与扔掉没有什么不同泛型的好处。可分配的IComputable 实现必须在其compute 方法中接受任何Object...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-07
相关资源
最近更新 更多