Android image linq stl bootstrap后台管理系统 sketch up教程 java三维数组 datetimepicker赋值 ab软启动器 coreldraw入门学习 idea中svn的使用 数据库教程 python类与对象 python中def的用法 python支持中文 python语言编程 python函数参数 python时间戳 java编程实例 java数组扩容 java实例变量 java抛出自定义异常 linux内核编程 bcdautofix python封装 rar去广告 英雄联盟体验服转换器 bbm注册 凤凰刷机 小洛快跑 comsol下载 ios删除描述文件 屏幕录像专家注册机 jquery手册 光标变粗 鬼灵战马 dnf卡邮件 php保留两位小数 刻刀工具 python爬取图片
当前位置: 首页 > 学习教程  > 编程语言

在 Kotlin 的 data class 中使用 MapStruct

2020/8/31 14:34:38 文章标签:

一. data class 的 copy() 为浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

data class 的 copy() 是复制函数,能够复制一个对象的全部属性,也能复制部分的属性。

例如下面的代码:

data class Address(var street:String)

data class User(var name:String,var password:String,var address: Address)

fun main(args: Array<String>) {
    val user1 = User("tony","123456", Address("renming"))

    val user2 = user1.copy()
    println(user2)

    println(user1.address===user2.address) // 判断 data class 的 copy 是否为浅拷贝,如果二者的address指向的内存地址相同则为浅拷贝,反之为深拷贝

    val user3 = user1.copy("monica")
    println(user3)

    val user4 = user1.copy(password = "abcdef")
    println(user4)
}

 执行结果:

User(name=tony, password=123456, address=Address(street=renming))
true
User(name=monica, password=123456, address=Address(street=renming))
User(name=tony, password=abcdef, address=Address(street=renming))

 user1.address===user2.address 打印的结果是 true 表示二者内存地址相同。 如果对象内部有引用类型的变量,通过拷贝后二者指向的是同一地址,表示为浅拷贝。所以 data class 的 copy 为浅拷贝。

 

 

本文接下来要介绍的不是深拷贝,但跟深拷贝会有一些关系,是 Java Bean 到 Java Bean 的之间的映射。这样类似的工具有:Apache 的 BeanUtils、Dozer、MapStruct 等等。

二. MapStruct 简介

MapStruct 是一个基于JSR269的 Java 注释处理器。开发者只需要定义一个 Mapper 接口,该接口声明任何所需的映射方法。在编译期间 MapStruct 将生成此接口的实现类。

使用 MapStruct 可以在两个 Java Bean 之间实现自动映射的功能,只需要创建好接口。由于它是在编译时自动创建具体的实现,因此无需反射等开销,在性能上也会好于 Apache 的 BeanUtils、Dozer 等。

三. Kotlin 中使用 MapStruct

在 github 上找到了一个 MapStruct Kotlin 实现的开源项目:https://github.com/Pozo/mapstruct-kotlin

3.1 mapstruct-kotlin 的安装:

apply plugin: 'kotlin-kapt'

然后在项目中添加如下依赖: 

api("com.github.pozo:mapstruct-kotlin:1.3.1.2")
kapt("com.github.pozo:mapstruct-kotlin-processor:1.3.1.2")

另外,还需要添加如下依赖:

api("org.mapstruct:mapstruct:1.4.0.Beta3")
kapt("org.mapstruct:mapstruct-processor:1.4.0.Beta3")

3.2 mapstruct-kotlin 的基本使用

对于需要使用 MapStruct 的 data class,必须加上一个@KotlinBuilder注解

@KotlinBuilder
data class User(var name:String,var password:String,var address: Address)

@KotlinBuilder
data class UserDto(var name:String,var password:String,var address: Address)

通过添加@KotlinBuilder注解会在编译时生成 UserBuilder、UserDtoBuilder 对象,他们在 Mapper 的实现类中被使用,用于创建对象以及对对象的赋值。

再定义一个 Mapper:

@Mapper
interface UserMapper {

    fun toDto(user: User): UserDto
}

这样,就可以使用了。MapStruct 会在编译时自动生成好 UserMapperImpl 类,完成将 User 对象转换成 UserDto 对象。

fun main() {
    val userMapper = UserMapperImpl()

    val user = User("tony","123456", Address("renming"))

    val userDto = userMapper.toDto(user)

    println("${user.name},${user.address}")
}

 执行结果:

tony,Address(street=renming)

3.3 mapstruct-kotlin 的复杂应用

对于稍微复杂的类:

// domain elements
@KotlinBuilder
data class Role(val id: Int, val name: String, val abbreviation: String?)

@KotlinBuilder
data class Person(val firstName: String, val lastName: String, val age: Int, val role: Role?)

// dto elements
@KotlinBuilder
data class RoleDto(val id: Int, val name: String, val abbreviation: String, val ignoredAttr: Int?)

@KotlinBuilder
data class PersonDto(
    val firstName: String,
    val phone: String?,
    val birthDate: LocalDate?,
    val lastName: String,
    val age: Int,
    val role: RoleDto?
)

Person 类中还包含有 Role 类,以及 Person 跟 PersonDto 的属性并不完全一致的情况。在 Mapper 接口中,支持使用@Mappings来做映射。

@Mapper(uses = [RoleMapper::class])
interface PersonMapper {

    @Mappings(
        value = [
            Mapping(target = "role", ignore = true),
            Mapping(target = "phone", ignore = true),
            Mapping(target = "birthDate", ignore = true),
            Mapping(target = "role.id", source = "role.id"),
            Mapping(target = "role.name", source = "role.name")
        ]
    )
    fun toDto(person: Person): PersonDto

    @Mappings(
        value = [
            Mapping(target = "age", ignore = true),
            Mapping(target = "role.abbreviation", ignore = true)
        ]
    )
    @InheritInverseConfiguration
    fun toPerson(person: PersonDto): Person

}

在 PersonMapper 的 toDto() 中,对于 PersonDto 没有的属性,在 Mapping 时可以使用ignore = true

下面来看看,将 person 映射成 personDto,以及 personDto 再映射回 person。

fun main() {

    val role = Role(1, "role one", "R1")
    val person = Person("Tony", "Shen", 20, role)
    val personMapper = PersonMapperImpl()

    val personDto = personMapper.toDto(person)
    val personFromDto = personMapper.toPerson(personDto)
    println("personDto.firstName=${personDto.firstName}")
    println("personDto.role.id=${personDto.role?.id}")
    println("personDto.phone=${personDto.phone}")
    println("personFromDto.firstName=${personFromDto.firstName}")
    println("personFromDto.age=${personFromDto.age}")
}

执行结果:

personDto.firstName=Tony
personDto.role.id=1
personDto.phone=null
personFromDto.firstName=Tony
personFromDto.age=0

由于 Person 没有 phone 这个属性并且在 Mapping 时忽略了,因此转换成 PersonDto 后personDto.phone=null

而 PersonDto 虽然有 age 属性,但是在 Mapping 时忽略了,因此转换成 Person 后personFromDto.age=0

这样的结果达到了我们的预期。

总结

在使用 Kotlin 的 data class 时,如果需要做 Java Bean 之间的映射,使用 MapStruct 是一个很不错的选择。


本文链接: http://www.dtmao.cc/news_show_150238.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?