当前位置: 首页 > 知识库问答 >
问题:

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

公羊晟
2023-03-14

我正在使用Spring的转换服务,添加一个简单的转换器,将ZonedDateTime(Java 8)转换为字符串:

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

我使用的是Spring版本4.1.0。版本和Java 8更新20。

共有2个答案

双元魁
2023-03-14

签出类型工具:

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.ConstantPoolAPI。

注意:我被要求将这项工作贡献给Spring,但我还没有空闲时间来做这件事(快速看一眼,与Spring现有的泛型类型解析功能进行干净的集成似乎很重要)。

阳博赡
2023-03-14

艾伦·斯托克斯评论中的这篇文章很好地解释了这个问题。

基本上,在当前的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中相应参数类型擦除的子类或子接口的实例;如果不是,则抛出一个类CastException。

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

关于类型的唯一信息是在上面的静态方法中。由于该方法是声明类的一部分,因此在给定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将能够找到它并解析目标和源类型。

 类似资料:
  • 我正在从事一个严重依赖泛型类型的项目。其关键组件之一是所谓的TypeToken,它提供了一种在运行时表示泛型类型并在其上应用一些实用函数的方法。为了避免Java的类型擦除,我使用了花括号表示法()来创建一个自动生成的子类,因为这使类型可重新定义。 这是TypeToken的一个非常简化的版本,比最初的实现要宽松得多。然而,我正在使用这种方法,因此我可以确保真正的问题不在于这些效用函数之一。 基本上,

  • 问题内容: 我在Java 8映射操作中传递了一个Function,Intellij告诉我可以将其替换为lambda表达式。但是我不知道如何在不创建中间对象结构的情况下做到这一点。 这是我的工作: 我认为Intellij建议我这样做: 我不知道一种干净的方法来获取在匿名函数中检索到的objectType.getTempUrl()。getFullUrl()部分,有什么建议吗? 问题答案: 你总是可以写

  • 我在JPanel上有一个文本字段,这个文本字段上有一个文档侦听器。由于文档侦听器包含三个方法(removeUpdate,insertUpdate,changeUpdate),所以如何使用Lambda表达式调用特定方法。 如何使用像field.getDocument()这样的Lambda表达式调用此文档侦听器insertUpdate方法。AddDocumentListener(Lambda表达式);

  • 问题内容: 在此问题中用户@Holger提供了一个答案,该答案显示了匿名类的不常见用法,我并不知道。 该答案使用流,但是此问题与流无关,因为这种匿名类型构造可以在其他上下文中使用,即: 令我惊讶的是,它编译并打印了预期的输出。 注意:我很清楚,自古以来,就可以构造一个匿名内部类并按如下方式使用其成员: 但是,这不是我要问的。我的情况有所不同,因为匿名类型是通过方法链传播的。 现在,我可以想象到此功

  • 它不编译。它在lambda表达式中显示错误: “目标方法是泛型的” 好的,当我使用编译它时,它显示了以下错误: