简体   繁体   中英

What's the best way to declare a constant in Kotlin?

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 (ie, one copy of the constant per object).

If a constant is static, there will be only one copy of the constant for that class (ie, 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!

BUT If I have 100 or more contacts in one class all static, when they are released?

You're going to write the info of 100 contacts in a source file? That's.. generally not where such data is supposed to go, but okay. 100.

You know what? That's bush league. Let's make it 10,000 contacts, all written out in one gigantic kotlin or java file.

It's still peanuts, memory wise. What's the share of a penny compared to the GDP of the world? Something along those lines. Whenever you load a class file, that's in memory. And is never getting released. And it doesn't matter one iota.

All those constant values are part of the class def and are loaded at least once in that sense no matter what you do.

The correct way is obviously for static data (ie things that are inherent to the class and do not ever change) to be loaded once precisely, instead of 'x times, where x is the amount of instances'. It's semantically correct (by its nature, unchanging, class-global stuff is static , that's what static means), and increments the 'load' of the fact that this class has been touched by a few percentage points (you're just adding references; all those strings are loaded only once and are loaded whether you make this stuff static or not, it's part of the class definition. Where do you imagine those strings go if there are 0 instances? JVM is not going to reload that class file from disk every time you call new Foo() .) - whereas if it's once-per-instance you might be looking at millions of refs for no reason at all.

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:

  1. At the top (file) level :
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.

  1. In a companion object :
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.Aa , and Aa 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.)

  1. In a class :
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.)

  1. In a class, with an explicit getter :
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 thegetter 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.

  1. As enum values :
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.

  1. As values in a structure such as an array or map :
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 (eg 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.

The ability to override makes a different between the two. Take a look at the following example. speed might be a property that is constant across a class, but you might want a different constant value for different subclasses.

import kotlin.reflect.KClass


open class Animal{
    
    companion object{
        val speed : Int = 1
    }
    
    var x: Int = 0
    
    fun move(){
        x += speed
        print("${this} moved to ${x}\n")
    }
}

class Dog : Animal(){
    companion object{
        val speed : Int = 3
    }
}

class Cat : Animal(){
    companion object{
        val speed : Int = 2
    }
}

fun main()
{
    val a = Animal()
    a.move()
    val c = Cat()
    c.move()
    val d = Dog()
    d.move()
}

Output:

Animal@49c2faae moved to 1
Cat@17f052a3 moved to 1
Dog@685f4c2e moved to 1

This doesn't work because speed in move() always refer to Animal.speed . So in this case, you want speed be an instance member instead of static (or companion).

open class Animal{
    
    open val speed : Int = 1
    
    var x: Int = 0
    
    fun move(){
        x += speed
        print("${this} moved to ${x}\n")
    }
}

class Dog : Animal(){
    override val speed : Int = 3
}

class Cat : Animal(){
    override val speed : Int = 2
}

Output:

Animal@37f8bb67 moved to 1
Cat@1d56ce6a moved to 2
Dog@17f052a3 moved to 3

As a general practice, if a value is something that is absolutely independent to individual instances, make it static. In contrast, if a property sounds like a property belongs to the individual instance, not belongs to a type, even it is constant across every instances (for the time being), I would put it as an instance member as it is likely subject to change in future development. Though it is totally fine to make it static until you actually find the needs to change in the future. For the above example, you might even eventually change speed to var instead of val when you later find that every individual dog has different speed. Just do what suit your needs at the moment:)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM