Kotlin: Difference between constant in companion object and top level

Kotlin

Kotlin Problem Overview


The general pattern to create constants in Kotlin seems to be using companion objects. However, I can also define a constant at the file level. Why is that not so popular? Am I missing something?

With companion object:

class Example {
    companion object {
        const val CONSTANT = "something"
}

On top level:

const val CONSTANT = "something"

class Example {
}

Kotlin Solutions


Solution 1 - Kotlin

In Java you're forced to put all static field and method declarations in a class and often you even have to create a class just for that purpose. Coming to Kotlin, many users look for the equivalent facility out of habit and end up overusing companion objects.

Kotlin completely decouples the notions of a file and a class. You can declare any number of public classes in the same file. You can also declare private top-level functions and variables and they'll be accessible only to the classes within the same file. This is a great way to organize closely associated code and data.

Compared to top-level declarations, the syntax of companion objects is quite unwieldy. You should use them only when you specifically want to associate some public static code or data with a class and want your users to qualify access to it with the class's name. The use cases for this are quite rare and in most cases the top-level declarations are more natural.

Whenever you have some private static code/data that you want to couple to a class, you'll be better served with private top-level declarations.

Finally, sometimes the concern of the generated bytecode matters. If, for whatever reason, you have to produce a Java class with Kotlin code such that the class has a static member, you must resort to a companion object and a special annotation.

Solution 2 - Kotlin

Differences in usage

Defining the field in a companion object limits the scope it is available in without importing to only that class, which can help keeping the data from being used in unexpected places.

Defining in the file makes the field available to any code in the same package as the field.

Differences in Bytecode

const val CONSTANT = "something"

class Example {
}

Creates the following:

Example.java

public final class Example {}

XKt.java

import kotlin.Metadata;
import org.jetbrains.annotations.NotNull;


public final class XKt {
   public static final String CONSTANT = "something";
}

Whereas:

class Example {
    companion object {
        const val CONSTANT = "something"
    }
}

Creates the following:

public final class Example {
    public static final String CONSTANT = "something";
    public static final Example.Companion Companion = new Example.Companion((DefaultConstructorMarker) null);

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

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

Solution 3 - Kotlin

I think that basically depends on whether you want that constant to be part of a class. If you put it inside a companion object, it will be accessed like this:

Example.CONSTANT

If you choose to put a constant on file level, it will be imported from other files and accessed with simply CONSTANT normally.

There are reasons for putting constants in classes as well as for putting them top-level.

Note that the const keyword can only be applied to variables of type String or primitive types (Int etc.) (reference). For most cases though, there's no need to apply the keyword. Defining constant values as shown in the following works as well:

val constantFIS = FileInputStream("path")

Solution 4 - Kotlin

Sometimes you actually need to put constant outside of companion object. Apparently constants in companion objects are not that “that much” constant as one would suppose. For instance:

internal const val MY_FOO = "It's my ${Foo.FOO}";

open class Foo {
    internal companion object {
        const val FOO = "foo";
    }
}

@Kaboom(name=MY_FOO)
open class Bar {}

Above code is not compiling. As long some “constans” are part of companion objects, they're not really constants. But when you move FOO outside of companion object, everything works.

On the other hand I'd like the compiler to do the work for me and to find out if it is possible to functionally turn some static final field to a constant or not. Why should I put my effort and time to decide what is or is not a literal constant for the compiler? It is just wrong.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMartin TarjányiView Question on Stackoverflow
Solution 1 - KotlinMarko TopolnikView Answer on Stackoverflow
Solution 2 - KotlinjrtapsellView Answer on Stackoverflow
Solution 3 - Kotlins1m0nw1View Answer on Stackoverflow
Solution 4 - KotlinCromaxView Answer on Stackoverflow