Kotlin From Scratch: Ranges and Collections
2022-4-19 22:51:0 Author: code.tutsplus.com(查看原文) 阅读量:21 收藏

Kotlin is a modern programming language that compiles to Java bytecode. It is free and open source, and promises to make coding for Android even more fun.

In the previous article in this series, you learned about nullability, loops, and conditions in Kotlin. In this tutorial, we'll continue to learn the language by looking at the ranges and collections API in Kotlin.

1. Ranges

A Range in Kotlin is a unique type that defines a start value and an end value. In other words, it is an interval between a start and an end value. Ranges in Kotlin are closed, meaning that the start value and end value are included in the range. 

We'll now look at the different ways of creating ranges in Kotlin.

The .. Operator

val oneToFive = 1..5

In the code above, we have created a closed range. This variable oneToFive will include the following values: 1, 2, 3, 4, 5. We can loop over it using the for loop construct.

for (n in oneToFive) {
    print(n)
}

The code above can be shortened to:

for (n in 1..5) {
    print(n)
}

We can also create a range of characters:

val aToZ = "a".."z"

The variable aToZ will have all the letters in the English alphabet.

The rangeTo() Function

The .. operator can be replaced with the rangeTo() extension function to create a range. For example, we can also do this 1.rangeTo(5) and it would still have the same results as using the .. operator as discussed earlier. 

val oneToFive: IntRange = 1.rangeTo(5)

The downTo() Function

This is another extension function that will create a range starting from a given number down to another one.

val fiveToOne = 5.downTo(1)

We can modify the range using the step() function. This will modify the delta between each element in the range.

val oneToTenStep = 1..10 step 2 // 1, 3, 5, 7, 9

The code above will contain odd numbers between 1 and 10.

The in Operator

The in operator is used to ascertain whether a value is present in a given range.

if (5 in 1..10) {
    print("Yes 5 is in the range") // prints "Yes 5 is in the range"
}

In the code above, we checked to see if 5 is in the range 1..10 using the in operator. We can also do the opposite by using !n to check if 5 is not in the range.

2. Collections

Collections are used to store groups of related objects in memory. In a collection, we can retrieve, store or organize the objects. Kotlin provides its collections API as a standard library built on top of the Java Collections API. (We'll discuss interfaces in Kotlin in a future post.) 

You should note that these interfaces are linked to their implementation at compile time. You can't see the implementation source code in Kotlin, because the collections are actually implemented by the standard Java Collections such as ArrayListMapsHashMapSetsHashSetList and so on. To really understand the collections API in Kotlin, you'll need to be familiar with these basic classes and interfaces in Java.

In this section, we'll learn about the List, Set and Map collections in Kotlin. (If you want a refresher on arrays in Kotlin, kindly visit the first tutorial in this series.) 

Kotlin's collections give us the ability to achieve a lot with just a little code—unlike in Java, which seems to need a lot of code to achieve a little! Kotlin has two variants of collections: mutable and immutable. A mutable collection provides us with the ability to modify a collection by either adding, removing or replacing an element. Immutable collections cannot be modified and don't have these helper methods. 

Note that the addition, removal or replacement of an element in an immutable collection is possible via operator functions (we'll get to that shortly), but these will end up creating a new collection.

The Iterable Interface

The Kotlin Iterable interface is at the top of the collections class hierarchy. This interface enables collections to be represented as a sequence of elements (which can be iterated over, naturally). 

public interface Iterable<out T> {
    public abstract operator fun iterator(): Iterator<T>
}

The Collection Interface

The Kotlin Collection interface extends the Iterable interface. The Collection interface is immutable. In other words, you have read-only access to collections. The Set and List interfaces (more about these shortly) in Kotlin extend this interface. 

Some of the functions and properties available in the Collection interface are:

  • size: this property returns the size of the collection.
  • isEmpty(): returns true if the collection is empty or false otherwise. 
  • contains(element: E): returns true if the element specified in the argument is present in the collection.
  • containsAll(element: Collection<E>): returns true if the element in the collection passed as argument is present in the collection.  
public interface Collection<out E> : Iterable<E> {
    public val size: Int
    public fun isEmpty(): Boolean
    public operator fun contains(element: @UnsafeVariance E): Boolean
    override fun iterator(): Iterator<E>
    public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
}

The MutableIterable Interface

This interface in Kotlin gives us a specialized mutable iterator from the parent Iterable interface.

public interface MutableIterable<out T> : Iterable<T> {
    override fun iterator(): MutableIterator<T>
}

The MutableCollection Interface

The MutableCollection interface in Kotlin is a specialized interface that enables collections to be mutable. In other words, add and remove operations can be performed on a given collection. This interface extends both the Collection interface and the MutableIterable interface already discussed above. The MutableSet and MutableList interfaces (we'll get to them shortly) in Kotlin extend this interface. The functions available in this interface—apart from the ones available in its parents—are:

  • add(element: E): adds the element passed as an argument to the collection and returns true if successful or false if the collection does not support duplicates and the element is already present.
  • remove(element: E): removes the element passed as an argument from the collection. Returns true if successful or false if it was not present in the collection.
  • addAll(elements: Collection<E>): adds all the elements in the collection passed as arguments to the collection. Returns true if successful or false if nothing was added.
  • removeAll(elements: Collection<E>): removes all of the elements present in the collection passed as arguments. Returns true if successful or false if nothing was removed.
  • retainAll(elements: Collection<E>): retains only the elements present in the collections passed as arguments. Returns true if successful or false if nothing was retained. 
  • clear(): removes all elements from this collection.
public interface MutableCollection<E> : Collection<E>, MutableIterable<E> {
    override fun iterator(): MutableIterator<E>
    public fun add(element: E): Boolean
    public fun remove(element: E): Boolean
    public fun addAll(elements: Collection<E>): Boolean
    public fun removeAll(elements: Collection<E>): Boolean
    public fun retainAll(elements: Collection<E>): Boolean
    public fun clear(): Unit
}

Now you have learned about the top interfaces in the collection class hierarchy in Kotlin, let's now look into how Kotlin handles collections such as Lists, Sets and Maps in the remaining part of the tutorial.

Lists

A list is an ordered collection of elements. This is a popular collection that is widely used. Let's look at different ways of creating a list in Kotlin.

Using the listOf() Function

In Kotlin, we can create an immutable (read-only) list using the listOf() helper function from the Kotlin standard library. This function returns a Kotlin List interface type.

var numbers: List<Int> = listOf(1, 2, 3, 4, 5)
var names: List<String> = listOf("Chike", "Nnamdi", "Mgbemena")
for (name in names) {
    println(name)
}

Running the code above will print: 

Chike
Nnamdi
Mgbemena

Moreover, we can pass values of different types into the listOf() as arguments and the result will still work—it will be a list of mixed type. 

var listMixedTypes = listOf("Chike", 1, 2.445, 's') // will still compile

Using the emptyList() Function

This function just creates an empty immutable list and returns a Kotlin List interface type.

val emptyList: List<String> = emptyList<String>()

Using the listOfNotNull() Function

This function creates a new immutable list containing only elements that are not null. Notice that this function returns a Kotlin List interface type also.

val nonNullsList: List<String> = listOfNotNull(2, 45, 2, null, 5, null)

The List interface from the Kotlin standard library extends only the Collection interface. In other words, its only parent is the Collection interface. It overrides all the functions in the parent interface to cater for its special needs and also defines its own functions, such as:

  • get(index: Int): a function operator that returns the element at the specified index. 
  • indexOf(element: E): returns the index of the first occurrence of the element passed as an argument in the list, or -1 if none is found.
  • lastIndexOf(element: E): returns the index of the last occurrence of the element passed as an argument in the list, or -1 if none is found. 
  • listIterator(): returns a list iterator over the elements in the list.
  • subList(fromIndex: Int, toIndex: Int): returns a list that contains the portion of the list between the specified start and end indices. 
println(names.size) // 3
println(names.get(0)) // "Chike"
println(names.indexOf("Mgbemena")) // 2
println(names.contains("Nnamdi")) //  'true'

Using the arrayListOf() Function

This creates a mutable list and returns a Java ArrayList type.

val stringList: ArrayList<String> = arrayListOf<String>("Hello", "You", "There")

Using the mutableListOf() Function

To add, remove or replace values in a list, we need to make the list a mutable one. We can convert an immutable list to a mutable one by calling the function toMutableList() on the list. However, note that this method will create a new list.

var mutableNames1 = names.toMutableList()
mutableNames1.add("Ruth") // now mutable and added "Ruth" to list

To create a mutable list of a certain type from scratch, e.g. String, we use mutableListOf<String>(), while for mixed types we can just use the mutableListOf() function instead.

// a mutable list of a certain type e.g. String
val mutableListNames: MutableList<String> = mutableListOf<String>("Josh", "Kene", "Sanya")
mutableListNames.add("Mary")
mutableListNames.removeAt(1) 
mutableListNames[0] = "Oluchi" // replaces the element in index 0 with "Oluchi"

// a mutable list of mixed types
val mutableListMixed = mutableListOf("BMW", "Toyota", 1, 6.76, 'v')

Any of these functions will return a MutableList Kotlin interface type. This interface extends both the MutableCollection and List interfaces discussed earlier in this section. The MutableList interface adds methods for the retrieval or substitution of an item based upon its position: 

  • set(index: Int, element: E): substitutes an element in the list with another element. This returns the element previously at the specified position.
  • add(index: Int, element: E): inserts an element at the specified index. 
  • removeAt(index: Int): gets rid of the element at a particular index. 
val mutableListFood: MutableList<String> = mutableListOf<String>("Rice & stew", "Jollof rice", "Eba & Egusi", "Fried rice")
mutableListFood.remove("Fried rice")
mutableListFood.removeAt(0)
mutableListFood.set(0, "Beans")
mutableListFood.add(1, "Bread & tea")

for (foodName in mutableListFood) {
    println(foodName)
}

Running the code above, we produce the following result:

Beans
Bread & tea
Eba & Egusi

Note that all these functions create a Java ArrayList behind the scenes.

Sets

A set is an unordered collection of unique elements. In other words, it can't have any duplicates! Let's look at some of the different ways of creating a set in Kotlin. Each of these creates a different data structure, each of which is optimized for a certain kind of task. 

Using the setOf() Function

To create an immutable (read-only) set in Kotlin, we can use the function setOf(), which returns a Kotlin Set interface type. 

// creates a immutable set of mixed types
val mixedTypesSet = setOf(2, 4.454, "how", "far", 'c') // will compile

var intSet: Set<Int> = setOf(1, 3, 4)  // only integers types allowed

Note that the Kotlin Set interface extends only the Kotlin Collection interface and overrides all the properties available in its parent.

Using the hashSetOf() Function 

Using the hashSetOf() function creates a Java HashSet collection which stores elements in a hash table. Because this function returns a Java HashSet type, we can add, remove, or clear elements in the set. In other words, it's mutable. 

val intsHashSet: java.util.HashSet<Int> = hashSetOf(1, 2, 6, 3)
intsHashSet.add(5)
intsHashSet.remove(1)

Using the sortedSetOf() Function

Using the sortedSetOf() function creates a Java TreeSet collection behind the scenes, which orders elements based on their natural ordering or by a comparator. This set is also mutable.

val intsSortedSet: java.util.TreeSet<Int>  = sortedSetOf(4, 1, 7, 2)
intsSortedSet.add(6)
intsSortedSet.remove(1)
intsSortedSet.clear()

Using the linkedSetOf() Function

This function returns a Java LinkedHashSet type. This mutable set maintains a linked list of the entries in the set, in the order in which they were inserted. 

val intsLinkedHashSet: java.util.LinkedHashSet<Int> = linkedSetOf(5, 2, 7, 2, 5) // 5, 2, 7
intsLinkedHashSet.add(4)
intsLinkedHashSet.remove(2)
intsLinkedHashSet.clear()

Using the mutableSetOf() Function

We can use mutableSetOf() to create a mutable set. This function returns a Kotlin MutableSet interface type. Behind the scenes, this function simply creates a Java LinkedHashSet.  

// creates a mutable set of int types only
val intsMutableSet: MutableSet<Int> = mutableSetOf(3, 5, 6, 2, 0)
intsMutableSet.add(8) 
intsMutableSet.remove(3)

The MutableSet interface extends both the MutableCollection and the Set interfaces. 

Maps

Maps associate keys to values. The keys must be unique, but the associated values don't need to be. That way, each key can be used to uniquely identify the associated value, since the map makes sure that you can't have duplicate keys in the collection. Behind the scenes, Kotlin uses the Java Map collection to implement its map collection type.

Using the mapOf() Function

To create an immutable or read-only Map collection in Kotlin, we use the mapOf() function. We create a map with this function by giving it a list of pairs—the first value is the key, and the second is the value. Calling this function returns a Kotlin Map interface type.

val callingCodesMap: Map<Int, String> = mapOf(234 to "Nigeria", 1 to "USA", 233 to "Ghana")
for ((key, value) in callingCodesMap) {
    println("$key is the calling code for $value")
}
print(callingCodesMap[234]) // Nigeria

Running the code above will produce the result: 

234 is the calling code for Nigeria
1 is the calling code for USA
233 is the calling code for Ghana

Unlike the List and Set interfaces in Kotlin that extend the Collection interface, the Map interface doesn't extend any at all. Some of the properties and functions available in this interface are:

  • size: this property returns the size of map collection.
  • isEmpty(): returns true if the map is empty or false otherwise.
  • containsKey(key: K): returns true if the map contains the key in the argument. 
  • containsValue(value: V): returns true if the map maps one or more keys to the value passed as an argument.
  • get(key: K): returns the value matching the given key or 'null' if none is found. 
  • keys: this property returns an immutable Set of all the keys in the map.
  • values: returns an immutable Collection of all the values in the map.

Using the mutableMapOf() Function

The mutableMapOf() function creates a mutable map for us so that we can add and remove elements in the map. This returns a Kotlin MutableMap interface type.

val currenciesMutableMap: MutableMap<String, String> = mutableMapOf("Naira" to "Nigeria", "Dollars" to "USA", "Pounds" to "UK")
println("Countries are ${currenciesMutableMap.values}") // Countries are [Nigeria, USA, UK]
println("Currencies are ${currenciesMutableMap.keys}") // Currencies are [Naira, Dollars, Pounds]
currenciesMutableMap.put("Cedi", "Ghana")
currenciesMutableMap.remove("Dollars")

The MutableMap interface doesn't extend the MutableCollection interface; it's only parent is the Map interface. It overrides the keysentries and values properties from the parent interface in order to redefine them. Here are some of the extra functions available in the MutableMap interface:

  • put(key: K, value: V): inserts the key, value pair into the map. This will return the previous value linked with the key or null if the key was not previously used. 
  • remove(key: K): removes the key and its linked value from the map. 
  • putAll(from: Map<out K, V>): updates the map with all the data from the given map. New keys will be added, and existing keys will be updated with new values. 
  • clear(): removes all elements from the map. 

We can get the value for a key using the get() function. We can also use square bracket notation as a shortcut for get()

print(currenciesMutableMap.get("Nigeria")) // will print Naira
print(currenciesMutableMap["Nigeria"]) // will print Naira

Using the hashMapOf() Function

Using this function returns a Java HashMap type that is mutable. The HashMap class uses a hash table to implement the Java Map interface.  

val personsHashMap: java.util.HashMap<Int, String> = hashMapOf(1 to "Chike", 2 to "John", 3 to "Emeka")
personsHashMap.put(4, "Chuka") 
personsHashMap.remove(2)
print(personsHashMap[1]) // will print Chike

Using the linkedHashMap() Function

This function returns a Java LinkedHashMap type that is mutable. The LinkedHashMap class extends Java HashMap and maintains a linked list of the entries in the map in the order in which they were inserted. 

val postalCodesHashMap: java.util.LinkedHashMap<String, String> =
linkedMapOf("NG" to "Nigeria","AU" to "Australia","CA" to "Canada")
postalCodesHashMap.put("NA", "Namibia")
postalCodesHashMap.remove("AU")
postalCodesHashMap.get("CA") // Canada

Using the sortedMapOf() Function

This function returns a Java SortedMap type which is mutable. The Java SortedMap class sees that the entries in the map are maintained in an ascending key order.

val personsSortedMap: java.util.SortedMap<Int, String> = sortedMapOf(2 to "Chike", 1 to "John", 3 to "Emeka")
personsSortedMap.put(7, "Adam")
personsSortedMap.remove(3)

Remember, implementation of these collection interfaces in Kotlin happens at compile time.

Collections Operation Functions

Kotlin provides us with many useful operator functions called extension functions that can be invoked on collections. Let's take a look at some of the most useful.

The last() Function

This operator function returns the last element in a collection such as a list or set. We can also supply a predicate to search within a subset of elements.

val stringList: List<String> = listOf("in", "the", "club")
print(stringList.last()) // will print "club"

// given a predicate
print(stringList.last{ it.length == 3}) // will print "the"

val intSet: Set<Int> = setOf(3, 5, 6, 6, 6, 3)
print(intSet.last()) // will print 6

The first() Function

This operator function returns the first element when invoked on a collection such as a list or set. If a predicate is given, it then uses the predicate to restrict the operation to a subset of elements.

print(stringList.first()) // will print "in"
print(intSet.first()) // will print 3

The max() Function

Invoking this operator function on a collection such as a list or set returns the largest element, or null if no largest element is found.

val intList: List<Int> = listOf(1, 3, 4)
print(intList.max()) // will print 4
print(intSet.max()) // will print 6

The drop() Function

Calling this operator function returns a new list or set containing all elements except the first n elements.

print(stringList.drop(2)) // will print "club"

The plus() Function

This operator function returns a collection containing all elements of the original and then the given element if it isn't already in the collection. This will end up creating a new list instead of modifying the list.

print(intList.plus(6)) // will print [1, 3, 4, 6]

The minus() Function

The opposite of the plus() function is the minus() function. It returns a collection containing all elements of the original set except the given element. This also ends up creating a new list instead of altering the list.

print(intList.minus(3)) // will print [1, 4]

The average() Function

Calling this operator function will return an average number of elements in the collection.

print(intList.average()) // will print 2.6666666666666665

Most of these extension functions are available in the Kotlin collections standard library. You are advised to check out the documentation to learn about the others.

Conclusion

In this tutorial, you learned about the range and collections API in the Kotlin programming language. In the next tutorial in the Kotlin From Scratch series, you'll be introduced to functions in Kotlin. See you soon!

To learn more about the Kotlin language, I recommend visiting the Kotlin documentation. Or check out some of our other Android app development posts here on Envato Tuts!


文章来源: https://code.tutsplus.com/tutorials/kotlin-from-scratch-ranges-and-collections--cms-29397
如有侵权请联系:admin#unsafe.sh