![](/img/trans.png)
[英]Different generic behaviour when using lambda instead of explicit anonymous inner class
[英]Spring can't determine generic types when lambda expression is used instead of anonymous inner class
我正在使用Spring的ConversionService
,添加一个简单的转换器将ZonedDateTime
(Java 8)转换为String
:
@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表达式替换匿名内部类:
Converter<ZonedDateTime, String> dateTimeConverter =
source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
如果我这样做,那么它不再起作用,我得到一个关于Spring无法确定泛型类型的错误:
Caused by: java.lang.IllegalArgumentException: Unable to the determine sourceType <S> 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表达式的Class
对象显然与Spring无法确定泛型类型的匿名内部类的Class
不同。 Java 8如何使用lambda表达式完成这项工作? 这是Spring中的一个可以修复的错误,还是Java 8没有提供必要的信息?
我正在使用Spring版本4.1.0.RELEASE和Java 8更新20。
Alan Stokes在评论中链接的这篇文章很好地解释了这个问题。
基本上,在当前的JDK中,lambda的实际实现被编译到声明类中,JVM生成一个Lambda类,其方法是在接口中声明的方法的擦除。
所以
Converter<ZonedDateTime, String> dateTimeConverter =
source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
产生一种合成方法
private static java.lang.String com.example.Test.lambda$0(java.time.ZonedDateTime source) {
return source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
由生成的lambda类实例调用。 在内部,功能接口方法简单地转换为上述方法的参数类型。 JLS表示
如果被覆盖的方法类型的擦除在其签名上与
U
的函数类型的擦除不同,那么在评估或执行lambda主体之前,该方法的主体检查每个参数值是子类或子接口的实例。 U的函数类型中相应参数类型的擦除; 如果没有,则抛出ClassCastException
。
VM本身生成一个重写方法,它是接口中声明的方法的原始等效方法。
关于类型的唯一信息是上面的static
方法。 由于此方法是声明类的一部分,因此在给定从lambda表达式生成的实例的情况下,Spring无法检索它。
但是,你可以这样做
interface ZonedDateTimeToStringConverter extends Converter<ZonedDateTime, String> {
}
和
Converter<ZonedDateTime, String> dateTimeConverter = (ZonedDateTimeToStringConverter)
source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
要么
ZonedDateTimeToStringConverter dateTimeConverter = source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
这迫使lambda声明一个类似的方法
public String convert(ZonedDateTime zdt);
和Spring将能够找到它并解析目标和源类型。
查看TypeTools :
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;
这种方法适用于甲骨文/ OpenJDKs,因为它使用的sun.reflect.ConstantPool
API。
注意:我被要求将这项工作贡献给Spring,但我还没有空闲时间(快速浏览一下,与Spring现有的泛型类型解决方案完全整合似乎并非易事)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.