当我们使用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"
}
是正确的方式。因为任何示例都会在某个时候被释放,那么内存就会被释放。
不太确定我错过了什么。有什么评论吗?谢谢!
3条答案
按热度按时间cpjpxq1n1#
但是如果我有100个或更多的接触在一个类都静态,当他们被释放?
你要把100个联系人的信息写在一个源文件里?那是......一般来说,这样的数据不应该去的地方,但是好吧。100个。
你知道吗?那是布什联盟。让我们把它做成10,000个联系人,全部写在一个巨大的Kotlin或java文件中。
内存方面,这仍然是微不足道的。一美分占世界GDP的比例是多少?大概是这样的。每当你加载一个类文件时,它就在内存中。永远不会被释放。这一点都不重要。
所有这些常量值都是class def的一部分,无论你做什么,都至少加载一次。
正确的方法显然适用于静态数据(即类固有的并且永远不会改变的内容)精确地加载一次,而不是“x次,其中x是示例的数量”。这在语义上是正确的(本质上,不变的、类全局的东西是
static
,这就是static的意思),并将这个类被触及的事实的“负载”增加几个百分点(您只是添加引用;所有这些字符串只被加载一次,并且不管你是否将其设置为静态的,它都是类定义的一部分。如果示例为0,你会想到这些字符串会去哪里?JVM不会在每次调用new Foo()
时从磁盘重新加载那个类文件!)--然而,如果它是每个示例一次,你可能会毫无理由地查看数百万个引用。vlf7wbxs2#
在Kotlin中定义常量的方法有很多种, 它们的作用域和名称空间的使用、内存使用以及继承和重写的能力各不相同。
没有单一的“最佳”方法;这取决于你的常量 * 代表什么,以及你如何使用它们。
以下是一些(简化)示例:
1.在顶层(文件):
声明在文件中是“松散的”,不包含在任何类中。 这通常是最简单和最简洁的方式-但习惯于Java的人可能不会想到,因为它没有直接的Java等价物。
常量在文件中的任何位置都可用(作为裸
a
);如果不是私有的,它也可以用在其他任何地方(或者作为完全限定的list.of.packages.a
,或者如果是导入的,就简单地作为a
) 。1.在伴随对象中:
如果你了解Java,这大致相当于一个静态字段(如问题所示)。 与顶级声明一样,内存中只有一个属性示例。
主要的区别是它现在是A的一部分,这影响了它的范围和可访问性:它在
A
及其companion object
中的任何地方都可用,并且(除非您限制它)它也可以在其他地方使用(如果A
在作用域中,则为list.of.packages.A.a
和A.a
,如果整个对象都导入,则为简单的a
)。 (您不能从单个对象(如伴随对象)继承,因此它不能被继承或覆盖。)1.在课堂上:
这在概念和实践上都是不同的,因为
A
的 * 每个示例 * 都有自己的属性, 这意味着A
的每个示例将占用额外的4或8个字节(或者平台需要存储引用的任何字节)--即使它们都拥有相同的引用 (String对象本身就是interned)。如果
A
或a
是封闭的(如这里所示),那么无论是代码的“含义”还是内存使用,都不太可能有什么 意义(如果只有几个示例,这不会有太大区别-但如果内存中有几十万个示例呢?) 如果A
和a
都是open
,则子类可以覆盖该值,这会很方便 (不过,请参阅下文)。同样,该属性在
A
中的任何地方都可用,(除非受到限制)在任何可以看到A
的地方都可用 (注意,在这种情况下,该属性不能是const
,这意味着编译器不能内联使用它)。1.在类中,使用显式getter:
这在概念上与前面的情况非常相似:
A
的每个示例都有自己的属性,可以在子类中覆盖, 并且访问方式完全相同。然而,实现更高效。 这个版本提供了getter函数--因为它没有引用支持字段,所以编译器不会创建一个。 所以你可以得到类属性的所有好处,但没有内存开销。
1.作为枚举值:
只有当你有很多这样的值,并且这些值都是某个公共类别的例子时,这才有意义;但是如果你这样做了,那么这通常是一个更好的方法来将它们组合在一起,并使它们作为命名常量可用。
1.作为结构(如数组或Map)中的值:
如果您希望通过编程方式查找值,这种方法很有意义,但是如果您有很多值,并且希望避免污染名称空间,即使您只使用常量访问名称空间,这种方法仍然有意义。
它也可以在运行时加载(或扩展)(例如从文件或数据库)。
(我肯定还有其他我没有想到的方法。)
正如我所说的,很难推荐一个特定的实现,因为它取决于您要解决的问题:常量的含义,它们与什么相关联,以及如何和在哪里使用它们。
u91tlkcl3#
重写的能力使这两个属性不同。看一下下面的例子。
speed
可能是一个类中的常量属性,但您可能需要为不同的子类使用不同的常量值。输出:
这不起作用,因为
move()
中的speed
总是引用Animal.speed
,所以在本例中,您希望speed
成为示例成员,而不是static(或companion)。输出:
作为一种常规做法,如果一个值是绝对独立于单个示例的,那么就将其设置为静态。相反,如果一个属性听起来像是属于单个示例的属性,而不是属于某个类型的属性,即使它在每个示例中都是常量(暂时),我会把它作为一个示例成员,因为它可能会受到未来发展的变化。尽管在你真正发现将来需要改变之前,让它保持静态是完全可以的。你甚至可能最终把
speed
改成var
而不是val
,当你后来发现每只狗都有不同的速度时。现在就做适合你需要的吧:)