44

I understand in Kotlin const val is used to declare constants and val is for readonly properties. However, I'm wondering in the following case, which one is more suitable to use.

Assume I have a fragment which needs a key to use for saveInstanceState and restoreInstanceState. I'm wondering which one of the following 2 options is better:

Option 1:

class MyFragment {
    private val MY_KEY = "my_key"
    ...
}

Option 2:

private const val MY_KEY = "my_key" // declared in the same file.

class MyFragment {
    ...
}

I'd prefer the #option 2 since it makes it clear that MY_KEY is a constant and the value is determined in compile time. However since it's declared on the top level, it costs a class i.e. MyFragmentKt (assume the file name is MyFragment.kt) to be created in the compiled java code. In #option 1, no extra class is generated and although MY_KEY's value is going to be assigned at runtime and not constant, that makes no difference in how it's used in this specific case.

So although I personally prefer #option 2, my analysis makes me think #option 1 is not worse, if not better. I'm just wondering how other developers think about this and if there are any other benefits of #option 2 that I haven't thought of. Thanks.

H.Nguyen
  • 1,621
  • 5
  • 19
  • 31

2 Answers2

24

Every time you write a (non-inline) lambda expression, you have created another class. Compared to that, creating a single class to hold top-level declarations seems minor.

Furthermore, if all you have at the top level is a constant declaration, it will be inlined into each use site (by specification) so the owning class itself will become unreferenced and therefore targetable by ProGuard's minimization. It will most likely not appear in your production APK.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • `Every time you write a (non-inline) lambda expression, you have created another class` I think you are wrong, there aren't replaced with FunctionN fucntions from jvm.functions package ? – crgarridos Feb 07 '18 at 11:11
  • 4
    I was thinking the opposite all this time, you are right! here is a post explaining it: https://marcin-chwedczuk.github.io/lambda-expressions-in-kotlin – crgarridos Feb 07 '18 at 11:16
  • 2
    Thanks @Marko, the fact that the constant will be inlined and that the generated class will later be excluded by Proguard makes #Option 2 a clear winner here IMO – H.Nguyen Feb 07 '18 at 11:41
17

There is not only a semantic difference between the two options.

Option 1 (val inside the class) is an instance field.

Option 2 (top-level const val) is a top-level "static" member (roughly, since static doesn't exist in Kotlin.)

This is why you have a MyFragmentKt class generated: top-level members are compiled into a class of the name [Filename]Kt.

I would consider a third option:

class MyFragment {
    companion object {
        private const val MY_KEY = "my_key"
    }
}

This way, MY_KEY is (from Java) a static member of the MyFragment class, since JvmStatic is inferred for const variables. There will be a Companion class generated (but it will be empty).

Since your original approach was a field inside the class I feel like the companion object/static constant might be preferable.

More about companion objects vs Java's static

Salem
  • 13,516
  • 4
  • 51
  • 70
  • 2
    While fully correct and legitimate, I'd argue that this is non-idiomatic for Kotlin and that users should just use the great and liberating feature of top-level declarations instead of worrying about very minor or nonexistent effects on the final product. – Marko Topolnik Feb 07 '18 at 10:35
  • 1
    @MarkoTopolnik I'm not so sure it's non-idiomatic, since top-level declarations are placed into the package. Considering that you might have several fragments I wouldn't go with having them at the package level since identifiers must be unique. It also doesn't seem very idiomatic to have a field like this completely decoupled from the class it is related to. – Salem Feb 07 '18 at 10:37
  • 3
    Actually, `private` top-level declarations have different semantics and do not become members of the package, as they aren't accessible outside the file where they are declared. This also means that `private` top-level declarations are naturally coupled to that file. The file in Kotlin is the unit of encapsulation of tightly coupled entities which, as opposed to Java, can consist of more than just one class. – Marko Topolnik Feb 07 '18 at 10:38
  • @MarkoTopolnik you're right, I didn't notice the field was private. Well, I would still prefer the companion object but [I suppose that is just a preference](https://www.reddit.com/r/Kotlin/comments/3wmws0/what_would_be_the_kotlin_idiomatic_way_of/cxxgytk/?st=jdcy0ajh&sh=3a90e590). – Salem Feb 07 '18 at 10:40
  • It's true, companion objects are more natural to those that got accustomed to Java's misuse of the class as a namespacing tool. This created a preference held by many thousands of programmers. – Marko Topolnik Feb 07 '18 at 10:44
  • @MarkoTopolnik I wouldn't call it a misuse, I would say that using the package as a namespace for something this general is actually worse. Of course the field is private, but that doesn't seem very relevant - just because something is possibly in an easier way doesn't mean it must be done this way. With C for example this would of course be done with a global `const` variable but such a field surely "belongs" to the class, does it not? It seems more like a limitation of object-oriented thinking than a limitation of Java et al. – Salem Feb 07 '18 at 10:46
  • 2
    In Kotlin I've switched to the mindset of "belonging to the file" as opposed to just one class. Nothing that can live without a class instance shouldn't really belong to that class, that's just a vestige of the way of thinking Java imposed on us. I often group several tightly coupled classes into a single file and then i can declare on the top level all the things they share. In Java you can't even do this. – Marko Topolnik Feb 07 '18 at 10:54
  • Thanks both @MMoira and @Marko Topolnik for the thoughtful discussion. I was thinking the companion object way results in more compiled Java code than the top-level declaration option does so I didn't consider it. However after some testing I found they seem equivalent. In fact, a companion object results in a `Companion` class generated but if it wraps only the constant, it would be unreferenced and therefore omitted by Proguard like what would happen with top-level constant declaration. – H.Nguyen Feb 07 '18 at 11:39
  • 1
    I also agree that the choice of companion object vs. top-level declaration very much depends on developer's preferences. I personally prefer top-level declaration since I think something is used by a class does not need to belong to that class and the concept of file scoping that allows multiple things declared in a file is actually a huge benefit of Kotlin over Java. – H.Nguyen Feb 07 '18 at 11:39