在Kotlin中声明常量的最佳方法是什么?

5t7ly7z5  于 2022-12-19  发布在  Kotlin
关注(0)|答案(3)|浏览(142)

当我们使用Kotlin时,主要有两种声明常量的方法:

class Foo {
    companion object {
        private const val a = "A"
    }
}

以及:

class Foo {
    private val a = "A"
}

哪个比较好?
我搜索companion的方式相当于

public static final class Companion {
        @NotNull
             private static final String a = "A"; 
    }

在 java 。
在此question
如果一个常量不是静态的,Java将在类的每个对象中为该常量分配一个内存(即每个对象一个常量副本)。
如果一个常量是静态的,那么该类只有一个常量的副本(即每个类一个副本)。
是的,这是真的。

BUT如果我有100个或更多的常量在一个类中都是静态的,什么时候释放它们?内存中总是有,直到程序终止它们才会被释放,对吗?

所以我认为第二种方法

class Foo {
        private val a = "A"
    }

是正确的方式。因为任何示例都会在某个时候被释放,那么内存就会被释放。
不太确定我错过了什么。有什么评论吗?谢谢!

cpjpxq1n

cpjpxq1n1#

但是如果我有100个或更多的接触在一个类都静态,当他们被释放?
你要把100个联系人的信息写在一个源文件里?那是......一般来说,这样的数据不应该去的地方,但是好吧。100个。
你知道吗?那是布什联盟。让我们把它做成10,000个联系人,全部写在一个巨大的Kotlin或java文件中。
内存方面,这仍然是微不足道的。一美分占世界GDP的比例是多少?大概是这样的。每当你加载一个类文件时,它就在内存中。永远不会被释放。这一点都不重要。
所有这些常量值都是class def的一部分,无论你做什么,都至少加载一次。
正确的方法显然适用于静态数据(即类固有的并且永远不会改变的内容)精确地加载一次,而不是“x次,其中x是示例的数量”。这在语义上是正确的(本质上,不变的、类全局的东西是static,这就是static的意思),并将这个类被触及的事实的“负载”增加几个百分点(您只是添加引用;所有这些字符串只被加载一次,并且不管你是否将其设置为静态的,它都是类定义的一部分。如果示例为0,你会想到这些字符串会去哪里?JVM不会在每次调用new Foo()时从磁盘重新加载那个类文件!)--然而,如果它是每个示例一次,你可能会毫无理由地查看数百万个引用。

vlf7wbxs

vlf7wbxs2#

在Kotlin中定义常量的方法有很多种, 它们的作用域和名称空间的使用、内存使用以及继承和重写的能力各不相同。
没有单一的“最佳”方法;这取决于你的常量 * 代表什么,以及你如何使用它们。
以下是一些(简化)示例:
1.在顶层(文件)

const val a = "A"

声明在文件中是“松散的”,不包含在任何类中。 这通常是最简单和最简洁的方式-但习惯于Java的人可能不会想到,因为它没有直接的Java等价物。
常量在文件中的任何位置都可用(作为裸a);如果不是私有的,它也可以用在其他任何地方(或者作为完全限定的list.of.packages.a,或者如果是导入的,就简单地作为a) 。
1.在伴随对象中

class A {
    companion object {
        const val a = "A"
    }
}

如果你了解Java,这大致相当于一个静态字段(如问题所示)。 与顶级声明一样,内存中只有一个属性示例。
主要的区别是它现在是A的一部分,这影响了它的范围和可访问性:它在A及其companion object中的任何地方都可用,并且(除非您限制它)它也可以在其他地方使用(如果A在作用域中,则为list.of.packages.A.aA.a,如果整个对象都导入,则为简单的a)。 (您不能从单个对象(如伴随对象)继承,因此它不能被继承或覆盖。)
1.在课堂上

class A {
    val a = "A"
}

这在概念和实践上都是不同的,因为A的 * 每个示例 * 都有自己的属性, 这意味着A的每个示例将占用额外的4或8个字节(或者平台需要存储引用的任何字节)--即使它们都拥有相同的引用 (String对象本身就是interned)。
如果Aa是封闭的(如这里所示),那么无论是代码的“含义”还是内存使用,都不太可能有什么 意义(如果只有几个示例,这不会有太大区别-但如果内存中有几十万个示例呢?) 如果Aa都是open,则子类可以覆盖该值,这会很方便 (不过,请参阅下文)。
同样,该属性在A中的任何地方都可用,(除非受到限制)在任何可以看到A的地方都可用 (注意,在这种情况下,该属性不能是const,这意味着编译器不能内联使用它)。
1.在类中,使用显式getter

class A {
    val a get() = "A"
}

这在概念上与前面的情况非常相似:A的每个示例都有自己的属性,可以在子类中覆盖, 并且访问方式完全相同。
然而,实现更高效。 这个版本提供了getter函数--因为它没有引用支持字段,所以编译器不会创建一个。 所以你可以得到类属性的所有好处,但没有内存开销。
1.作为枚举值

enum class A {
    A
}

只有当你有很多这样的值,并且这些值都是某个公共类别的例子时,这才有意义;但是如果你这样做了,那么这通常是一个更好的方法来将它们组合在一起,并使它们作为命名常量可用。
1.作为结构(如数组或Map)中的值

val letterConstants = mapOf('a' to "A")

如果您希望通过编程方式查找值,这种方法很有意义,但是如果您有很多值,并且希望避免污染名称空间,即使您只使用常量访问名称空间,这种方法仍然有意义。
它也可以在运行时加载(或扩展)(例如从文件或数据库)。
(我肯定还有其他我没有想到的方法。)
正如我所说的,很难推荐一个特定的实现,因为它取决于您要解决的问题:常量的含义,它们与什么相关联,以及如何和在哪里使用它们。

u91tlkcl

u91tlkcl3#

重写的能力使这两个属性不同。看一下下面的例子。speed可能是一个类中的常量属性,但您可能需要为不同的子类使用不同的常量值。

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()
}

输出:

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

这不起作用,因为move()中的speed总是引用Animal.speed,所以在本例中,您希望speed成为示例成员,而不是static(或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
}

输出:

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

作为一种常规做法,如果一个值是绝对独立于单个示例的,那么就将其设置为静态。相反,如果一个属性听起来像是属于单个示例的属性,而不是属于某个类型的属性,即使它在每个示例中都是常量(暂时),我会把它作为一个示例成员,因为它可能会受到未来发展的变化。尽管在你真正发现将来需要改变之前,让它保持静态是完全可以的。你甚至可能最终把speed改成var而不是val,当你后来发现每只狗都有不同的速度时。现在就做适合你需要的吧:)

相关问题