0

is using Any as collection type consume less memory than using concrete type?

suppose

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

i wonder if list1 consume less memory than list2 since String type allocates memory to store its properties (e.g size)

So, is it better to use list1 if i dont use any String type function?


EDIT

what if i want to use other type in the collection?

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

is it more efficient than

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



EDIT 2
thanks to @João Dias and @gidds

as @gidds says :

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. 

So, The List<String> and List<Any> are the exactly the same because of the Type Erasure --which pointed out by @João Dias-- with a difference of compile-time & runtime type

but, does it mean that

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

and

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

is consuming the same memory as

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

AFAIK, String is basically collections of Char. A String contains a reference to the Char. And since everything is an object in Kotlin, then every Char inside the String should contain a reference to the value in the heap, am i correct up to here?

if thats the case, doesnt it make sense that List<String> consume more memory than List<Any> because List<String> is having more than 1 reference.

Jooo21
  • 110
  • 6
  • 3
    Please be cautious when editing questions. This site works best for specific questions that have a clear answer; it's not suited to discussion. (And you risk invalidating existing answers.) – gidds Dec 04 '21 at 18:02

2 Answers2

5

A point not addressed so far is that 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. (That size depends on the internals of the JVM running the code; it might be 4 or 8 bytes. See these questions.)

Of course, the objects being referred to will also take up their own space in the heap; but that will be the same in both cases.


EDITED TO ADD:

The internal details of how List and String are implemented is irrelevant to the original question. (Which is good, coz they vary between implementations.) JVM languages (such as Kotlin) have only two kinds of value: primitives (Int, Short, Long, Byte, Char, Double, Float, Boolean), and references (to an object or an array).

So any collection, if it's not a collection of primitives, is a collection of references. That applies to all List implementations. So your list1 and list2 objects will be exactly the same size, depending only on the number of references they hold (or can hold), not on what's in those references.

If you want a deeper picture, list1 is a reference, pointing to an object which implements the List interface. There are many different implementations, and I don't know off-hand which one Kotlin will pick (and again, that might change between versions), but say for example it's an ArrayList. That has at least two properties: a size (which is probably an Int), and a reference to an array which holds the references to the items in the list. (The array will usually be bigger than the current size of the list, so that you can add some more items without having to re-allocate the array each time; the current size of the array is known as the list's capacity.) If those items are Strings, then the exact internal representation depends on the JVM version, but it might be an object with at least three properties: an array of Char, an Int giving the start index of the string within the array, and another Int giving either the length.

But as I said, the details change over time and between JVM versions. What doesn't change is that List is a collection of references, and the size of a reference doesn't depend on its type. So a list of String references will (all other things being equal) take exactly the same space as a list of Any references to those same strings.

(And, as has been mentioned elsewhere, due to type erasure at runtime the JVM has no concept of type parameters, and so the objects will in fact be identical.)

Of course, the ‘deep size’ (the overall heap space taken up by the list and the objects it contains) will depend upon the size of those objects — but in the case we're discussing, those are the exact same String objects, so there's no difference in size there either.

gidds
  • 16,558
  • 2
  • 19
  • 26
  • but doesnt a `String` is basically collections of `Char`? So `List>` is consuming the same memory as `List`? Then, what about a lot of nested list, like `List>>>>`. Is it consuming the same memory as `List` ?? – Jooo21 Dec 04 '21 at 16:44
  • Thanks, you answered most of my question. But one more thing, you say `String` object contains at least three properties which take another memory in the heap. Does an `Any` object also contain properties as many as `String` object has? `Any` is the Superclass of every objects in Kotlin. Thus, object of `Any` should contain less property than any other objects. So, in theory `Any` should consume _a bit_ less memory than any other objects, right? – Jooo21 Dec 04 '21 at 19:47
  • 1
    @Jooo21 Yes. Though, as usual, the details vary; many platforms round up objects to the nearest 8 or 16 bytes, so adding a field or two might not take any more memory in practice. (Or it might take more than you expect.) The general theme here is that **it's well worth having a _general_ understanding of how JVMs tend to manage memory — but don't rely on the details because they vary between implementations**: between JVMs (OpenJDK, HotSpot, OpenJ9, GraalVM…), between CPU architectures (even between 32-bit and 64-bit code on the same CPU), and between versions of the same JVM. (contd…) – gidds Dec 04 '21 at 21:31
  • 1
    …(For example, early String implementations on HotSpot/OpenJDK used to share the same array when creating substrings; later ones don't, because in practice the memory lost keeping large arrays in memory unnecessarily tends to outweigh the memory gained from sharing. OTOH, its recent String implementations store their characters as bytes in a string-specific encoding, as many strings can be stored more compactly in e.g. Latin1 than the fairly inefficient UTF-16 of `Char`s. Many versions of the OpenJDK source code are available online if you want to see the gory details…) – gidds Dec 04 '21 at 21:32
4

It does not make any difference in memory consumption. You are building the exact same list with exactly the same content. Additionally, there is a thing called type erasure that goes something like this:

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

This means that at runtime, there is no List<String> or List<Any> just List making no difference whatsoever if you use the first or the second in regards to memory consumption. In regards to code readability, maintainability and robustness you definitely should go with List<String>. Given that in Kotlin lists exposed as List are read-only by default (thanks @Alex.T and @Tenfour04 for the hints, Lists can only be considered immutable if the elements in them are also immutable and if the concrete implementation of List is indeed immutable, e.g., consider a list : List<String> property with an underlying mutable ArrayList<String> then list is still not completely immutable since a since cast allows you to add or remove elements from it (list as ArrayList<String>).add("new-element")) you basically have only disadvantages on using List<Any> (because then if you want to iterate or use any of its elements all you will know at that time is that it is Any element which is way harder to work with than a specific type like String).

João Dias
  • 16,277
  • 6
  • 33
  • 45
  • 2
    1up, but one note: "Lists are immutable", they are `read-only`, not `immutable`. – AlexT Dec 04 '21 at 12:41
  • what if i want to use other type in the collection? i edited my question – Jooo21 Dec 04 '21 at 12:53
  • 1
    That is very true @Alex.T, I abused the word by only thinking about a List of Strings. I will update my answer ;) – João Dias Dec 04 '21 at 12:58
  • 1
    @Jooo21, there is a more important topic when considering `listOf("ABC", 123, 12.34)` versus `listOf("ABC", "123", "12.34")`, which is how you will use this List. Do you really need `123` as an `Int` or is it acceptable to have it as a `String` instead? It really comes down to what exactly is your use case for the list. Think about code readability and maintainability before thinking about performance (which then again will have no difference between them, with the catch that you will have a harder time working with `List` than with `List`). – João Dias Dec 04 '21 at 13:03
  • Lists are only immutable if the class implementing List is immutable. You can have a mutable ArrayList full of immutable objects and publicly exposed as only a List, and it’s still not immutable because the contents of the list can be changed from elsewhere. – Tenfour04 Dec 04 '21 at 15:09
  • Very well pointed out @Tenfour04. Thanks for the hint, I will include the details in my answer ;) – João Dias Dec 04 '21 at 15:29