2016-12-15 11 views
4

Следующий код аварийно завершает работу во время выполнения, но если вы измените эту строку так, чтобы Model & Serializable стал Serializable & Model, тогда он работает нормально. Кто-нибудь может объяснить, что происходит? Это ошибка на Java? Не похоже, что порядок ограничений типов должен иметь значение.Изменение порядка двух ограничений параметров типа вызывает ошибку времени выполнения 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.

+2

Я думаю, что это дубликат [этого вопроса] (http://stackoverflow.com/q/27031244/2711488), хотя здесь проблемный тип - это тип возвращаемого типа, а не тип приемника, но в любом случае, неправильное обращение с типами пересечений одинаковое. – Holger

ответ

4

Существует угловой корпус, в котором задан порядок размеров. Сигнатура метода raw общего метода или метода, относящегося к переменным типа, определяется связыванием первого типа.

Так что если вы объявляете параметр T типа из getModel в <T extends Model & Serializable>, это сырье возвращаемого типа будет Model, но когда вы объявляете его как <T extends Serializable & Model> этого сырье возвращаемого типа будет Serializable.

Если вы объявите его <T extends Object & Serializable & Model>, его необработанный тип возврата будет Object.

По-видимому, javac использует ту же стратегию для синтетического метода, созданного для выражения лямбда, возвращающего T. Однако, поскольку целевой тип Maker<U extends Serializable> имеет функциональную подпись () -> U, это необработанная подпись () -> Serializable. Поэтому, когда вы используете объявления 's T, которые приводят к тому, что тип необработанного возврата будет Object или Model, он не соответствует указанной подписи целевого типа, ожидая, что тип возврата будет совместим с Serializable.

Чтобы проиллюстрировать, как сырые подписи взаимодействуют здесь, если вы измените объявление Maker на interface Maker<U extends Object & Serializable>, тип возвращаемого необработанном функционального Signature будет Object, которая будет совместима со всеми вариантами декларации T «s.

Но, конечно, это детали реализации. Способ, которым вы его объявляете , не должен влиять на правильность кода, даже если исходный код отличается, он не должен внезапно ломаться. Это можно считать ошибкой компилятора. Как вы можете видеть в this question, плохое обращение с типами пересечений имеет более давнюю традицию. Проблема исчезнет, ​​если компилятор просто выбирает тип необработанного возврата для синтетического метода, соответствующего исходной функциональной сигнатуре целевого типа.

+1

Это имеет смысл. Я замечаю ваш ответ в том, что другому вопросу больше двух лет. Я думаю, что исправление ошибки на уровне языка, подобной этому, будет иметь высокий приоритет, но, возможно, разработчики Java не согласны. –

+2

Ну, было введенное в заблуждение утверждение о том, что проблема была исправлена, тогда как исправление не было достаточно широким. Возможно, оказалось, что полное исправление нуждается в более глубоких изменениях в 'javac' (есть еще много открытых проблем), поэтому, возможно, некоторые разработчики считают, что полная редизайн поможет больше, чем добавить исправление после исправления, но это исключает возможность быстрое решение. Есть также гораздо меньше разработчиков, работающих над компилятором, как вы думаете (вы не можете просто переназначить разработчиков из других полей на него, вам нужны эксперты компилятора) ... – Holger