Search code examples
constructorkotlindata-class

Local parameters in Kotlin's primary constructor of a data class


Regarding data classes it is forbidden to not use var or val keywords in the primary constructor, i.e. every parameter is implicitly turned into a class property. However, sometimes there are cases which I don't want each parameter to be turned into a class property.

So, as far as I can see, there is no chance of passing a parameter in a primary constructor that is accessible only within the constructor and is forgotten after the construction of the instance has finished. Is there a good reason for this?

The only way I see to get around this, is not to use data classes or to use a secondary constructor that allows for non-var/val-prefixed variables. However, having a lot of parameters that need to be passed, a secondary constructor would immensely inflate the class. Of course, I could wrap all the parameters into another object, but that would just kind of shift the problem to another place.

Is there a recommended approach or pattern in order to cope with that?


Solution

  • You are not limited at all, you just have to do things a bit differently.

    Data classes are intended to be very clear about what they contain and in what order, and only allow members in the primary constructor parameter list.

    But you have other options: use a secondary constructor, and/or create top-level functions with the same name as the class with different overloads, or create factory methods in the companion object:

    data class Person(val name: String, val age: Int) {
        // secondary constructor
        constructor (name: String): this(name, 0) {
           // ... make a newborn
        }
    
        // factory methods in companion object
        companion object {
            fun of(name: String, birthdate: LocalDate): Person {
                return Person(name, yearsSince(birthdate))
            }
        }
    }
    
    // function with same name as class acting like a constructor
    fun Person(name: String, birthdate: LocalDate): Person {
        return Person(name, yearsSince(birthdate))
    }
    
    // these all work now:
    
    Person("Fred", 30)                                  // primary constructor
    Person("Baby")                                      // secondary constructor
    Person("Geoff", LocalDate.parse("12/08/1990"))      // top-level function
    Person.of("Jennifer", LocalDate.parse("01/01/1981") // companion function
    

    You can also hide the primary constructor by making it private, but you cannot hide the copy version of that constructor.

    By the way, having data classes with this contract for the primary constructor really help serialization/deserialization libraries know what to do with the class that would be guesswork otherwise. It is a good thing!