익명클래스 생성에서 타입추론 컴파일 에러
개발 진행중에 갑자기 알수 없는 컴파일 에러가 발생했습니다. 컴파일 에러가 발생할만한 코드도 없었고 IDE(IntelliJ)도 별다른 문법에러 표기를 하지 않았으므로 의문속에서 살펴보기 시작했습니다.
사용환경
- macOS Mojave 10.14.6
- JDK 11.0.2(Zulu JDK)
- IntelliJ
증상
아래와 같이 컴파일 에러 표시와 메시지가 출력되었습니다.
Information:java: compiler message file broken: key=compiler.misc.msg.bug arguments=11.0.2, {1}, {2}, {3}, {4}, {5}, {6}, {7}
Information:java: java.lang.NullPointerException
Information:java: at jdk.compiler/com.sun.tools.javac.comp.Flow$FlowAnalyzer.visitApply(Flow.java:1235)
Information:java: at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCMethodInvocation.accept(JCTree.java:1634)
Information:java: at jdk.compiler/com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:49)
Information:java: at jdk.compiler/com.sun.tools.javac.comp.Flow$BaseAnalyzer.scan(Flow.java:398)
Information:java: at jdk.compiler/com.sun.tools.javac.comp.Flow$FlowAnalyzer.visitReturn(Flow.java:1210)
Information:java: at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCReturn.accept(JCTree.java:1546)
Information:java: at jdk.compiler/com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:57)
Information:java: at jdk.compiler/com.sun.tools.javac.comp.Flow$FlowAnalyzer.visitBlock(Flow.java:997)
Information:java: at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCBlock.accept(JCTree.java:1020)
Information:java: at jdk.compiler/com.sun.tools.javac.comp.Flow$FlowAnalyzer.visitMethodDef(Flow.java:964)
Information:java: at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCMethodDecl.accept(JCTree.java:866)
Information:java: at jdk.compiler/com.sun.tools.javac.comp.Flow$FlowAnalyzer.visitClassDef(Flow.java:927)
Information:java: at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCClassDecl.accept(JCTree.java:774)
Information:java: at jdk.compiler/com.sun.tools.javac.comp.Flow$FlowAnalyzer.analyzeTree(Flow.java:1327)
Information:java: at jdk.compiler/com.sun.tools.javac.comp.Flow$FlowAnalyzer.analyzeTree(Flow.java:1317)
Information:java: at jdk.compiler/com.sun.tools.javac.comp.Flow.analyzeTree(Flow.java:218)
Information:java: at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1401)
Information:java: at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1375)
Information:java: at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:973)
Information:java: at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.lambda$doCall$0(JavacTaskImpl.java:104)
Information:java: at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.handleExceptions(JavacTaskImpl.java:147)
Information:java: at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:100)
Information:java: at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:94)
일단 별다른 문법에러 표시는 없지만 컴파일시에 에러라고 알려주는 부분을 확인해 보았습니다. 위의 코드에서 ParameterizedTypeReference
를 익명으로 생성하는 부분을 가르키고 있었습니다.
private ResponseEntity<ApiResponse<PaymentDto>> exchangeApi(String targetUrl) {
return this.restTemplate.exchange(
targetUrl,
HttpMethod.GET,
new HttpEntity<>(null, getAuthHeader()),
new ParameterizedTypeReference<>() {
}
);
}
해당 메소드의 시그니처를 확인해보았습니다.
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException
시그니처상으로도 구현된 부분중 문제가 될만한 것은 보이지 않았습니다.
해결
역시 별다른 문제는 보이지 않습니다. 에러메시지를 통해서 유추가 가능한 것은 컴파일러에서 어떤 에러가 발생했고 메시지를 출력하려다가 NullPointerException이 발생했다는 사실입니다. 즉 없는 메시지를 출력할려고 했다는 것이므로 상정하지 않던 에러가 발생했다고 볼 수 있습니다. 일단 자바의 버그가 등록되어있는 JDK Bug System에 접속하여 검색해보았습니다.
이리저리 검색을 하던중 NullPointerException when compile generic ParameterizedTypeReference이라는 이슈를 발견했습니다. 제목만봐도 같은 문제처럼 보입니다. 내용을 확인해보니 역시나 일치하는 것을 알 수 있었습니다. 해당되는 버전(Affects Version/s:
8, 10.0.2, 11.0.1, 12)을 보니 제가 사용해서 문제가 된 11.0.2 뿐만 아니라 상당히 많은 버전에서 문제가 되고 있었습니다.
일단 문제 해결을 위해서 해결된 버전을 확인해 보니 12(Fix Version/s: 12)부터 해결되었다고 되어있었습니다. 제가 사용하는 11버전에서는 아직 해결이 안된 상태로 나와있었습니다.
실망한 상태로 연관 이슈들을 확인해 보던중 한 연관 이슈에서(javac can't tell during speculative attribution if a diamond expression is creating an anonymous inner class or not) 해당 이슈가 11.0.4에서 해결되었다는 것을 발견하고 JDK버전을 11.0.4로 업그레이드하고 문제없이 컴파일 되는것을 확인할 수 있었습니다.
버그의 내용으로 볼때 익명클래스를 생성할때 다이아몬드 오퍼레이터(<>)를 사용한 경우 타입추론이 되지 않는 문제였습니다. 따라서 위의 경우에는 JDK의 버전을 올리지 않고도 new ParameterizedTypeReference<>() {}
의 다이아몬드 오퍼레이터에 타입을 명시하면 간단하게 회피할 수 있습니다.
마치며
별 문제는 아니었지만 컴파일러에도 버그가 있을 수 있다는 사실을 오랜만에 느꼈던 순간이었습니다. 개발중 해결되지 않는 문제를 마주쳤을때 컴파일러 혹은 JVM 자체에도 문제가 있을 수 있다는 사실을 기억해두면 한번씩 도움이 되는 순간이 있지 않을까요?