The Kotlin Standard Library provides implementations for basic collection types: sets, lists, and maps. A pair of interfaces represent each collection type:
Note that altering a mutable collection doesn't require it to be a var
: write operations modify the same mutable collection object, so the reference doesn't change. Although, if you try to reassign a val
collection, you'll get a compilation error.
The read-only collection types are covariant. This means that, if a Rectangle
class inherits from Shape
, you can use a List<Rectangle>
anywhere the List<Shape>
is required. In other words, the collection types have the same subtyping relationship as the element types. Maps are covariant on the value type, but not on the key type.
In turn, mutable collections aren't covariant; otherwise, this would lead to runtime failures. If MutableList<Rectangle>
was a subtype of MutableList<Shape>
, you could insert other Shape
inheritors (for example, Circle
) into it, thus violating its Rectangle
type argument.
As you see, in some aspects lists are very similar to arrays. However, there is one important difference: an array's size is defined upon initialization and is never changed; in turn, a list doesn't have a predefined size; a list's size can be changed as a result of write operations: adding, updating, or removing elements.
In Kotlin, the default implementation of List
is ArrayList which you can think of as a resizable array.
Set<T> stores unique elements; their order is generally undefined. null
elements are unique as well: a Set
can contain only one null
. Two sets are equal if they have the same size, and for each element of a set there is an equal element in the other set.
MutableSet is a Set
with write operations from MutableCollection
.
The default implementation of Set
– LinkedHashSet – preserves the order of elements insertion. Hence, the functions that rely on the order, such as first()
or last()
, return predictable results on such sets.
An alternative implementation – HashSet – says nothing about the elements order, so calling such functions on it returns unpredictable results. However, HashSet
requires less memory to store the same number of elements.
Map<K, V> is not an inheritor of the Collection
interface; however, it's a Kotlin collection type as well. A Map
stores key-value pairs (or entries); keys are unique, but different keys can be paired with equal values. The Map
interface provides specific functions, such as access to value by key, searching keys and values, and so on.
MutableMap is a Map
with map write operations, for example, you can add a new key-value pair or update the value associated with the given key.
The default implementation of Map
– LinkedHashMap – preserves the order of elements insertion when iterating the map. In turn, an alternative implementation – HashMap – says nothing about the elements order.
The most common way to create a collection is with the standard library functions listOf<T>()
, setOf<T>()
, mutableListOf<T>()
, mutableSetOf<T>()
. If you provide a comma-separated list of collection elements as arguments, the compiler detects the element type automatically. When creating empty collections, specify the type explicitly.
The same is available for maps with the functionsmapOf()
and mutableMapOf()
. The map's keys and values are passed as Pair
objects (usually created with to
infix function).
Note that the to notation creates a short-living Pair
object, so it's recommended that you use it only if performance isn't critical. To avoid excessive memory usage, use alternative ways. For example, you can create a mutable map and populate it using the write operations. The apply() function can help to keep the initialization fluent here.
For lists, there is a constructor that takes the list size and the initializer function that defines the element value based on its index.
List()
& listOf()
To create a concrete type collection, such as an ArrayList
or LinkedList
, you can use the available constructors for these types. Similar constructors are available for implementations of Set
and Map
.
To create a collection with the same elements as an existing collection, you can use copying operations. Collection copying operations from the standard library create shallow copy collections with references to the same elements. Thus, a change made to a collection element reflects in all its copies.
Collection copying functions, such as toList()
, toMutableList()
, toSet()
and others, create a snapshot of a collection at a specific moment. Their result is a new collection of the same elements. If you add or remove elements from the original collection, this won't affect the copies. Copies may be changed independently of the source as well. These functions can also be used for converting collections to other types, for example, build a set from a list or vice versa.
Collection initialization can be used for restricting mutability. For example, if you create a List
reference to a MutableList
, the compiler will produce errors if you try to modify the collection through this reference.
Collections can be created in result of various operations on other collections. For example, filtering a list creates a new list of elements that match the filter:
Mapping produces a list of a transformation results:
Association produces maps:
For traversing collection elements, the Kotlin standard library supports the commonly used mechanism of iterators – objects that provide access to the elements sequentially without exposing the underlying structure of the collection. Iterators are useful when you need to process all the elements of a collection one-by-one, for example, print values or make similar updates to them.
Iterators can be obtained for inheritors of the Iterable<T>
interface, including Set
and List
, by calling the iterator()
function. Once you obtain an iterator, it points to the first element of a collection; calling the next()
function returns this element and moves the iterator position to the following element if it exists.
Note : Once the iterator passes through the last element, it can no longer be used for retrieving elements; neither can it be reset to any previous position. To iterate through the collection again, create a new iterator.
Another way to go through an Iterable
collection is the well-known for
loop. When using for
on a collection, you obtain the iterator implicitly. So, the following code is equivalent to the example above:
Finally, there is a useful forEach()
function that lets you automatically iterate a collection and execute the given code for each element. So, the same example would look like this:
For lists, there is a special iterator implementation: ListIterator. It supports iterating lists in both directions: forwards and backwards. Backward iteration is implemented by the functions hasPrevious()
and previous()
. Additionally, the ListIterator
provides information about the element indices with the functions nextIndex()
and previousIndex()
.
Having the ability to iterate in both directions, means the ListIterator
can still be used after it reaches the last element.
For iterating mutable collections, there is MutableIterator that extends Iterator
with the element removal function remove()
. So, you can remove elements from a collection while iterating it.
In addition to removing elements, the MutableIterator can also insert and replace elements while iterating the list.
Kotlin lets you easily create ranges of values using the rangeTo() function from the kotlin.ranges
package and its operator form ..
. Usually, rangeTo()
is complemented by in
or !in
functions:
Integral type ranges (IntRange, LongRange, CharRange) have an extra feature: they can be iterated over. These ranges are also progressions of the corresponding integral types. Such ranges are generally used for iteration in the for
loops:
To iterate numbers in reverse order, use the downTo
function instead of ..
:
It is also possible to iterate over numbers with an arbitrary step (not necessarily 1). This is done via the step
function:
To iterate a number range which does not include its end element, use the until
function:
A range defines a closed interval in the mathematical sense: it is defined by its two endpoint values which are both included in the range. Ranges are defined for comparable types: having an order, you can define whether an arbitrary instance is in the range between two given instances. The main operation on ranges is contains
, which is usually used in the form of in
and !in
operators.
To create a range for your class, call the rangeTo()
function on the range start value and provide the end value as an argument. rangeTo()
is often called in its operator form ..
.
As shown in the examples above, the ranges of integral types, such as Int
, Long
, and Char
, can be treated as arithmetic progressions of them. In Kotlin, these progressions are defined by special types: IntProgression, LongProgression, and CharProgression.
Progressions have three essential properties: the first
element, the last
element, and a non-zero step
. The first element is first
, subsequent elements are the previous element plus a step
. Iteration over a progression with a positive step is equivalent to an indexed for
loop in Java/JavaScript.
for (int i = first; i <= last; i += step) {
// ...
}
When you create a progression implicitly by iterating a range, this progression's first
and last
elements are the range's endpoints, and the step
is 1.
for (i in 1..10) print(i)
To define a custom progression step, use the step
function on a range.
for (i in 1..8 step 2) print(i)
The last
element of the progressions is calculated this way:
For a positive step: the maximum value not greater than the end value such that:
(last - first) % step == 0
.
For a negative step: the minimum value not less than the end value such that:
(last - first) % step == 0
.
Thus, the last
element is not always the same as the specified end value.
for (i in 1..9 step 3) print(i) // the last element is 7
To create a progression for iterating in reverse order, use downTo
instead of ..
when defining the range for it.
for (i in 4 downTo 1) print(i)
Progressions implement Iterable<N>
, where N
is Int
, Long
, or Char
respectively, so you can use them in various collection functions like map
, filter
, and other.
println( (1..10).filter { it % 2 == 0 } )
A few weeks ago, I decided to explore a streaming service dedicated to nature documentaries. I came across an acclaimed series that focused on the wildlife of the Amazon Rainforest, highlighting its rich biodiversity and the challenges it faces. The website had a beautifully designed interface that reflected the lush and vibrant world of https://kinogo.build/ the rainforest. The video quality was exceptional, with high-definition footage that showcased the stunning landscapes and intricate details of the wildlife. The sound quality was equally impressive, with immersive audio that captured the ambient sounds of the rainforest and the informative narration that guided the viewer through each episode.
Look for casinos offering a diverse range of games that suit your preferences. Whether you enjoy slots, table games, or live dealer games, a good offshore casino should cater to your tastes. Top Offshore Casinos says