关于java:使用lambda表达式代替匿名内部类时,Spring无法确定泛型类型

Spring can't determine generic types when lambda expression is used instead of anonymous inner class

我在玩Spring的ConversionService,添加了一个简单的转换器将ZonedDateTime(Java 8)转换为String

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表达式的Class对象显然与Class对于匿名内部类足够不同,Spring无法再确定泛型类型。 Java 8如何使用lambda表达式做到这一点? 这是Spring中的可修复错误吗?或者Java 8是否没有提供必要的信息?

我正在使用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 of U, 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, a ClassCastException is thrown.

VM本身会产生覆盖方法,该方法与接口中声明的方法完全等效。

关于类型的唯一信息是在上面的static方法中。由于此方法是声明类的一部分,因此给定从lambda表达式生成的实例,Spring无法检索它。

但是,你可以做

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
public String convert(ZonedDateTime zdt);

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上有效,因为它使用sun.reflect.ConstantPool API。

注意:我被要求为Spring贡献这项工作,但是我还没有空闲时间(快速浏览一下,与Spring现有的泛型类型解析的东西完美地集成似乎是不平凡的)。