告诉编译器一个 <Object> 等同于它想要的 <?>

Tell compiler an <Object> is equivalent to the <?> it wants

我有一些对象会预先生成一些配置,这样它们以后就可以更快地处理计算(可能是几次)。我正在尝试将其通用化以避免将配置作为 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)
    }
}

我遇到编译错误:

The method compute(capture#3-of ?) in the type TestInterface.IComputable<capture#3-of ?> is not applicable for the arguments (Object)

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

我还可以使 ComputableInfo 通用

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

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

有办法实现吗?我什至愿意在编译器设置中将此类问题从错误变为警告,或者可能有一个额外的私有方法可以 return 在单个对象中同时 configresult

编辑:如果我使 ComputableInfo 通用,则添加“进一步的编译问题”:我在界面中有另一个方法(见编辑)通过 ComputableInfo 调用:

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

问题是 ComputableInfo 无法知道 Computable<T>T 类型(或者我不知道),因为它来自建造它的工厂来自配置文件。

您需要使用 lower-bounded 通配符。 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 引用了一个 IComputable,它可能是以下之一:

  • IComputable<Number>
  • IComputable<Object>.

由于引用 可能 IComputable<Number>,所以使用 Object 计算是非法的,只要它不适合 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

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

List<?> list = …

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

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

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

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 的用户。