When we using kotlin there are mainly two ways of declaring constant:
class Foo {
companion object {
private const val a = "A"
}
}
And:
class Foo {
private val a = "A"
}
which one is better?
I searched the companion
way is equivalent to
public static final class Companion {
@NotNull
private static final String a = "A";
}
in Java.
In this question
If a constant is not static, Java will allocate a memory for that constant in every object of the class (i.e., one copy of the constant per object).
If a constant is static, there will be only one copy of the constant for that class (i.e., one copy per class).
Yes that's true.
BUT If I have 100 or more constants in one class all static, when they are released? There are always in memory. They won't be released until the program killed/terminated, right?
So I think the second way
class Foo {
private val a = "A"
}
is the right way. As any instance will be released some point then the memory is released.
Not quite sure I missed something. Any comments? Thanks!
There are many ways to define constants in Kotlin. They differ in their scope and use of namespaces, memory usage, and ability to inherit and override.
There's no single ‘best’ approach; it depends on what your constants represent, and how you're using them.
Here are some (simplified) examples:
const val a = "A"
The declaration is ‘loose’ in a file, not contained in any class. This is usually the simplest and most concise way — but it may not occur to folks who are used to Java, as it has no direct Java equivalent.
The constant is available anywhere in the file (as a bare a
); and if not private, it can also be used anywhere else (either as a fully-qualified list.of.packages.a
, or if that's imported, simply as a
). It can't be inherited or overridden.
class A {
companion object {
const val a = "A"
}
}
If you know Java, this is roughly equivalent to a static field (as the question demonstrates). As with a top-level declaration, there is exactly one instance of the property in memory.
The main difference is that it's now part of A, which affects its scope and accessibility: it's available anywhere within A
and its companion object
, and (unless you restrict it) it can also be used elsewhere (as list.of.packages.A.a
, and A.a
if A
is in scope, and simple a
if the whole thing is imported). (You can't inherit from a singleton such as a companion object, so it can't be inherited or overridden.)
class A {
val a = "A"
}
This differs both in concept and in practice, because every instance of A
has its own property. This means that each instance of A
will take an extra 4 or 8 bytes (or whatever the platform needs to store a reference) — even though they all hold the same reference. (The String object itself is interned.)
If A
or a
are closed (as here), that's is unlikely to make good sense either in terms of the meaning of the code, or its memory usage. (If you only have a few instances, it won't make much difference — but what if you have hundreds of thousands of instances in memory?) However, if A
and a
are both open
, then subclasses can override the value, which can be handy. (However, see below.)
Once again, the property is available anywhere within A
, and (unless restricted) anywhere that can see A
. (Note that the property can't be const
in this case, which means the compiler can't inline uses of it.)
class A {
val a get() = "A"
}
This is conceptually very similar to the previous case: every instance of A
has its own property, which can be overridden in subclasses. And it's accessed in exactly the same way.
However, the implementation is more efficient. This version provides the getter function — and because that makes no reference to a backing field, the compiler doesn't create one. So you get all the benefits of a class property, but without the memory overhead.
enum class A {
A
}
This makes sense only if you have a number of these values which are all examples of some common category; but if you do, then this is usually a much better way to group them together and make them available as named constants.
val letterConstants = mapOf('a' to "A")
This approach makes good sense if you want to look values up programatically, but if you have a lot of values and want to avoid polluting namespaces, it can still make sense even if you only ever access it with constants.
It can also be loaded up (or extended) at runtime (e.g. from a file or database).
(I'm sure there are other approaches, too, that I haven't thought of.)
As I said, it's hard to recommend a particular implementation, because it'll depend upon the problem you're trying to solve: what the constants mean, what they're associated with, and how and where they'll be used.