Best practice - Where to put my class constants

I am interested in what you think is the best practice where to put (private, or otherwise) “static” constants in Kotlin.

In Java, there is one way. In Android for example, one often has a constant for the log tag:

public class ThingDoer {
    private static final String TAG = "Thing";

    public doThing() { Log.i(TAG, "hello"); }
}

In Kotlin, there are three:

  1. companion object as outputted by Java → Kotlin converter

     class ThingDoer {
         fun doThing() { Log.i(TAG, "hello") }
    
         companion object {
             private const val TAG = "Thing"
         }
     }
    
  2. file-private constant

     class ThingDoer {
         fun doThing() { Log.i(TAG, "hello") }
     }
    
     private const val TAG = "Thing"
    
  3. Don’t care about it being “static” (IntelliJ will show a weak warning by default because variables should by convention not be written starting with capital letters)

     class ThingDoer {
         private val TAG = "Thing"
    
         fun doThing() { Log.i(TAG, "hello") }
     }
    
3 Likes

#3 is wrong-ish: that means that you’ll have a copy of your constant in each instance of the class, which might or might not be a problem, but is clearly sub-optimal.

1 and 2 are very similar, it all depends on whether there are other classes or functions in that file.
If there are other classes/functions then decide if those other classes should see/depend on that constant.
If there aren’t other classes/functions in the file it doesn’t really make any difference I can think of.

I am aware of the technicalities. That specifically 1 and 2 are so similar and (I guess) it doesn’t really matter much makes it so hard to choose on one choice for code style guidelines for a project.

So, my question is mostly on the aesthetics, what would you like to read consistently in an project of your own?

1 Like

In my opinion as long as class doesn’t have any functions static fields should be allowed normally. Especially for const val

Coding guidelines don’t need to have an opinion on everything. Whether you use one way or the other is completely invisible to the outside.

It is perfectly sane to have some classes use #1 and some using #2.

The timing of initialization is different, which could be important if it’s expensive. The set of operations that could throw an initialization exception are different, too.

In the case of #3, if this is a val declaration and the right hand side of = is a constant expression (for instance a string literal, like in this example), wouldn’t the JVM be able to optimize this away as a static field?

(I would say the compiler should already be able to do this optimization, but then maybe it would make Java compatibility less obvious, since an instance field from Kotlin world would have to be used as a static field in Java world.)

That said, in the case of string literals, this wouldn’t be a big optimization since the string content itself is stored only one time in memory. The optimization would only save 32 bits per instance (the string reference).

No it can’t. This would break the ABI if you change it from val to var at a later point. That said the JVM might inline this during runtime which would have basically the same effect.

I think some older versions of android don’t do this, but I agree marking something as const is mostly a statement of intent than real functionality. The only real difference in my oppinion is that const can be used as a parameter for annotations.

I was indeed talking about runtime (JVM) optimizations :slight_smile: (to put in the same basket as stuff like JIT or runtime inlining).

Does anyone know why Kotlin does not allow private const val in a class like:

 class ThingDoer {
     private const val TAG = "Thing"

     fun doThing() { Log.i(TAG, "hello") }
 }

To me the JVM bytecode generated by Kotlin for both #1 and #2 isn’t optimal. I find it odd that this relatively common need isn’t resolved nicely / cleanly as I see it.

Any clues why this is the case?

Thanks, Rob.

My guess is that this is a sideeffect by kotlins decision to go with companion objects instead of the “normal” static.

How so? I was under the impression that kotlin will simply inline the use of const val whenever possible. Even if it decides not to #2 is just a static field so I don’t see how it can be improved on a bytecode level.


Also there is a KEEP for a better syntax for stuff like this:

Yes it does that but it also generates a whole other class (to put the static final in) that as I see it we should not really want or need but instead have that in our ThingDoer class - for me I’d rather not have 2 classes generated for this.

1 Like

It is my understanding that the bytecode for #1 would include the extra companion object class being effectively the equivalent of this Java:

public final class ThingDoer {
   @NotNull
   public static final String TAG = "Thing";
   public static final ThingDoer.Companion Companion = new 
ThingDoer.Companion((DefaultConstructorMarker)null);

   public static final class Companion {
      private Companion() {
      }

      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

and #2 would create an external class, looking something like this:

public final class ThingDoerKt {
   @NotNull
   public static final String TAG = "Thing";
}

Meaning that #2 would be preferable from a purely technical perspective as soon as you had multiple constants that would normally be spread across multiple Java classes and you placed them all in a single Constants file instead.

With that said if you’re writing code whose performance would be impacted by having that extra class then chances are you’re not writing Kotlin anyways :laughing: and your team should choose the approach that you consider more readable.

1 Like

With that said if you’re writing code whose performance would be impacted by having that extra class then chances are you’re not writing Kotlin anyways :laughing: and your team should choose the approach that you consider more readable.

Yes, so actually, this is what my question is about mostly. What of the three options you find the most readable and understandable?

I think most people would choose to use #1 or #2. #3 seems wrong since you are not declaring it as const but instead it looks like a normal class property. I would personally prefer #2. It saves you the effort to create the companion and also allows you to use the constant in utility functions or extensions you might define in the same file. That said #1 or #2 is down to personal preference and should be decided on by the team, but it doesn’t really matter.

1 Like

There are a few other cases where you might prefer companion objects.

  • If you want multiple classes in one file, and you want the constant private to a single class.

  • If you want the same constants with different values in multiple related classes. In that case you can have the companion objects implement a common interface, to clearly signal the pattern and help you be consistent in later refactoring.

1 Like