You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用ModelMapper映射泛型继承字段时触发ClassCastException异常

问题

我有如下类结构(完整代码见文末):

  • abstract class AbstractThing:包含不同事物公共字段的抽象类
  • class Foo extends AbstractThing:继承自AbstractThing的具体实现类Foo
  • abstract class AbstractReq <T extends AbstractThing>:关联事物的请求抽象类,含字段T subject
  • FooReq extends AbstractReq<Foo>:针对Foo的具体请求类
  • FooReqDto:扁平化包含FooReq所有字段的DTO类

尝试用ModelMapper将FooReq实例映射为FooReqDto,无需自定义类型映射时一切正常,但添加自定义映射(比如把subject.fooField映射为FooReqDto的somethingDifferent字段)后,运行测试抛出如下错误:

java.lang.ClassCastException: class com.example.test.TempTest$AbstractThing$ByteBuddy$PsTTXfw5 cannot be cast to class com.example.test.TempTest$Foo (com.example.test.TempTest$AbstractThing$ByteBuddy$PsTTXfw5 and com.example.test.TempTest$Foo are in unnamed module of loader 'app')

创建类型映射时我遗漏了什么?涉及继承和泛型的类映射本身没问题,无自定义映射时可正常运行,且这些映射在提取抽象类前也能正常工作(提取抽象类是为了减少重复代码)。

完整代码:

package com.example.test;

import org.junit.Test;
import org.modelmapper.ModelMapper;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class TempTest {

    @Test
    public void testStuff() {
        final ModelMapper mapper = new ModelMapper();
        mapper.createTypeMap(FooReq.class, FooReqDto.class)
                .addMappings(map -> {
                    map.map(req -> req.getSubject().getFooField(), FooReqDto::setSomethingDifferent);
                });
        final FooReq req = new FooReq();
        req.setSubmitted(true);
        req.getSubject().setName("testFoo");
        req.getSubject().setFooField("bar");
        final FooReqDto dto = mapper.map(req, FooReqDto.class);
        assertTrue(dto.isSubmitted());
        assertEquals("testFoo", dto.getSubjectName());
        assertEquals("bar", dto.getSomethingDifferent());
    }

    public static abstract class AbstractThing {
        private String name;

        // getters and setters omitted
        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    public static class Foo extends AbstractThing {
        private String fooField;

        // getters and setters omitted
        public String getFooField() {
            return fooField;
        }

        public void setFooField(String fooField) {
            this.fooField = fooField;
        }
    }

    public static abstract class AbstractReq <T extends AbstractThing> {
        protected T subject;
        private boolean submitted;

        // getters and setters omitted
        public T getSubject() {
            return subject;
        }

        public void setSubject(T subject) {
            this.subject = subject;
        }

        public boolean isSubmitted() {
            return submitted;
        }

        public void setSubmitted(boolean submitted) {
            this.submitted = submitted;
        }
    }

    public static class FooReq extends AbstractReq<Foo> {
        public FooReq() {
            super();
            subject = new Foo();
        }
    }

    public static class FooReqDto {
        private boolean submitted;
        private String subjectName;
        private String somethingDifferent;

        public FooReqDto() {

        }

        // getters and setters omitted
        public boolean isSubmitted() {
            return submitted;
        }

        public void setSubmitted(boolean submitted) {
            this.submitted = submitted;
        }

        public String getSubjectName() {
            return subjectName;
        }

        public void setSubjectName(String subjectName) {
            this.subjectName = subjectName;
        }

        public String getSomethingDifferent() {
            return somethingDifferent;
        }

        public void setSomethingDifferent(String somethingDifferent) {
            this.somethingDifferent = somethingDifferent;
        }
    }
}
解决方案

这个问题的核心是ModelMapper处理泛型抽象类时,无法正确推断AbstractReq<T>中subject的实际类型,生成了AbstractThing的代理子类(ByteBuddy生成)而非Foo,进而触发类型转换异常。

解决方法1:显式强制转换类型

在自定义映射的Lambda表达式中,明确将subject转换为Foo类型后再访问字段:

@Test
public void testStuff() {
    final ModelMapper mapper = new ModelMapper();
    
    mapper.createTypeMap(FooReq.class, FooReqDto.class)
            .addMappings(map -> {
                // 显式转换为Foo类型,避免类型推断错误
                map.map(req -> ((Foo) req.getSubject()).getFooField(), FooReqDto::setSomethingDifferent);
            });
    
    final FooReq req = new FooReq();
    req.setSubmitted(true);
    req.getSubject().setName("testFoo");
    req.getSubject().setFooField("bar");
    final FooReqDto dto = mapper.map(req, FooReqDto.class);
    assertTrue(dto.isSubmitted());
    assertEquals("testFoo", dto.getSubjectName());
    assertEquals("bar", dto.getSomethingDifferent());
}

解决方法2:配置ModelMapper的泛型解析策略

调整ModelMapper的配置,让它能正确识别泛型的实际类型,无需强制转换即可正常工作:

@Test
public void testStuff() {
    final ModelMapper mapper = new ModelMapper();
    // 配置类型解析策略,启用泛型类型推断
    mapper.getConfiguration()
            .setMatchingStrategy(MatchingStrategies.STANDARD)
            .setFieldMatchingEnabled(true)
            .setFieldAccessLevel(org.modelmapper.config.AccessLevel.PRIVATE);
    
    mapper.createTypeMap(FooReq.class, FooReqDto.class)
            .addMappings(map -> {
                map.map(req -> req.getSubject().getFooField(), FooReqDto::setSomethingDifferent);
            });
    
    final FooReq req = new FooReq();
    req.setSubmitted(true);
    req.getSubject().setName("testFoo");
    req.getSubject().setFooField("bar");
    final FooReqDto dto = mapper.map(req, FooReqDto.class);
    assertTrue(dto.isSubmitted());
    assertEquals("testFoo", dto.getSubjectName());
    assertEquals("bar", dto.getSomethingDifferent());
}

原因说明

添加自定义映射时,ModelMapper默认无法准确解析泛型抽象类AbstractReq<T>中T的实际类型(Foo),会用AbstractThing的代理类处理字段,导致后续类型转换失败。显式指定类型或调整配置让ModelMapper正确识别泛型实际类型,就能解决该问题。

内容的提问来源于stack exchange,提问作者Michelle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 16:03:17