Cocoa distinguishes NSArray
from NSMutableArray
, among many other mutable variations on standard types. In Swift, you var
or let
a collection and let the compiler handle the rest of the details, whether the outcome is mutable or not. Kotlin follows the Cocoa model. It uses distinct collection types based on mutability.
For example, the listOf
function returns a new read-only serializable list of elements.
val myList: List<Double> = listOf(1.0, 2.0, 3.0)
You cannot automatically promote typeless literals the way you can in Swift. In Swift the literal “1
” doesn’t have an intrinsic type (although it defaults to Int
in the absence of any other context). “1
” can represent an integer, a double, a float, and more.
Kotlin does not support literal inference. If you use listOf(1, 2, 3)
for the preceding example, Kotlin issues an error: “Type inference failed. Expected type mismatch: inferred type is List<Int> but List<Double> was expected”.
Like Swift, Kotlin’s type inference system permits you to drop the explicit List<Double>
type annotation from the myList
declaration. The listOf
function still produces the expected type, which the compiler assigns to the constant myList
. In this declaration, Kotlin’s val
is the same as Swift’s let
. It produces a constant rather than a (var
) variable.
However, the list you produced is not a value type. Switch the list from immutable to mutable, and you’ll still (like an NSMutableArray
reference) be able to add new members:
val myList: MutableList<Double> = mutableListOf(1.0, 2.0, 3.0) myList.add(4.0) println(myList) // [1.0, 2.0, 3.0, 4.0]
You won’t be able to re-assign myList
to a new list, but as a mutable reference type, the underlying instance can be modified because it is a MutableList<T>
.
Kotlin also offers dictionaries, which it calls maps. That’s really not a bad name for a type that performs one-to-one mapping, which is all that key-value lookup really does. The underlying types honor that key/value relation. They’re called Map<K, out V>
and MutableMap<K, V>
.
val myDictionary = mutableMapOf( "cow" to "moo", "dog" to "woof", "pig" to "oink" ) println(myDictionary["dog"]) // woof println(myDictionary["kitten"]) // null
Like Swift, Kotlin returns a “not there” result for a failed map lookup, but the meaning of null
is distinct from Swift’s nil
. A type must be nullable (sort of but not entirely like Optional
) to store a null reference.
var string = "Hello" println(string) // string = null // error: null can not be a value of a non-null type String var string2: String? = "Hello" println(string2) // hello string2 = null println(string2) // null
The initial value of string2
is not Optional("hello")
or something like that. It feels (at least so far, because I know nothing about Kotlin, see the title of this post) more like the Objective-C nil
, that is a null-reference in memory rather than a pointer to a reference type. I’m new to this, so I’m still learning.
Speaking of null
, the first string
example demonstrates what happens when you assign null
to a non-nullable type. You end up with a (more or less) null pointer exception, which is a bad thing.
Getting back to collections, Kotlin also supports sets, both mutable and immutable. All three types: lists (arrays), maps (dictionaries) and sets (sets), inherit from Kotlin Collection
, and in turn from Kotlin Iterable
. All three types have a way to count the number of elements (or entries), they can be iterated over, and Collection
offers many methods that may or may not apply to each type. For example, you don’t use dropWhile
on a map/dictionary.
The Collections reference page lists which functions apply to which types. It’s a bit hodgepodge compared to Swift’s cleaner, hierarchical, and protocol-based system. That said, all the things you would expect to do to an array, set, or dictionary in Swift, you can do to a list, set, or map in Kotlin. Have a browse through that Collections page to get a sense of the functionality. It’s all pretty familiar.
If anything, the various subtypes (LongArray
, ByteArray
, IntArray
, etc) feel like overkill and the available functions feel a tiny bit bloated. But that’s me looking at this stuff with Swift-ized eyes.
If you like this series of posts, let me know. Drop a tweet, email, or comment if you’d like me to keep going.