Michael Pardo
Michael Pardo

Reputation: 2600

Kotlin data class copy function not working when called from java class

Maybe I'm misinterpreting how the copy function of a data class works or maybe there's a bug, but the following is an example of the copy function not working as expected:

Kotlin:

data class A {
    public var x: String? = null
    public var y: String? = null
    public var z: B = B.ONE
}

enum class B {
    ONE
    TWO
    THREE
}

Java

A a1 = new A()
a1.setX("Hello")
a1.setY("World")
a1.setZ(B.TWO)

A a2 = a1.copy()
// a2.x is null
// a2.y is null
// a2.z is B.ONE

It seems that copy is just making a new instance of A and not copying the values. If I put the variables in the constructor, the values are assigned, but then it's no different than constructing a new instance.

Upvotes: 5

Views: 15699

Answers (4)

Dan Tanner
Dan Tanner

Reputation: 2444

This question is high in search rankings, and potentially confusing for those new to kotlin, since the question's sample code is not typical kotlin code or usage of the copy function. I added some sample code below to help clarify what's going on, and also show typical usage of a data class.
In short, the copy function is most useful when called from a kotlin class. I agree that its behavior isn't obvious when called from java code.

//
// A.kt
//

// this is an idiomatic kotlin data class. note the parens around the properties, not braces.
data class A(
    val x: String? = null,
    val y: String? = null,
    val z: B = B.ONE
) {
    // this javaCopy function is completely unnecessary when being called from kotlin; it's only here to show a fairly simple way to make kotlin-java interop a little easier (like what Nokuap showed).
    fun javaCopy(): A {
        return this.copy()
    }
}

enum class B {
    ONE,
    TWO,
    THREE
}

fun main() {
    val a1 = A("Hello", "World", B.TWO)

    // here's what copy usage looks like for idiomatic kotlin code.
    val a2 = a1.copy()
    assert(a2.x == "Hello")
    assert(a2.y == "World")
    assert(a2.z == B.TWO)

    // more typical is to `copy` the object and modify one or more properties during the copy. e.g.:
    val a3 = a1.copy(y = "Friend")
    assert(a2.x == "Hello")
    assert(a3.y == "Friend")
}
public class App {

    public static void main(String[] args) {
        A a1 = new A("Hello", "World", B.TWO);

        // the kotlin copy function is primarily meant for kotlin <-> kotlin interop
        // copy works when called from java, but it requires all the args.
        // calling the `javaCopy` function gives the expected behavior.
        A a2 = a1.javaCopy();
        assert a2.getX().equals("Hello");
        assert a2.getY().equals("World");
        assert a2.getZ().equals(B.TWO);
    }
}

Official docs on data classes, including the copy function:
https://kotlinlang.org/docs/reference/data-classes.html

Upvotes: 0

Nokuap
Nokuap

Reputation: 2379

For interop with java you can make function that use kotlin generated .copy

@Entity
data class User(@PrimaryKey var id: Int = 0,
            var firstName: String? = null,
            var lastName: String? = null,
            var phone: String? = null,
            var email: String? = null,
            var phoneCode: String? = null,
            var tokenId: String? = null,
            var provider: SocialProvider? = null) : Serializable {


var countryCodeIso: String? = null
    set(countryCodeIso) {
        if (countryCodeIso != null) {
            field = countryCodeIso.toLowerCase()
        }
    }

fun javaCopy(): User {
    val user = copy()
    user.countryCodeIso = countryCodeIso
    return user
}}

Upvotes: 1

DimitrisCBR
DimitrisCBR

Reputation: 2553

What you can do to get around the limitations of Kotlin's copy(), is to create your own copy function inside your data class. Example below:

data class User(val name : String, val property: String) {

    fun copy() : User {
      //uses the fields name and property defined in the constructor
      return User(name,property)
    }

    //or if you need a copy with a changed field
    fun copy(changedProperty : String) : User {
      return User(name, changedProperty)
    }

}

Upvotes: 2

Michael Pardo
Michael Pardo

Reputation: 2600

Okay, I missed this sentence in the docs:

If any of these functions is explicitly defined in the class body or inherited from the base types, it will not be generated.

Which, infact, makes copy no better than a constructor for Java interop.

Upvotes: 8

Related Questions