Kotlin 集合中的任何与嵌套具体类型

Any vs Nested Concrete type in Kotlin Collections

是否使用 Any 作为集合类型比使用具体类型消耗更少的内存?

假设

val list1 = listOf<Any>("ABC", "DEF", "GHI", "JKL", "MNO")
val list2 = listOf<String>("ABC", "DEF", "GHI", "JKL", "MNO")

我想知道 list1 是否比 list2 消耗更少的内存,因为 String 类型分配内存来存储其属性(例如 size

那么,如果我不使用任何 String 类型的函数,那么使用 list1 会更好吗?


编辑

如果我想在集合中使用其他类型怎么办?

list = listOf<Any>("ABC", 123, 12.34)

效率高吗
list = listOf<String>("ABC", "123", "12.34")



编辑 2
感谢@João Dias 和@gidds

如@gidds 所说:

the list does not directly contain String objects, or Any objects — it contains references.

And a String reference is exactly the same size as an Any reference or a reference of any other type. 

因此,List<String>List<Any> 完全相同,因为 Type Erasure -- 指出@João Dias——编译时和运行时类型不同

但是,这是否意味着

val list1 = listOf<Any>("ABC", "DEF", "GHI")

val list2 = listOf<String>("ABC", "DEF", "GHI")

消耗的内存与

相同
val list3 = listOf<List<List<List<String>>>>(
listOf(listOf(ListOf("ABC"))), 
listOf(listOf(ListOf("DEF"))), 
listOf(listOf(ListOf("GHI")))
)

AFAIKString 基本上是 Char 的集合。 String 包含对 Char 的引用。由于在 Kotlin 中一切都是对象,因此 String 中的每个 Char 都应该包含对堆中值的引用,我到这里是正确的吗 ?

如果是这样的话,List<String>List<Any> 消耗更多的内存没有意义,因为 List<String> 有超过 1 个引用。

它对内存消耗没有任何影响。您正在构建内容完全相同的完全相同的列表。此外,还有一种叫做类型擦除的东西,它是这样的:

Type erasure can be explained as the process of enforcing type constraints at compile-time and discarding the element type information at runtime.

这意味着在运行时,没有 List<String>List<Any> 只是 List 在内存消耗方面使用第一个或第二个没有任何区别。在代码可读性、可维护性和健壮性方面,你绝对应该选择 List<String>。鉴于在 Kotlin 中公开为 List 的列表在默认情况下是只读的(感谢@Alex.T 和@Tenfour04 的提示,列表只能被认为是不可变的如果它们中的元素也是不可变的,如果 List 的具体实现确实是不可变的,例如,考虑一个 list : List<String> 属性 和一个底层可变的 ArrayList<String> 那么 list 仍然不是完全不可变的,因为 since cast 允许您从中添加或删除元素 (list as ArrayList<String>).add("new-element")) 您基本上只有使用 List<Any> 的缺点(因为如果您想迭代或使用它的任何那时你所知道的是 Any 元素比 String).

这样的特定类型更难处理。

目前尚未解决的一点是 列表不直接包含 String 个对象,或 Any 个对象——它包含 references.

并且 String 引用与 Any 引用或任何其他类型的引用的大小完全相同。 (该大小取决于 JVM 的内部 运行 代码;它可能是 4 或 8 个字节。参见 these questions。)

当然,被引用的对象也会在堆中占用自己的space;但这两种情况都是一样的。


编辑添加:

如何实现 ListString 的内部细节与原始问题无关。 (这很好,因为它们在实现之间有所不同。) JVM 语言(例如 Kotlin)只有两种值:原语(IntShortLongByteCharDoubleFloatBoolean)和引用(对象或数组)。

所以任何集合,如果不是基元集合,就是引用集合。这适用于所有 List 实施。因此,您的 list1list2 对象的大小将完全相同,仅取决于它们持有(或可以持有)的引用数量,而不是 in这些引用。

如果您想要更深入的了解,list1 是一个引用,指向一个实现了 List 接口的对象。有许多不同的实现,我不知道 Kotlin 会选择哪一个(同样,这可能会在不同版本之间发生变化),但是举个例子,它是 ArrayList。它至少有两个属性:一个大小(可能是一个 Int),以及一个对数组的引用,该数组包含对列表中项目的引用。 (数组通常会比列表的当前大小大,这样你就可以添加更多的项目而不必每次都重新分配数组;数组的当前大小被称为列表的 capacity.)‖如果这些项是 String,那么确切的内部表示取决于 JVM 版本,但它可能是一个至少具有三个属性的对象:Char 的数组,一个 Int 给出数组中字符串的起始索引,另一个 Int 给出长度。

但正如我所说,细节会随着时间和 JVM 版本的不同而变化。不变的是 List 是引用的集合,引用的大小不依赖于它的类型。因此,String 引用列表(所有其他条件相同)将采用与 Any 引用相同字符串的列表完全相同的 space。

(而且,正如其他地方提到的,由于运行时的类型擦除,JVM 没有类型参数的概念,因此对象实际上是相同的。)

当然,“深度大小”(列表及其包含的对象占用的整个堆space)将取决于那些的大小对象 — 但在我们讨论的情况下,这些是完全相同的 String 对象,因此它们的大小也没有差异。