簡體   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