2016-12-15 10 views
4

런타임에 다음 코드가 충돌하지만 Model & SerializableSerializable & 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

유형 경계의 순서가 중요한 경우가 있습니다. 일반 메서드의 메서드 서명 또는 형식 변수를 참조하는 메서드는 바인딩 된 첫 번째 형식에 의해 결정됩니다. 당신이 <T extends Model & Serializable>getModelT 형식 매개 변수를 선언하는 경우

그래서, 그것은 원시 반환 형식이 Model 될 것입니다,하지만 당신은 <T extends Serializable & Model>로 선언 할 때 원시 반환 형식이 Serializable 될 것입니다.

<T extends Object & Serializable & Model>으로 선언하면 원시 반환 유형은 Object이됩니다.

분명히 javacT을 반환하는 람다 식에 대해 작성된 합성 방법에 대해 동일한 전략을 사용합니다. 그러나 대상 유형 Maker<U extends Serializable>의 기능 서명은 () -> U이므로 원시 서명은 () -> Serializable입니다. 따라서 의 T 선언을 사용하여 원시 반환 형식이 Object 또는 Model 인 경우 반환 유형이 Serializable과 호환 될 것으로 기대하는 지정된 대상 형식의 서명과 일치하지 않습니다. 원시 서명이 여기에 어떻게 상호 작용하는지 당신이 interface Maker<U extends Object & Serializable>Maker의 선언을 변경하는 경우

는 원시 기능 서명의 반환 형식이 T의 선언 변종 모두와 호환됩니다 Object이 될 것입니다,,, 설명하기.

그러나 물론 구현 세부 사항입니다. 을 선언하는 방법은이 코드의 정확성에 영향을 주어서는 안되며, 원시 코드가 다르더라도 갑자기 중단해서는 안됩니다. 이것은 컴파일러 버그로 간주 될 수 있습니다. this question에서 볼 수 있듯이 교차 유형의 학대는 더 오래된 전통을 가지고 있습니다. 컴파일러가 대상 유형의 원시 기능 시그니처와 일치하는 합성 메소드에 대해 원시 리턴 유형을 선택하면 문제가 해결됩니다.

+1

그건 의미가 있습니다. 다른 질문이 2 세 이상인 경우 귀하의 답변을 확인합니다. 이런 언어 수준 버그를 수정하는 것이 최우선 순위라고 생각 하겠지만, Java 개발자는 동의하지 않을 수도 있습니다. –

+2

글쎄, 문제가 수정되었다는 오해의 소지가있는 진술이 있었지만 수정 사항은 충분하지 않았습니다. 완전한 수정은'javac'에 대한 더 깊은 변경이 필요하다는 것을 알았을 수도 있습니다 (더 많은 공개 된 이슈가 있습니다), 아마도 일부 개발자들은 수정 후에 수정을 추가하는 것보다 완전한 재 설계가 도움이 될 것이라고 생각하지만, 이것은 빠른 솔루션. 생각하는 것처럼 컴파일러에서 작업하는 개발자가 훨씬 적습니다 (다른 분야의 개발자를 재 할당 할 수는 없으며 컴파일러 전문가가 필요합니다) ... – Holger