How to deal with long init blocks?

I have a problem with very long init block in my class, so i decided to split it into functions.
How it looks now:

class Foo {
  val property1: Int
  val property2: String
  init {
    property1 = 1
    property2 = "two"
  }
}

After changes it should look like this:

class Foo {
  val property1: Int
  val property2: String
  init {
    initProperty1()
    initProperty2()
  }
  fun initProperty1() {
    property1 = 1
  }
  fun initProperty2() {
    property2 = "two"
  }
}

But there is no way to do it.

Val cannot be reassigned
Property must be initialized or be abstract

So what about adding this feature? It can make code much cleaner.

1 Like

The thing is that long initialization logic in kotlin is a code smell anyway. The initialization order is the major pitfall anyway. If you insist on using initialization logic and want to use separate functions for that, just write val property1: Int = initProperty1().

2 Likes

Ok my example wasnt clear enough. I wanted to initialize not only one property in function. I’m getting some resources from file and these resources contain information to initialize couple properties.
Here’s how it’s supposed to work in my code.

class MyClass {
  // foo.json
  val foo1: Int
  val foo2: Int
  // bar.json
  val bar: String

  init {
    initFoo()
  }
  fun initFoo() {
    // parse json
    foo1 = ; // initialization
    foo1 = ; // initialization
  }
  fun initBar() {
    // parse json
    bar = ; // initialization
  }
}

While writing it i’ve thought about something like this:

class MyClass {
  companion object {
    // foo.json
    val foo1: Int = fooDict["foo1"]
    val foo2: Int = fooDict["foo2"]
    // bar.json
    val bar: String = barDict["bar"]

    init {
      val fooDict: HashMap<String, Int> = parseJson("foo.json")
      val barDict: HashMap<String, Int> = parseJson("bar.json")
    }
  }
}

So is there a way to initialize temporary property that will be deleted after initializing?

What I tried to tell you is that doing heavy logic and parsing json in the initializer block is a bad practice in kotlin anyway. The idiomatic way is to create a private constructor that accepts the processed values and move the initialization logic to a factory function.

1 Like

So how do i initialize static properties then? Constructors are not allowed for companion objects. How to deal with properties described in JSONs then? Couple links would be nice, thanks)

1 Like

class MyClassWithData prvate constructor(
  val property1: Int,
  val property2: String
){
  companion object{
    fun from(json: JsonObject): MyClassWithData{
      val p1 = json["property1"].int ?: error("")
      val p2 = json["property2"].string ?: error("") 
      return MyClassWithData(p1, p2)
    }
    fun parse(jsonString: String): MyClassWithData = from(parse(jsonString))
  }
}

Something similar to this.

6 Likes

Yes, factory methods in the companion object is a common pattern in Kotlin.

You can even make them look like a constructor, callable with Classname(…), by defining them as operator fun invoke(…).

That’s not always appropriate. In Java, factory methods are recommended where a method name would add useful extra information, and to avoid having multiple constructors that could get confused (especially if they have the same number of parameters with similar types) — both of which are reasons for explicitly-named factory methods. But if there’s no risk of confusion, and no extra information you can add, then making them look like constructors can give the calling code one less thing to worry about.

However you name them, factory methods are a powerful tool and well worth considering. (In fact, they’re the very first item in all 3 editions of Effective Java!) Other advantages that apply whatever the name are that you can return a subclass and/or a cached object; and they don’t have the restriction on the amount of processing you can do before calling another constructor or a superclass constructor.

Factory methods have downsides, too, of course — the main one being making it awkward to subclass. But if you have control over the superclass, you can usually work around that.

1 Like

Your comment about subclassing is not quite relevant here since we are talking about secondary constructors. If you want to subclass something, you just make the constructor public or protected. In fact, inheritance is exactly the case, where factory methods are better than constructors since you can avoid the confusion about which descendant constructor calls which parent constructor.

1 Like

And if you want a subclass with a constructor or factory method that works like the superclass’s factory method?

The standard approach (having your constructor call a superclass constructor) doesn’t work for factory methods; if you call the superclass’s one, you normally get a superclass instance back.

So you either have to duplicate the entire factory method in your subclass, or you need to modify the superclass somehow so it knows about your subclass and can return an instance of that under the appropriate conditions. And neither cut-and-paste coding nor tight coupling are things to encourage.

However… people shouldn’t let that put them off factory methods, which are otherwise a great idea! :slight_smile:

I kind of agree. I would often want to have a secondary constructor that would call this factory (which is either a free function or companion object function) and forward that to another constructor. Of course it depends on whether the handling of parsing is logically part of the class. If not, there may need to be another class or function that is responsible. For example for loading configuration from a file, if it could be multiple formats, the loading could be separated from the configuration class itself. With the caveat that every added class adds complexity and as long as in all cases you have access to the source (this is not in a library) it is normally better not to add the complexity until you need to (as you can fix at any point you want)

My application has to parse JSONs to work, it made once on startup. I’m making discord bot with spring and MyClass in my examples is basically Bot class, whereas JSONs contain information about token and other stuff for setting up my bot. I wanted to call all this parameters statically without depending on a bean, so i decided to create static class with properties inside Bot class.
This is how it looks now:

@Component
class Bot
@Autowired constructor(
    messageListener: MessageListener,
) {

  init {
    jda.listener(messageListener::onMessageReceived)
  }

  companion object Properties {
    // bot.json
    val token: String
    val name: String
    init {
      val gson = Gson()
      val botProperties: HashMap<String, String> =
        gson.fromJsonAsType(createJsonString("bot.json"))
      try {
        token = botProperties["token"]!!
        name = botProperties["name"]!!
      } catch (exception: NullPointerException) {
        throw PropertyNotFoundException("Please provide correct properties.")
      }
    }
  }
}

Why don’t you use the Spring config facilities and create a config class that Spring uses to create a config object that you can then inject into your Bot class?

In general, I’d keep things like Json parsing out of the classes that are using the parsing result. You can check your architecture, if you think about parts of your application that would change if you would switch to XML. The Bot class shouldn’t change in this case.

1 Like