Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Secondary construction syntax kotlin

Tags:

kotlin

I have the following kotlin class with a primary constructor,

class Person(first: String, last: String, age: Int){ 

    init{
        println("Initializing")
    }

}

I'd like to add a secondary constructor that parses a fullname into a first and last name and calls the primary constructor. However, I can't get the syntax right...

class Person(first: String, last: String, age: Int){  

    // Secondary constructor
    constructor(fullname: String, age: Int):
        this("first", "last", age)
        {
            println("In secondary constructor")
        }

    init{
        println("Initializing")
    }
}

This works fine, because I'm not actually parsing fullname in the secondary constructor. When I go ahead and try to parse fullname,

constructor(fullname: String, age: Int):
var first = fullname.split()[0];
...
{
    println("In secondary constructor")
}

I get an unresolved reference: fullname. It doesn't exist in scope, but if I put it in the braces, then I cannot call the primary constructor via this,

constructor(fullname: String, age: Int):
{
    var first = fullname
    this(first, "foo", age)
    println("In secondary constructor")
}

I get an error involving a missing invoke function.

Can't find a good example of this case on Kotlin docs, sorry.

like image 487
Adam Hughes Avatar asked Feb 13 '17 16:02

Adam Hughes


People also ask

How do I make a secondary constructor in Kotlin?

To do so you need to declare a secondary constructor using the constructor keyword. If you want to use some property inside the secondary constructor, then declare the property inside the class and use it in the secondary constructor. By doing so, the declared variable will not be accessed inside the init() block.

Why do we use secondary constructor in Kotlin?

Secondary constructors allow initialization of variables and allow to provide some logic to the class as well. They are prefixed with the constructor keyword.

Can we have multiple constructors in Kotlin?

A Kotlin class can have a primary constructor and one or more additional secondary constructors.


2 Answers

The solution I use when I want a secondary constructor that needs to perform some calculations before passing the results to the primary constructor is to a function on the companion object. The code to do this would look like:

class Person(first: String, last: String, age: Int) {  

    companion object {
        fun fromFullNameAndAge(fullname: String, age: Int) : Person {
          println("In secondary constructor")
          var bits = fullname.split()
          // Additional error checking can (and should) go in here.
          return Person(bits[0],bits[1],age)
        }
    }

    init{
        println("Initializing")
    }
}

You can then use it like this

var p = Person.fromFullNameAndAge("John Doe", 27)

Which is not as neat as Person("John Doe", 27) but is IMO not too bad.

like image 51
Michael Anderson Avatar answered Nov 15 '22 08:11

Michael Anderson


Constructor calls via this must be the first call. This is why it's handled as a delegate, rather than a normal method invocation. This means you cannot declare variables before the call is delegated.

You can solve this by simply inlining whatever values you planned on storing in variables:

constructor(fullName : String, age : int) : this(fullName.split(" ")[0], fullName.split(" ")[1])

But this can potentially index out of bounds if a last name wasn't specified, or if the client decided to use - or some other character as the delimiter. On top of that, it's an eye sore.

Design Analysis

The issue with your structure is giving the Person class the responsibility of determining the first and last name. This deteriorates the reusability of that class, as it'll be limited to one form of parsing. This is why the parsing of names should not be carried out by Person.

Instead, you should expose your primary constructor, then have the client of Person separate the first and last name.

Solution Example

Imagine we were reading names from a file. Each line in the file consists of a full name.

nameFile.forEachLine({ personList.add(Person(it)) })

This is the luxury you are attempting to give your clients: allow them to simply input a name, without worrying about parsing it.

The problem with this is the lack of safety: what if the line only contained a first name? What if the file didn't use whitespace to separate first and last name? You'd be forced to define new Person types just to handle different first/last name combinations.

Instead, the parsing should occur outside of the class:

file.forEachLine({
    val firstName = ...
    val secondName = ...

    personList.add(Person(firstName, secondName))
})

Now that the responsibility has been taken out of Person, we can give the responsibility to a new object if we wanted:

val parser = NameParser(" ") //specify delimiter
file.forEachLine({
    val firstName = parser.extractFirstName(it)
    val lastName = parser.extractLastName(it)

    personList.add(Person(firsrName, lastName))
})
like image 38
Vince Avatar answered Nov 15 '22 08:11

Vince