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

如何实例化继承Java基类的Kotlin实体类并设置基类字段

Kotlin继承Java基类后设置父类字段的解决方案

问题背景

项目中所有实体类需继承定义了identifier、version等公共字段的Java基类BaseEntity,用Kotlin编写的MyEntity继承该基类后,单元测试时无法直接通过构造器传入父类字段,用apply赋值又提示val cannot be reassigned错误。Java类可通过Lombok的@SuperBuilder解决,但不知道Kotlin中如何操作。

相关代码

Java基类BaseEntity:

@Getter
@SuperBuilder(toBuilder = true)
@MappedSuperclass
@EqualsAndHashCode(onlyExplicitlyIncluded = true, doNotUseGetters = true)
@NoArgsConstructor
@AllArgsConstructor
@Audited
@ToString
public class BaseEntity {
    @Id
    @GeneratedValue
    @Column(
        name = "identifier",
        nullable = false,
        columnDefinition = "uuid"
    )
    private UUID identifier;
    @Version
    @Column(
        name = "vrsn",
        nullable = false
    )
    private long version;
    // 其他公共字段
}

Kotlin实体类MyEntity:

@Entity
@Audited
@Table(name = "my_entity")
class MyEntity(
    val identityDocumentNumber: String?,
    val startDate: Instant?,
    val endDate: Instant?,
    @Enumerated(EnumType.STRING)
    val enum: AnEnum,
    val email: String,
) : BaseEntity()

尝试过的错误写法

直接在构造器传入父类字段:

private val MOCK_MY_ENTITY =
MyEntity(
  identifier = MOCK_ANALYST_NAME, // 错误:找不到该参数
  identityDocumentNumber = null,
  startDate = Instant.now(),
  // 其他参数
)

用apply赋值:

private val MOCK_MY_ENTITY =
MyEntity(
  identityDocumentNumber = null,
  startDate = Instant.now(),
  // 其他参数
).apply { 
  identifier = MOCK_ANALYST_UID // 错误:val无法重新赋值
}

Java中可行的写法:

private static final MyEntity MY_ENTITY = MyEntity.builder()
    .identifier(MOCK_IDENTIFIER_UUID)
    .identityDocumentNumber(MOCK_DOCUMENT_NUMBER)
    // 其他参数
    .build();

解决方案

1. 修改Kotlin实体类,显式调用父类全参构造器

利用BaseEntity的@AllArgsConstructor生成的全参构造器,在Kotlin类中显式传入父类字段:

@Entity
@Audited
@Table(name = "my_entity")
class MyEntity(
    val identityDocumentNumber: String?,
    val startDate: Instant?,
    val endDate: Instant?,
    @Enumerated(EnumType.STRING)
    val enum: AnEnum,
    val email: String,
    identifier: UUID,
    version: Long = 0L // 测试时可给版本号默认值
) : BaseEntity(identifier, version, /* 其他父类字段的默认值或必填参数 */)

创建实例时直接传入identifier:

private val MOCK_MY_ENTITY = MyEntity(
    identityDocumentNumber = null,
    startDate = Instant.now(),
    endDate = null,
    enum = AnEnum.SOME_VALUE,
    email = "test@example.com",
    identifier = MOCK_ANALYST_UID
)

2. 兼容Lombok的@SuperBuilder

给Kotlin的MyEntity添加@Builder注解(需确保项目配置了Lombok对Kotlin的支持),然后直接调用Builder:

@Entity
@Audited
@Table(name = "my_entity")
@Builder // 添加Lombok的Builder注解
class MyEntity(
    val identityDocumentNumber: String?,
    val startDate: Instant?,
    val endDate: Instant?,
    @Enumerated(EnumType.STRING)
    val enum: AnEnum,
    val email: String,
) : BaseEntity()

使用Builder构造实例:

private val MOCK_MY_ENTITY = MyEntity.builder()
    .identifier(MOCK_ANALYST_UID)
    .identityDocumentNumber(null)
    .startDate(Instant.now())
    .enum(AnEnum.SOME_VALUE)
    .email("test@example.com")
    .build()

3. 反射赋值(仅测试环境使用)

在单元测试中,通过反射绕过val的限制,直接修改父类字段:

private val MOCK_MY_ENTITY = MyEntity(
    identityDocumentNumber = null,
    startDate = Instant.now(),
    endDate = null,
    enum = AnEnum.SOME_VALUE,
    email = "test@example.com"
).apply {
    val identifierField = BaseEntity::class.java.getDeclaredField("identifier")
    identifierField.isAccessible = true
    identifierField.set(this, MOCK_ANALYST_UID)
    
    // 如需设置version字段同理
    val versionField = BaseEntity::class.java.getDeclaredField("version")
    versionField.isAccessible = true
    versionField.set(this, 1L)
}

⚠️ 注意:此方法仅适合测试环境,生产环境禁止使用,会破坏封装性。

4. 使用Mock框架(Mockito)

如果测试仅需要模拟实体的行为而非真实实例,用Mockito直接Mock:

import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`

private val MOCK_MY_ENTITY = mock(MyEntity::class.java).apply {
    `when`(identifier).thenReturn(MOCK_ANALYST_UID)
    `when`(identityDocumentNumber).thenReturn(null)
    `when`(startDate).thenReturn(Instant.now())
    // 其他字段的模拟
}

这种方式适合不需要完整实体实例的测试场景,避免构造真实对象的繁琐。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 11:22:47