Sorting out the basic syntax and idioms of Android development using Kotlin

Posted by jackthebookie on Tue, 18 Jan 2022 12:42:52 +0100

Basic syntax and idioms of Android development using Kotlin

Since Google I/O in 2019, Kotlin has become the first choice for Android mobile development.

Android development with Kotlin can benefit from:

  • Less code and more readable. Spend less time writing code and understanding other people's code.
  • Mature language and environment. Since its establishment in 2011, Kotlin has been developing throughout the ecosystem not only through language but also through powerful tools. Now, it has been seamlessly integrated into Android Studio and is actively used by many companies to develop Android applications.
  • Android Jetpack and Kotlin support in other libraries. KTX extension adds Kotlin language features to the existing Android library, such as coroutine, extension function, lambdas and named parameters.
  • Interoperability with Java. You can use Kotlin with the Java programming language in your application without having to migrate all your code to Kotlin.
  • Support multi platform development. You can not only use Kotlin to develop Android, but also iOS, back-end and Web applications. Enjoy the benefits of sharing common code between platforms.
  • Code security. Less code and better readability result in fewer errors. The Kotlin compiler detects these remaining errors to make the code safe.
  • Easy to learn and use. Kotlin is very easy to learn, especially for Java developers.
  • Big community. Kotlin has received strong support and many contributions from the community, which is growing all over the world. According to Google, more than 60% of the top 1000 apps in the Play store use kotlin.

1, Basic grammar

1.1 variables

Define read-only local variables using the keyword val. It can only be assigned once.

fun main() {
    val a: Int = 1  // Immediate assignment
    val b = 2   // Automatically infer 'Int' type
    val c: Int  // If there is no initial value type, it cannot be omitted
    c = 3       // definitely assigned 
    println("a = $a, b = $b, c = $c")
}

// a = 1, b = 2, c = 3

Variables that can be re assigned use the var keyword:

fun main() {
    var x = 5 // Automatically infer 'Int' type
    x += 1
    println("x = $x")
}

// x = 6

Top level variables:

val PI = 3.14
var x = 0

fun incrementX() { 
    x += 1 
}

fun main() {
    println("x = $x; PI = $PI")
    incrementX()
    println("incrementX()")
    println("x = $x; PI = $PI")
}

// x = 0; PI = 3.14
// incrementX()
// x = 1; PI = 3.14

1.2 function

Functions in Kotlin are declared with the fun keyword:
The function parameters are defined using Pascal notation, i.e. name: type. Parameters are separated by commas. Each parameter must have an explicit type:

fun double(x: Int, y: Int): Int {
    return 2 * x * y
}

Function parameters can have default values, which are used when corresponding parameters are omitted. This reduces the number of overloads compared to other languages:

fun double(x: Int, y: Int= 2): Int {
    return 2 * x * y
}

If a default parameter precedes a parameter without a default value, the default value can only be used by calling the function with a named parameter:

fun double(x: Int = 3, y: Int): Int {
    return 2 * x *y
}

// double(y = 2) uses the default value x = 3

If the last parameter after the default parameter is a lambda expression, it can be passed in or out of parentheses as a named parameter

fun foo(
    bar: Int = 0,
    baz: Int = 1,
    qux: () -> Unit,
) { /*......*/ }

foo(1) { println("hello") }     // Use the default value baz = 1
foo(qux = { println("hello") }) // Use two default values bar = 0 and baz = 1
foo { println("hello") }        // Use two default values bar = 0 and baz = 1

A function that returns Int with two Int parameters:

fun sum(a: Int, b: Int): Int {
    return a + b
}

Functions that automatically infer an expression as a function body and return value type:

fun sum(a: Int, b: Int) = a + b

The calling function uses the traditional method:

val result = double(2)

1.3 string template

String literals can contain template expressions, small pieces of code that evaluate and merge the results into the string. The template expression starts with the dollar sign ($) and consists of a simple name:

val i = 10
println("i = $i") // Output "i = 10"

Or any expression enclosed in curly braces:

val s = "abc"
println("$s.length is ${s.length}") // Output "abc.length is 3"

1.4 conditional expression

In Kotlin, if is an expression, that is, it returns a value. Therefore, there is no need for ternary operators (condition? Then: otherwise), because ordinary if can be competent for this role.

fun maxOf(a: Int, b: Int) = if (a > b) a else b

The branch of if can be a code block, and the last expression is the value of the block:

val max = if (a > b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}

1.5 null and null detection

In Kotlin, the type system distinguishes whether a reference can accommodate null (nullable reference) or not (non empty reference). For example, a regular variable of type String cannot hold null:

var a: String = "abc" // By default, regular initialization means non empty
// a = null / / compilation error

When the value of a variable can be null, it must be added after the type at the declaration? To identify that the reference can be empty.

var b: String? = "abc" // Can be set to null
b = null // ok

Now, if you call a's method or access its properties, it will not cause NPE, so you can safely use:

val l = a.length

However, if you want to access the same attribute of b, it is not safe, and the compiler will report an error:

val l = b.length // Error: variable 'b' may be empty

But we still need to access this property, right? There are several ways to do this.

1.5.1 detect null in condition

First, you can explicitly detect whether b is null and handle two possibilities:

val l = if (b != null) b.length else -1

The compiler tracks the information of the detection performed and allows you to call length inside the if. It also supports more complex (smarter) conditions:

val b: String? = "Kotlin"
if (b != null && b.length > 0) {
    print("String of length ${b.length}")
} else {
    print("Empty string")
}

Please note that this only applies when b is immutable (that is, a local variable that has not been modified between detection and use, or a val member that is not overwritable and has a field behind the scenes), because otherwise, b may become null after detection.

1.5.2 safe call

Your second choice is to safely call the operator, writing

val a = "Kotlin"
val b: String? = null
println(b?.length)
println(a?.length) // No security call required

If B is not empty, b.length is returned; otherwise, null is returned. The type of this expression is Int?.

Safe calls are useful in chained calls. For example, if an employee Bob may (or may not) be assigned to a department, and another employee may be the head of the Department, get the name of the head of Bob's Department (if any). We write:

bob?.department?.head?.name

If any attribute (link) is empty, the chain call will return null.

If you want to perform an operation only on non null values, the safe call operator can be used with let s:

val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
    item?.let { 
        println(it) 
    } // Output Kotlin and ignore null
}

Security calls can also appear to the left of the assignment. In this way, if any receiver in the call chain is empty, the assignment will be skipped, and the expression on the right will not be evaluated at all:

// If 'person' or 'person Department ` if one of them is empty, this function will not be called:
person?.department?.head = managersPool.getManager()

1.5.3 Elvis operator

When we have an nullable reference b, we can say "if b is not empty, I use it; otherwise, I use a non empty value":

val l: Int = if (b != null) b.length else -1

In addition to the complete if expression, this can also be expressed through the Elvis operator. Write?:

val l = b?.length ?: -1

If?: If the left expression is not empty, the elvis operator returns its left expression, otherwise it returns the right expression. Note that the expression on the right side is evaluated if and only if the left side is empty.

Note that because throw and return are both expressions in Kotlin, they can also be used to the right of the elvis operator. This can be very convenient, for example, to detect function parameters:

fun foo(node: Node): String? {
    val parent = node.getParent() ?: return null
    val name = node.getName() ?: throw IllegalArgumentException("name expected")
    // ......
}

1.5.4 !! Operator

The third option is for NPE enthusiasts: non empty assertion operator (!) Converts any value to a non null type, and throws an exception if the value is null. We can write B, This will return a non empty b value (for example, String in our example) or if B is empty, an NPE exception will be thrown:

val l = b!!.length

Therefore, if you want an NPE, you can get it, but you must explicitly require it, otherwise it won't come unexpectedly.

1.5.5 safe type conversion

If the object is not the target type, regular type conversion may result in ClassCastException. Another option is to use safe type conversion. If the conversion attempt is unsuccessful, null will be returned:

val aInt: Int? = a as? Int

1.5.6 collection of nullable types

If you have a collection of nullable elements and want to filter non empty elements, you can use filterNotNull to implement:

val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()

1.6 type detection and automatic type conversion

1.6.1.1 is and! Is operator

We can use the is operator or its negative form at runtime! Is to detect whether the object conforms to the given type:

if (obj is String) {
    print(obj.length)
}

if (obj !is String) { // And! (obj is String) same
    print("Not a String")
}
else {
    print(obj.length)
}

1.6.2 intelligent conversion

In many cases, explicit conversion operators do not need to be used in Kotlin, because the compiler tracks is detection of immutable values and explicit conversions, and automatically inserts (SAFE) conversions when needed:

fun demo(x: Any) {
    if (x is String) {
        print(x.length) // x is automatically converted to a string
    }
}

The compiler is smart enough to know that if reverse detection results in a return, the conversion is safe:

if (x !is String) return

print(x.length) // x is automatically converted to a string

Or to the right of & & and |:

// `||`The x on the right is automatically converted to a string
if (x !is String || x.length == 0) return

// `&&`The x on the right is automatically converted to a string
if (x is String && x.length > 0) {
    print(x.length) // x is automatically converted to a string
}

1.7 For cycle

val items = listOf("apple", "banana", "kiwifruit")
for (item in items) {
    println(item)
}

val items = listOf("apple", "banana", "kiwifruit")
for (index in items.indices) {
    println("item at $index is ${items[index]}")
}

for (i in 1..4) print(i) // Output "1234"

for (i in 4..1) print(i) // Nothing output

if (i in 1..10) { // Equivalent to 1 < = I & & I < = 10
    println(i)
}

// Use step to specify the step size
for (i in 1..4 step 2) print(i) // Output "13"

for (i in 4 downTo 1 step 2) print(i) // Output "42"


// Use the until function to exclude the end element
for (i in 1 until 10) {   // i in [1, 10) excludes 10
     println(i)
}

1.8 when expression

The when expression replaces the switch statement of Java like language. Its simplest form is as follows:

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // Pay attention to this block
        print("x is neither 1 nor 2")
    }
}

If when is used as an expression, there must be an else branch unless the compiler can detect that all possible conditions have been overridden

If many branches need to be processed in the same way, you can put multiple branch conditions together and separate them with commas:

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

We can use arbitrary expressions (not just constants) as branching conditions

when (x) {
    parseInt(s) -> print("s encodes x")
    else -> print("s does not encode x")
}

We can also detect whether a value is in (in) or not in (! In) an interval or set:

when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

Another possibility is to detect whether a value is (is) or not (! Is) a specific type of value. Note: due to intelligent transformation, you can access methods and properties of this type without any additional detection.

fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}

when can also be used to replace the if else if chain. If no parameters are provided, all branch conditions are simple Boolean expressions. when the condition of a branch is true, the branch is executed:

when {
    x.isOdd() -> print("x is odd")
    y.isEven() -> print("y is even")
    else -> print("x+y is odd.")
}

Since Kotlin 1.3, the subject of when can be captured into a variable using the following syntax:

fun Request.getBody() =
        when (val response = executeRequest()) {
            is Success -> response.body
            is HttpError -> throw HttpException(response.status)
        }

The scope of the variable introduced in the when subject is limited to the when subject.

1.9 collection

1.9.1 set conversion

  • mapping

The mapping transformation creates a collection from the result of a function on an element of another collection. The basic mapping function is map(). It applies the given lambda function to each subsequent element and returns a list of lambda results. The order of the results is the same as the original order of the elements. To apply a transformation that also uses an element index as a parameter, use mapIndexed().

val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 })
println(numbers.mapIndexed { idx, value -> value * idx })
//[3, 6, 9]
//[0, 2, 6]

If the transformation produces null values on some elements, you can filter out null values from the result set by calling the mapNotNull() function instead of map() or mapIndexedNotNull() instead of mapIndexed().

val numbers = setOf(1, 2, 3)
println(numbers.mapNotNull { if ( it == 2) null else it * 3 })
println(numbers.mapIndexedNotNull { idx, value -> if (idx == 0) null else value * idx })
//[3, 9]
//[2, 6]

When mapping conversion, there are two options: conversion key, keep the value unchanged, and vice versa. To apply the specified transformation to keys, use mapKeys(); In turn, mapValues() converts values. Both functions use transformations that take mapping entries as arguments, so you can manipulate their keys and values.

fun main() {
    val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
    println(numbersMap.mapKeys { it.key.toUpperCase() })
    println(numbersMap.mapValues { it.value + it.key.length })
}
//{KEY1=1, KEY2=2, KEY3=3, KEY11=11}
//{key1=5, key2=6, key3=7, key11=16}
  • Close
    The closure transformation is to build a Pair based on elements with the same position in two sets. In the Kotlin standard library, this is done through the zip() extension function. When called on a collection (or array) with another collection (or array) as a parameter, zip() returns a List of Pair objects. The element of the receiver collection is the first of these pairs. If the size of the collection is different, the result of zip() is the size of the smaller collection; The result does not contain subsequent elements of a larger set. zip() can also call a zip b as an infix.
fun main() {
    val colors = listOf("red", "brown", "grey")
    val animals = listOf("fox", "bear", "wolf")
    println(colors zip animals)

    val twoAnimals = listOf("fox", "bear")
    println(colors.zip(twoAnimals))
}
// [(red, fox), (brown, bear), (grey, wolf)]
// [(red, fox), (brown, bear)]

When you have a Pair's List, you can do the reverse conversion unzipping -- build two lists from these key value pairs:

The first list contains the keys for each Pair in the original list.
The second list contains the values for each Pair in the original list.
To split the list of key value pairs, call unzip().

fun main() {
    val numberPairs = listOf("one" to 1, "two" to 2, "three" to 3, "four" to 4)
    println(numberPairs.unzip())
}
// ([one, two, three, four], [1, 2, 3, 4])
  • relation
    Associative transformation allows you to build a Map from a collection element and some of its associated values. In different association types, elements can be keys or values in the association Map.

The basic association function associateWith() creates a Map in which the elements of the original collection are keys and generates values from it through the given transformation function. If the two elements are equal, only the last one remains in the Map.

fun main() {
    val numbers = listOf("one", "two", "three", "four")
    println(numbers.associateWith { it.length })
}
// {one=3, two=3, three=5, four=4}

To build a Map using collection elements as values, there is a function associateBy(). It requires a function that returns the key based on the value of the element. If the two elements are equal, only the last one remains in the Map. You can also use the value conversion function to call associateBy().

fun main() {
    val numbers = listOf("one", "two", "three", "four")

    println(numbers.associateBy { it.first().toUpperCase() })
    println(numbers.associateBy(keySelector = { it.first().toUpperCase() }, valueTransform = { it.length }))
}
// {O=one, T=three, F=four}
// {O=3, T=5, F=4}
  • Level
    If you want to manipulate nested collections, you may find it useful to provide standard library functions that provide flattening access to nested collection elements.

The first function is flatten(). It can be called in a Set of sets (for example, a List composed of sets). This function returns a List of all elements in the nested collection.

fun main() {
    val numberSets = listOf(setOf(1, 2, 3), setOf(4, 5, 6), setOf(1, 2))
    println(numberSets.flatten())
}
// [1, 2, 3, 4, 5, 6, 1, 2]

Another function, flatMap(), provides a flexible way to handle nested collections. It requires a function to map one collection element to another. Therefore, flatMap() returns a single list containing the values of all elements. Therefore, flatMap() is represented as a continuous call of map() (taking the set as the mapping result) and flatten().

data class StringContainer(val values: List<String>)

fun main() {
    val containers = listOf(
        StringContainer(listOf("one", "two", "three")),
        StringContainer(listOf("four", "five", "six")),
        StringContainer(listOf("seven", "eight"))
    )
    println(containers.flatMap { it.values })
}
// [one, two, three, four, five, six, seven, eight]
  • String representation

If you need to retrieve the contents of a collection in a readable format, use the functions that convert the collection to a string: joinToString() and joinTo().

joinToString() builds a single String from a collection element based on the supplied parameters. joinTo() does the same, but attaches the result to the given Appendable object.

When called with default parameters, the result returned by the function is similar to calling toString() on the collection: the String representation of each element is separated by spaces.

fun main() {
    val numbers = listOf("one", "two", "three", "four")

    println(numbers)         
    println(numbers.joinToString())

    val listString = StringBuffer("The list of numbers: ")
    numbers.joinTo(listString)
    println(listString)
}
// [one, two, three, four]
// one, two, three, four
// The list of numbers: one, two, three, four

To build a custom string representation, you can specify its parameters in the function parameters separator, prefix, and postfix. The result string will start with prefix and end with postfix. With the exception of the last element, the separator will follow each element.

fun main() {
    val numbers = listOf("one", "two", "three", "four")    
    println(numbers.joinToString(separator = " | ", prefix = "start: ", postfix = ": end"))
}
// start: one | two | three | four: end

For larger collections, you might want to specify limit -- the number of elements that will be included in the result. If the collection size exceeds the limit, all other elements are replaced by a single value of the truncated parameter.

fun main() {
    val numbers = (1..100).toList()
    println(numbers.joinToString(limit = 10, truncated = "<...>"))
}
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, <...>

Finally, to customize the representation of the element itself, provide the transform function.

fun main() {
    val numbers = listOf("one", "two", "three", "four")
    println(numbers.joinToString { "Element: ${it.toUpperCase()}"})
}
// Element: ONE, Element: TWO, Element: THREE, Element: FOUR

1.9.2 filtration

  • Filter by predicate

The basic filter function is filter(). When called with a predicate, filter() returns the collection elements that match it. For List and Set, the filtering result is a List, and for Map, the result is also a Map.

fun main() {
    val numbers = listOf("one", "two", "three", "four")  
    val longerThan3 = numbers.filter { it.length > 3 }
    println(longerThan3)

    val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
    val filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") && value > 10}
    println(filteredMap)
}
// [three, four]
// {key11=11}

The predicate in filter() can only check the value of an element. If you want to use the element's position in the collection in filtering, you should use filterIndexed(). It accepts a predicate with two parameters: the index of the element and the value of the element.

If you want to use negative conditions to filter the collection, use filterNot(). It returns a list of elements that make the predicate false.

fun main() {
    val numbers = listOf("one", "two", "three", "four")

    val filteredIdx = numbers.filterIndexed { index, s -> (index != 0) && (s.length < 5)  }
    val filteredNot = numbers.filterNot { it.length <= 3 }

    println(filteredIdx)
    println(filteredNot)
}
// [two, four]
// [three, four]

There are also functions that can narrow the type of an element by filtering elements of a given type:

filterIsInstance() returns a collection element of the given type. When called on a List, filterisinstance () returns a List, allowing you to call T-type functions on collection elements.

fun main() {
    val numbers = listOf(null, 1, "two", 3.0, "four")
    println("All String elements in upper case:")
    numbers.filterIsInstance<String>().forEach {
        println(it.toUpperCase())
    }
}
// All String elements in upper case:
// TWO
// FOUR

filterNotNull() returns all non empty elements. In a list < T? > When called on, filterNotNull() returns a list < T: any >, allowing you to treat all elements as non empty objects.

fun main() {
    val numbers = listOf(null, "one", "two", null)
    numbers.filterNotNull().forEach {
        println(it.length)   // Length is not available for nullable strings
    }
}
// 3  3
  • divide
    Another filter function – partition() – filters the collection through a predicate and stores mismatched elements in a separate List. Therefore, you get the Pair of a List as the return value: the first List contains the elements that match the predicate, and the second List contains all the other elements in the original collection.
fun main() {
    val numbers = listOf("one", "two", "three", "four")
    val (match, rest) = numbers.partition { it.length > 3 }

    println(match)
    println(rest)
}
// [three, four]
// [one, two]
  • Test predicate
    Finally, some functions simply detect a predicate for collection elements:

any() returns true if at least one element matches the given predicate.
none() returns true if no element matches the given predicate.
all() returns true if all elements match the given predicate. Note that calling all () with any valid predicate on an empty collection returns true. This behavior is logically called vacuous truth.

fun main() {
    val numbers = listOf("one", "two", "three", "four")

    println(numbers.any { it.endsWith("e") })
    println(numbers.none { it.endsWith("a") })
    println(numbers.all { it.endsWith("e") })

    println(emptyList<Int>().all { it > 5 })   // vacuous truth
}
// 
true
true
false
true

Any () and none() can also be used without predicates: in this case, they are only used to check whether the collection is empty. If there are elements in the collection, any() returns true, otherwise false; none() is the opposite.

fun main() {
    val numbers = listOf("one", "two", "three", "four")
    val empty = emptyList<String>()

    println(numbers.any())
    println(empty.any())

    println(numbers.none())
    println(empty.none())
}
//
true
false
false
true

2, Idiomatic usage

2.1 creating DTOs

data class Customer(val name: String, val email: String, var age: Int)

The following functions are provided for the Customer class:

getters of all attributes (setters defined for var)
equals()
hashCode()
toString()
copy()
component1(), component2(), etc. of all attributes

2.2 default parameters of function

fun foo(a: Int = 0, b: String = "") { ...... }

2.3 filter list

val positives = list.filter { x -> x > 0 }
// Or shorter:
val positives = list.filter { it > 0 }

2.4 detect whether the element exists in the collection

if ("john@example.com" in emailsList) { ...... }

if ("jane@example.com" !in emailsList) { ...... }

2.5 string interpolation

println("Name $name")

2.6 type judgment

when (x) {
    is Foo //-> ......
    is Bar //-> ......
    else   //-> ......
}

2.7 traversing map/pair list

k. v can be changed to any name.

for ((k, v) in map) {
    println("$k -> $v")
}

2.8 service interval

for (i in 1..100) { ...... }  // Closed interval: including 100
for (i in 1 until 100) { ...... } // Half open interval: excluding 100
for (x in 2..10 step 2) { ...... }
for (x in 10 downTo 1) { ...... }
if (x in 1..10) { ...... }

2.9 read only list

val list = listOf("a", "b", "c")

2.10 read only map

val map = mapOf("a" to 1, "b" to 2, "c" to 3)

2.11 access map

println(map["key"])
map["key"] = value

2.12 delay properties

val p: String by lazy {
    // Evaluate the string
}

2.13 extension function

fun String.toMD5(): String {
    return try {
        // Get an MD5 converter (if you want to change the SHA1 parameter to "SHA1")
        val messageDigest = MessageDigest.getInstance("MD5")
        // Convert the input string to a byte array
        val inputByteArray: ByteArray = toByteArray()
        // inputByteArray is a byte array converted from an input string
        messageDigest.update(inputByteArray)
        // The conversion and return result is also a byte array, containing 16 elements
        val resultByteArray = messageDigest.digest()
        // Convert character array to string and return
        ByteUtils.bytesToHex(resultByteArray)
    } catch (e: NoSuchAlgorithmException) {
        ""
    }
}

"How do you do".toMD5()

2.14 creating a single instance

object Resource {
    val name = "Name"
}

2.15 If not null abbreviation

val files = File("Test").listFiles()

println(files?.size)

2.16 If not null and else abbreviations

val files = File("Test").listFiles()

println(files?.size ?: "empty")

2.17 if not null execution code

val value = ......

value?.let {
    ...... // The code will execute here if the data is not null
}

2.18 mapping nullable value (if not empty)

val value = ......

val mapped = value?.let { transformValue(it) } ?: defaultValue 
// If the value or its conversion result is empty, defaultValue is returned.

2.19 return when expression

fun transform(color: String): Int {
    return when (color) {
        "Red" -> 0
        "Green" -> 1
        "Blue" -> 2
        else -> throw IllegalArgumentException("Invalid color param value")
    }
}

2.20 calling multiple methods (with) on an object instance

class Turtle {
    fun penDown()
    fun penUp()
    fun turn(degrees: Double)
    fun forward(pixels: Double)
}

val myTurtle = Turtle()

with(myTurtle) { // Draw a 100 pixel square
    penDown()
    for (i in 1..4) {
        forward(100.0)
        turn(90.0)
    }
    penUp()
}

2.21 configuration object properties (apply)

This is useful for configuring properties that do not appear in the object constructor.

val myRectangle = Rectangle().apply {
    length = 4
    breadth = 5
    color = 0xFAFAFA
}

2.22 try with resources

use

val stream = Files.newInputStream(Paths.get("/some/file.txt"))
stream.buffered().reader().use { reader ->
    println(reader.readText())
}

2.23 suitable forms of generic functions requiring generic information

//  public final class Gson {
//     ......
//     public <T> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxException {
//     ......

inline fun <reified T: Any> Gson.fromJson(json: JsonElement): T = this.fromJson(json, T::class.java)

2.24 exchange two variables

var a = 1
var b = 2
a = b.also { b = a }

Topics: Android