0

I have a custom date class which has primary constructor like this:

class CustomDate(val day : Int, val month : Int, val year : Int)
{
   init {
        ...some processing...
   }
}

I have a need for secondary constructor that gets a date as string "01.01.2021" and splits these into day, month and year before the primary constructor is called:

constructor(date : String) { ...some processing over date string that gives day, month and year... } 
: this(day, month, year)
{
   ...do stuff...
}

So far I can only think of doing:

constructor(date : String) : this(0, 0, 0) {
   ... do processing on date string and assign new values to fields instead of 0s ...
}

but this looks kinda ugly and would like if there is a way of doing processing before calling the primary constructor, like I have mentioned in the first example of the constructor

1 Answer 1

4

If you need to do any significant processing before calling the primary constructor, it's usually* better to have a factory method, not another constructor.

In Kotlin, a good way is to write it like this:

class CustomDate(val day: Int, val month: Int, val year: Int) {
    companion object {
        operator fun invoke(date: String): CustomDate {
            // Calculate day, month, year…
            return CustomDate(day, month, year)
        }
    }
}

See here for the docs on operator funs and what invoke() means.  But the important thing here is that calling that method looks exactly like calling a constructor!  You can call CustomDate("2021-03-11") in exactly the same way that you can call CustomDate(11, 3, 2021).

This gives you the advantages of a factory method, such as:

  • Can do arbitrary amounts of processing before creating the object.
  • Can avoid creating a new instance, e.g. returning a cached object.
  • Can return a subclass.

But because it looks like a straightforward constructor call, it's easy to find and easy to read.  It's also tidier, because it keeps all the pre-calculations separate from the rest of the class (which can otherwise have some nasty gotchas if you inadvertently access properties before they're initialised).

I've used this idiom quite a bit, and it feels really natural (once you get over the slightly odd operator fun invoke).

Obviously, this is only a good idea for methods that return a CustomDate instance!  And where it's obvious from the parameter types what the method will do with them.  If there's any possibility of confusion — especially if you want multiple factory methods with the same parameter types — then it's better to use a normal named function in the companion object, e.g.:

class CustomDate(val day: Int, val month: Int, val year: Int) {
    companion object {
        fun fromISO(date: String): CustomDate {
            // Calculate day, month, year…
            return CustomDate(day, month, year)
        }
    }
}

That would then be called as CustomDate.fromISO("…").


(* There are ways to do processing before calling the primary constructor, but it's quite limited.  The arguments for the primary constructor can be expressions, and so can call functions.  But if you have multiple parameters, that's likely to become unwieldy and involve some duplicated work.)

Sign up to request clarification or add additional context in comments.

1 Comment

Wow! Amazing! This is exactly what I have been looking for! Thank you for the detailed reply :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.