The copy() of the data class is a shallow copy

A shallow copy is a bitwise copy of an object that creates a new object with an exact copy of the original object’s property values. If the property is of a primitive type, the value of the primitive type is copied. If the property is a memory address (reference type), the memory address is copied, so if one object changes the address, the other object will be affected.

Deep copy copies all attributes and dynamically allocated memory to which the attributes point. Deep copy occurs when an object is copied along with the object it references. Deep copy is slower and more expensive than shallow copy.

Copy () of a data class is a copy function that copies all or part of an object’s properties.

For example:

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) // Check whether the copy of the data class is a shallow copy. If the address of the data class is the same as the memory address of the data class, it is a shallow copy; otherwise, it is a deep copy

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

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

Execution Result:

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))
Copy the code

User1. address=== If the value of user2.address is true, the two memory addresses are the same. If an object contains variables of reference type, the object is copied to the same address, indicating a shallow copy. Therefore, the copy of the data class is a shallow copy.

Of course, there are many ways to implement deep copy, such as using serialization deserialization, some open source libraries (e.g. github.com/enbandari/K…

What follows in this article is not deep copy, but has some relevance to deep copy, and is the mapping between Java beans and Java Beans. Similar tools include Apache’s BeanUtils, Dozer, MapStruct, and so on.

Introduction to MapStruct

MapStruct is a Java annotation processor based on JSR 269. The developer only needs to define a Mapper interface that declares any required mapping methods. MapStruct generates an implementation class for this interface during compilation.

MapStruct allows you to automatically map between two Java beans by creating an interface. Because it creates the concrete implementation automatically at compile time, there is no overhead such as reflection, and performance is better than Apache’s BeanUtils, Dozer, and so on.

Kotlin uses MapStruct

There is an open source project for MapStruct Kotlin on Github: github.com/Pozo/mapstr…

3.1 Mapstruct-Kotlin installation:

Add the kapt plugin

apply plugin: 'kotlin-kapt'
Copy the code

Then add the following dependencies to your project:

api("Com. Making. Pozo: mapstruct - kotlin: 1.3.1.2." ")
kapt("Com. Making. Pozo: mapstruct kotlin - processor: 1.3.1.2." ")
Copy the code

In addition, the following dependencies need to be added:

api("Org. Mapstruct: mapstruct: 1.4.0 Beta3." ")
kapt("Org. Mapstruct: mapstruct - processor: 1.4.0. Beta3." ")
Copy the code

3.2 Basic use of mapstruct-kotlin

For data classes that need MapStruct, you must add an @kotlinBuilder annotation

@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)
Copy the code

Adding the @kotlinBuilder annotation generates the UserBuilder, UserDtoBuilder object at compile time, which is used in Mapper’s implementation class to create and assign objects.

Define a Mapper again:

@Mapper
interface UserMapper {

    fun toDto(user: User): UserDto
}
Copy the code

That way, it’s ready to use. MapStruct automatically generates the UserMapperImpl class at compile time, completing the transformation of the User object into a UserDto object.

fun main(a) {
    val userMapper = UserMapperImpl()

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

    val userDto = userMapper.toDto(user)

    println("${user.name}.${user.address}")}Copy the code

Execution Result:

tony,Address(street=renming)
Copy the code

3.3 Complex application of Mapstruct-Kotlin

For slightly more complex classes:

// 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? )Copy the code

The Person class also contains the Role class, and cases where the attributes of Person and PersonDto are not exactly the same. In the Mapper interface, @mappings is supported for mapping.

@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

}
Copy the code

In PersonMapper’s toDto(), ignore = true can be used in Mapping for attributes that PersonDto does not have.

Let’s look at mapping Person to personDto and personDto back to Person.

fun main(a) {

    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}")}Copy the code

Execution Result:

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

PersonDto. Phone =null since Person does not have phone and is ignored in Mapping, PersonDto.

PersonDto has an age attribute, but it is ignored in the Mapping, so personFromdto.age =0 after being converted to Person.

The result met our expectations.

conclusion

When using Kotlin’s Data class, MapStruct is a good choice if you need to do mapping between Java beans.