繁体   English   中英

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

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

当我们使用 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。

在这个问题

如果常量不是 static,则 Java 将在 class 的每个 object 中为该常量分配一个 memory(即每个对象一个常量副本)。

如果常量是 static,则该 class 的常量将只有一个副本(即每个类一个副本)。

是的,这是真的。

但是如果我在一个 class 所有 static 中有 100 个或更多常量,它们什么时候被释放? memory 中总是有。直到程序被杀死/终止,它们才会被释放,对吧?

所以我认为第二种方式

class Foo {
        private val a = "A"
    }

是正确的方法。 由于任何实例都会在某个时候被释放,因此 memory 被释放。

不太确定我错过了什么。 任何意见? 谢谢!

但是如果我在一个 class 所有 static 中有 100 个或更多联系人,他们什么时候被释放?

你要在源文件中写入 100 个联系人的信息? 那是.. 通常不是这样的数据应该是 go 的地方,但是没关系。 100。

你知道吗? 那是丛林联盟。 假设有 10,000 个联系人,全部写在一个巨大的 kotlin 或 java 文件中。

它仍然是花生,memory 明智。 与世界 GDP 相比,一分钱的份额是多少? 沿着这些路线的东西。 每当你加载一个 class 文件时,它就在 memory 中。并且永远不会被释放。 一丁点都没关系。

所有这些常量值都是 class def 的一部分,并且无论您做什么,都至少加载一次。

正确的方法显然是将 static 数据(即 class 固有的东西,永远不会改变)精确加载一次,而不是“x 次,其中 x 是实例的数量”。 它在语义上是正确的(本质上,不变的,类全局的东西是static ,这就是 static 的意思),并且增加了这个 class 已经被几个百分点触及的事实的“负载”(你只是添加引用; 所有这些字符串只加载一次,无论你是否制作这个东西 static 都会加载,它是 class 定义的一部分。如果有 0 个实例,你认为这些字符串 go 在哪里?JVM 不会从重新加载 class 文件每次调用new Foo()时都会访问磁盘。) - 而如果它是每个实例一次,您可能会无缘无故地查看数百万个引用。

Kotlin定义常量的方法有很多种,区别在于它们scope和命名空间的使用,memory的用法,以及继承和覆盖的能力。

没有单一的“最佳”方法; 这取决于您的常量代表什么,以及您如何使用它们。

以下是一些(简化的)示例:

  1. 在顶层(文件)级别
const val a = "A"

声明在文件中是“松散的”,不包含在任何 class 中。这通常是最简单和最简洁的方式——但习惯 Java 的人可能不会想到,因为它没有直接的 Java 等价物。

该常量在文件中的任何位置都可用(作为a裸体); 如果不是私有的,它也可以在其他任何地方使用(作为完全限定的list.of.packages.a ,或者如果它是导入的,则简单地作为a )。 它不能被继承或覆盖。

  1. 在同伴 object 中
class A {
    companion object {
        const val a = "A"
    }
}

如果您知道 Java,这大致相当于 static 字段(如问题所示)。 与顶级声明一样,memory 中只有一个属性实例。

主要区别在于它现在是 A 的一部分,这会影响它的 scope 和可访问性:它在A及其companion object中的任何地方都可用,并且(除非你限制它)它也可以在其他地方使用(如list.of.packages.Aa , 如果A在 scope 中,则为Aa ,如果全部导入,则为简单a )。 (您无法继承 singleton,例如同伴 object,因此无法继承或覆盖。)

  1. 在 class 中
class A {
    val a = "A"
}

这在概念和实践上都有所不同,因为A每个实例都有自己的属性。 这意味着A每个实例都将占用额外的 4 或 8 个字节(或平台需要存储引用的任何内容)——即使它们都持有相同的引用。 (字符串 object 本身是interned 。)

如果Aa是封闭的(如此处),就代码的含义或其 memory 用法而言,这不太可能有意义。 (如果你只有几个实例,它不会有太大区别——但如果你在 memory 中有数十万个实例怎么办?)但是,如果Aa都是open ,那么子类可以覆盖该值,这可以得心应手。 (但是,请参见下文。)

再一次,该属性在A内的任何地方可用,并且(除非受到限制)在任何可以看到A地方可用。 (请注意,在这种情况下属性不能是const ,这意味着编译器不能内联使用它。)

  1. 在 class 中,有一个显式的 getter
class A {
    val a get() = "A"
}

这在概念上与前面的情况非常相似: A每个实例都有自己的属性,可以在子类中重写。 它的访问方式完全相同。

但是,实施效率更高。 这个版本提供了getter function——因为它没有引用支持字段,所以编译器不会创建一个。 因此,您可以获得 class 属性的所有好处,但没有 memory 开销。

  1. 作为枚举值
enum class A {
    A
}

仅当您拥有许多这些值都是某个常见类别的示例时,这才有意义; 但是如果你这样做,那么这通常是将它们组合在一起并使它们作为命名常量可用的更好方法。

  1. 作为数组或 map 等结构中的值
val letterConstants = mapOf('a' to "A")

如果你想以编程方式查找值,这种方法很有意义,但如果你有很多值并且想避免污染命名空间,即使你只使用常量访问它,它仍然有意义。

它也可以在运行时加载(或扩展)(例如从文件或数据库)。

(我敢肯定还有其他我没有想到的方法。)


正如我所说,很难推荐一个特定的实现,因为它取决于您试图解决的问题:常量的含义、它们的关联以及它们的使用方式和位置。

覆盖的能力使两者之间有所不同。 看看下面的例子。 speed可能是一个在 class 中保持不变的属性,但您可能希望对不同的子类使用不同的常量值。

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

这是行不通的,因为move()中的speed总是引用Animal.speed 因此,在这种情况下,您希望speed成为实例成员而不是 static(或伙伴)。

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

作为一般做法,如果一个值是绝对独立于单个实例的东西,则将其设为 static。相反,如果一个属性听起来像是属于单个实例的属性,而不属于某个类型,即使它在每个实例中都是常量instances(暂时),我会把它作为一个实例成员,因为它可能会在未来的发展中发生变化。 尽管将其设置为 static 完全没问题,直到您真正发现将来需要更改为止。 对于上面的示例,当您后来发现每只狗的速度不同时,您甚至可能最终将speed更改为var而不是val 只做适合你当下需要的事:)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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