Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

A collection of functional apis

The functional API for collections is an excellent example of learning Lambda programming.

Collections have traditionally been lists and sets, but more broadly key-value data structures such as maps can also be included.

List, Set, and Map are all interfaces in Java. The main implementation classes of List are ArrayList and LinkedList, the main implementation class of Set is HashSet, and the main implementation class of Map is HashMap.

1.1 the List

Kotlin writes ArrayList in Java thinking:

val list = ArrayList<String>()
list.add("Apple")
list.add("Banana")
list.add("Orange")
list.add("Pear")
list.add("Grape")
Copy the code

This is a bit more cumbersome, Kotlin writes:

val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape")
Copy the code

For-in loops can be used not only to iterate over intervals, but also over collections:

fun main(a) {
    val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape")
    for (fruit in list) {
        println(fruit)
    }
}
Copy the code

Note: The listOf() function creates an immutable collection.

An immutable collection means that the collection can only be read; we cannot add, modify, or delete the collection. Kotlin is extremely strict about immutability. Create mutable collections using the mutableListOf() function:

fun main(a) {
    val list = mutableListOf("Apple"."Banana"."Orange"."Pear"."Grape")
    list.add("Watermelon")
    for (fruit in list) {
        println(fruit)
    }
}
Copy the code

1.2 the Set

Sets are used similarly to lists, except that setOf() and mutableSetOf() are used instead.

val set = setOf("Apple"."Banana"."Orange"."Pear"."Grape")
for (fruit in set) {
    println(fruit)
}
Copy the code

It should be noted that the underlying Set uses the hash mapping mechanism to store data, so the elements in the Set cannot be guaranteed to be ordered, which is the biggest difference between the Set and List.

1.3 the Map

Map is a key-value pair data structure, so its usage is quite different from that of List and Set. The traditional use of a Map is to create an instance of a HashMap and then add key-value pairs of data to the Map.

Kotlin’s Java thinking for HashMap:

val map = HashMap<String, Int>()
map.put("Apple".1)
map.put("Banana".2)
map.put("Orange".3)
map.put("Pear".4)
map.put("Grape".5)
Copy the code

Instead of using the PUT () and get() methods to add and read data to a Map, Kotlin recommends using a syntax similar to array subscripts:

val map = HashMap<String, Int>()
map["Apple"] = 1
map["Banana"] = 2
map["Orange"] = 3
map["Pear"] = 4
map["Grape"] = 5
Copy the code

Most conveniently, like List and Set, Kotlin also provides mapOf() and mutableMapOf() functions.

val map = mapOf("Apple" to 1."Banana" to 2."Orange" to 3."Pear" to 4."Grape" to 5)
Copy the code

The key-value pair combinations here appear to be associated using the to keyword, but to is not a keyword, but an Infix function.

How to traverse data in a Map collection:

fun main(a) {
    val map = mapOf("Apple" to 1."Banana" to 2."Orange" to 3."Pear" to 4."Grape" to 5)
    for ((fruit, number) in map) {
        println("fruit is " + fruit + ", number is " + number)
    }
}
Copy the code

Two sets of functional APIS

2.1 Derivation of functional API of set

There are many functional apis for collections, and we will focus on the syntax structure of functional apis, that is, the syntax structure of Lambda expressions.

How to find the fruit with the longest word in a fruit collection?

val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
var maxLengthFruit = ""
for (fruit in list) {
    if (fruit.length > maxLengthFruit.length) {
        maxLengthFruit = fruit
    }
}
println("max length fruit is " + maxLengthFruit)
Copy the code

This code is neat, but we can make it much easier if we use the collection’s functional API:

val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
val maxLengthFruit = list.maxBy { it.length }
println("max length fruit is " + maxLengthFruit)
Copy the code

Definition of Lambda: A Lambda is a small piece of code that can be passed as an argument.

Normally, we can only pass in variables when passing parameters to a function, but Lambda allows us to pass in a small piece of code. How much code is a piece of code? Kotlin does not limit this, but it is generally not recommended to write code that is too long in Lambda expressions because it can affect the readability of the code.

Next, let’s look at the syntax of Lambda expressions:

{Parameter name 1: parameter type, parameter name 2: parameter type -> function body}Copy the code

This is the most complete syntactic structure definition of a Lambda expression. First of all, the outermost layer is a pair of curly braces, if there is a parameter passed to Lambda expressions, we also need to declare the parameter list, at the end of the argument list to use a – > symbol, said the end of the parameter list and the beginning of the function body, the body of the function can be written in any line of code (though long code) is not recommended, And the last line of code is automatically returned as a Lambda expression.

Going back to the need to find the longest word fruit, the syntax of the functional API used earlier may seem peculiar, but maxBy is just a normal function that takes a Lambda parameter and passes the value of each iteration as an argument to the Lambda expression as it traverses the collection. The maxBy function works by iterating through the set based on the condition we pass in to find the maximum value under that condition. For example, if we want to find the fruit with the longest word, the condition should naturally be the length of the word.

Now that we understand how the maxBy function works, we can apply the syntax structure of the Lambda expression we just learned and pass it into the maxBy function, as follows:

val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
val lambda = { fruit: String -> fruit.length }
val maxLengthFruit = list.maxBy(lambda)
Copy the code

As you can see, the maxBy function essentially takes a Lambda argument, and the Lambda argument is defined exactly according to the syntax structure of the expression we just learned, so this code should be easier to understand.

This works fine, but it’s verbose and has a lot of points to simplify. Let’s start simplifying this code step by step.

First, we don’t need to define a lambda variable. Instead, we can pass lambda expressions directly into maxBy functions, so the first step is simplified as follows:

val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
Copy the code

Kotlin then specifies that when a Lambda argument is the last argument to a function, the Lambda expression can be moved outside the function’s parentheses, as follows:

val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }
Copy the code

Next, if the Lambda argument is the only argument to the function, you can also omit the parentheses of the function:

val maxLengthFruit = list.maxBy { fruit: String -> fruit.length }
Copy the code

Does that make the code look cleaner? But we can keep simplifying. Thanks to Kotlin’s excellent type derivation mechanism, the argument lists in Lambda expressions don’t actually have to declare parameter types in most cases, so the code can be further simplified to:

val maxLengthFruit = list.maxBy { fruit -> fruit.length }
Copy the code

Finally, when there is only one argument in the argument list of a Lambda expression, instead of declaring the argument name, the it keyword can be used instead, and the code becomes:

val maxLengthFruit = list.maxBy { it.length }
Copy the code

By going through this step by step, we get exactly the same writing as we did in the beginning of the functional API.

2.1 Collection of common functional apis

2.1.1 the map function

The map function, one of the most commonly used functional apis, is used to map each element in a collection to another value, the rules of which are specified in a Lambda expression, resulting in a new collection. For example, if we want all fruit names to be capitalized, we could write:

fun main(a) {
    val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
    val newList = list.map { it.toUpperCase() }
    for (fruit in newList) {
        println(fruit)
    }
}
Copy the code

The map function is very powerful. It can map and transform the elements of a collection any way we want. The above is just a simple example. Alternatively, you can convert fruit names to all lowercase, or just the first letter of a word, or even a set of numbers like word length, simply by writing the logic you need in the Lambda representation.

2.1.2 the filter function

The filter function is used to filter the data in the collection, either alone or in conjunction with the map function.

For example, if we want to keep fruit with less than 5 letters, we can use the filter function to do so, as shown below:

fun main(a) {
    val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
    val newList = list.filter { it.length <= 5 }
                      .map { it.toUpperCase() }
    for (fruit in newList) {
        println(fruit)
    }
}
Copy the code

As you can see, both the filter and map functions are used and the fruit word is limited to five letters by the Lambda representation.

It is also worth noting that in the above code we called the filter function first and then the map function. If you call the map function first and then the filter function, you can achieve the same effect, but the efficiency is much less, because this is equivalent to mapping all the elements in the collection and then filtering, which is completely unnecessary. It is significantly more efficient to filter first and then map and transform the filtered elements.

2.1.3 Any and All functions

The any function is used to determine whether at least one element in the collection satisfies the specified condition, and the all function is used to determine whether all elements in the collection satisfy the specified condition.

fun main(a) {
    val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
    val anyResult = list.any { it.length <= 5 }
    val allResult = list.all { it.length <= 5 }
    println("anyResult is " + anyResult + ", allResult is " + allResult)
}
Copy the code

Here again, the conditional is set to words with less than five letters in the Lambda expression, so the any function indicates whether there are words with less than five letters in the set, and the all function indicates whether all words in the set are within five letters. Now run the code again:

The use of Java functional APIS

You can also use functional apis when calling Java methods in Kotlin, but this is limited. Specifically, if we call a Java method in Kotlin code and the method takes a Single Abstract Java method interface argument, we can use the functional API. A Java single abstract method interface is one in which there is only one method to implement. If there are multiple methods to implement in an interface, functional apis cannot be used.

Examples of Java neutron threads:

public interface Runnable {
    void run(a);
}
new Thread(new Runnable() {
    @Override
    public void run(a) {
        System.out.println("Thread is running");
    }
}).start();
Copy the code

Originally written as Kotlin:

Thread(object : Runnable {
    override fun run(a) {
        println("Thread is running")
    }
}).start()
Copy the code

The Thread constructor meets the requirements of Java functional API.

Thread(Runnable {
    println("Thread is running") 
}).start()
Copy the code

If more than one Java single abstract method interface parameter does not exist in the argument list of a Java method, we can also omit the interface name and simplify again:

Thread({
    println("Thread is running") 
}).start()
Copy the code

In Kotlin, when a Lambda expression is the last argument to a method, it is possible to move the Lambda expression outside the method parentheses. Also, if the Lambda expression is still the only argument to the method, you can omit the parentheses of the method to simplify the result:

Thread {
    println("Thread is running")
}.start()
Copy the code

You might think that since all the code in this book is written using Kotlin, this Kind of Java functional API is not very common. This is not the case, because the Android SDK we’ll be dealing with a lot is written in Java, and when we call these SDK interfaces in Kotlin, we’ll probably use this Java functional API.

For example, Android has a very common click event interface, OnClickListener, defined as follows:

public interface OnClickListener {
    void onClick(View v);
}
Copy the code

As you can see, this is again a single abstract method interface. Suppose we now have an instance of a button button, and use Java code to register the button click event like this:

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    }
});
Copy the code

Using Kotlin code to do the same thing, you can write a functional API to simplify the code as follows:

button.setOnClickListener {
}
Copy the code

As you can see, the code is much simpler with this approach. This is the code that registers the button click event, which we will use a lot as we begin to learn about Android application development.

To study the reference

1 www.kotlincn.net/docs/refere website document…

2 Guo Lin: The First Line of Code