Spring can't determine generic types when lambda expression is used instead of anonymous inner class
我在玩Spring的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Bean public ConversionServiceFactoryBean conversionServiceFactoryBean() { ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean(); Converter<ZonedDateTime, String> dateTimeConverter = new Converter<ZonedDateTime, String>() { @Override public String convert(ZonedDateTime source) { return source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); } }; conversionServiceFactoryBean.setConverters( new HashSet<>(Arrays.asList(dateTimeConverter))); return conversionServiceFactoryBean; } |
这很好。 但是我的IDE(IntelliJ)建议用lambda表达式替换匿名内部类:
1 2 | Converter<ZonedDateTime, String> dateTimeConverter = source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); |
如果这样做,那么它将不再起作用,我会收到有关无法确定泛型类型的错误:
1 2 3 4 5 6 7 | Caused by: java.lang.IllegalArgumentException: Unable to the determine sourceType and targetType < T > which your Converter<S, T> converts between; declare these generic types. at org.springframework.util.Assert.notNull(Assert.java:112) at org.springframework.core.convert.support.GenericConversionService.addConverter(GenericConversionService.java:100) at org.springframework.core.convert.support.ConversionServiceFactory.registerConverters(ConversionServiceFactory.java:50) at org.springframework.context.support.ConversionServiceFactoryBean.afterPropertiesSet(ConversionServiceFactoryBean.java:70) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1627) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1564) |
表示lambda表达式的
我正在使用Spring 4.1.0.RELEASE和Java 8 update 20。
艾伦·斯托克斯(Alan Stokes)在评论中链接的这篇文章很好地解释了这个问题。
基本上,在当前的JDK中,lambda的实际实现被编译到声明类中,并且JVM生成Lambda类,该类的方法是擦除接口中声明的方法。
所以
1 2 | Converter<ZonedDateTime, String> dateTimeConverter = source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); |
产生像
1 2 3 | private static java.lang.String com.example.Test.lambda$0(java.time.ZonedDateTime source) { return source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); } |
由生成的lambda类实例调用。在内部,功能接口方法仅转换为上述方法的参数类型。 JLS州
If the erasure of the type of a method being overridden differs in its
signature from the erasure of the function type ofU , then before
evaluating or executing the lambda body, the method's body checks that
each argument value is an instance of a subclass or subinterface of
the erasure of the corresponding parameter type in the function type
of U; if not, aClassCastException is thrown.
VM本身会产生覆盖方法,该方法与接口中声明的方法完全等效。
关于类型的唯一信息是在上面的
但是,你可以做
1 2 | interface ZonedDateTimeToStringConverter extends Converter<ZonedDateTime, String> { } |
和
1 2 | Converter<ZonedDateTime, String> dateTimeConverter = (ZonedDateTimeToStringConverter) source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); |
要么
1 | ZonedDateTimeToStringConverter dateTimeConverter = source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); |
这迫使lambda声明一个类似
1 |
Spring将能够找到它并解析目标和源类型。
查看TypeTools:
1 2 3 4 5 | Converter<ZonedDateTime, String> converter = source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); Class< ? >[] typeArgs = TypeResolver.resolveRawArguments(Converter.class, converter.getClass()); assert typeArgs[0] == ZonedDateTime.class; assert typeArgs[1] == String.class; |
该方法在Oracle / OpenJDK上有效,因为它使用
注意:我被要求为Spring贡献这项工作,但是我还没有空闲时间(快速浏览一下,与Spring现有的泛型类型解析的东西完美地集成似乎是不平凡的)。