4

以下代码在运行时崩溃,但如果您更改该行使其Model & Serializable变为Serializable & Model,则它运行正常。谁能解释发生了什么?这是Java中的错误吗?类型边界的顺序似乎并不重要。

import java.io.Serializable;

interface Model {
    void foo();
}

class ModelA implements Model, Serializable {
    public void foo() {

    }
}

class MemcachedHelper<T extends Serializable> {
    T getCached(String key, Maker<T> make) {
        return make.get();
    }
    interface Maker<U extends Serializable> {
        U get();
    }
}

class Query {
    Object getResult() {
        return new ModelA();
    }
}
public class Main {

    // private static <T extends Serializable & Model>
    private static <T extends Model & Serializable>
    T getModel(Class<T> modelClass, MemcachedHelper<T> cache) {
        String key = "key:" + modelClass.getSimpleName();
        T thing = cache.getCached(key, () -> {
            Query q = new Query();
            return (T)q.getResult();
        });
        return thing;
    }

    public static void main(String[] args) {
        MemcachedHelper<ModelA> cache = new MemcachedHelper<>();
        Model thing = getModel(ModelA.class, cache);
        System.out.printf("Got thing: %s\n", thing);
    }

}

运行时错误是:

Exception in thread "main" java.lang.BootstrapMethodError: call site initialization exception
  at java.lang.invoke.CallSite.makeSite(CallSite.java:341)
  at java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:307)
  at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:297)
  at Main.getModel(Main.java:33)
  at Main.main(Main.java:42)
  ...  
Caused by: java.lang.invoke.LambdaConversionException: Type mismatch for lambda expected return: interface Model is not convertible to interface java.io.Serializable
  at java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:286)
  at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:303)
  at java.lang.invoke.CallSite.makeSite(CallSite.java:302)
  ... 9 more

这是 JDK 版本 1.8.0_101。

4

1 回答 1

4

有一个极端情况,类型边界的顺序很重要。泛型方法或引用类型变量的方法的原始方法签名由第一个类型绑定确定。

因此,如果您声明as的T类型参数,它的原始返回类型将为,但是当您将其声明为原始返回类型时,它将是。getModel<T extends Model & Serializable>Model<T extends Serializable & Model>Serializable

如果将其声明为<T extends Object & Serializable & Model>,则其原始返回类型将为Object.

显然,javac为返回的 lambda 表达式创建的合成方法使用相同的策略T。但是,由于目标类型Maker<U extends Serializable>具有功能签名() -> U,它的原始签名是() -> Serializable. 因此,当您使用getModel's的声明T导致原始返回类型为ObjectorModel时,它与期望返回类型兼容的指定目标类型的签名不匹配Serializable

为了说明这里的原始签名如何交互,如果您更改Makerto的声明interface Maker<U extends Object & Serializable>,则其原始功能签名的返回类型将为Object,这将与所有T的声明变体兼容。

但是,当然,这些是实现细节。您声明它的方式不应影响代码的正确性,即使原始代码不同,也不应突然中断。这可以被认为是编译器错误。正如您在这个问题中看到的那样,对交集类型的滥用有着悠久的传统。如果编译器只是为与目标类型的原始功能签名匹配的合成方法选择原始返回类型,问题就会消失。

于 2016-12-15T15:50:14.830 回答