Three articles to get you started Kotlin (Middle)

Standard functions and static methods

Standard functions

Kolin’s Standard functions refer to those defined in the standard.kt file, and any Kotlin code is free to call all of the Standard functions.

There are not many standard functions, but it is difficult to learn them all in one row. Because we’re going to focus on a couple of common functions here.

with

The with function takes two arguments: the first argument can be an object of any type, and the second argument is a Lambda expression. The with function provides the context of the first argument object in the Lambda expression and returns the last line of code in the Lambda expression as the return value.

Example code is as follows:

val result = with(obj) {
      // Here is the context of obj
      "value" // The return value of the with function
}
Copy the code
fun with(a) {
    val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
    val result = with(StringBuilder()) {
        append("with\nstart\n")
        for (fruit in list) {
            append(fruit + "\n")
        }
        append("end")
        toString()
    }
    println(result)
}
Copy the code

run

The use and scenario of the run function is very similar to that of the with function, with a few syntax changes. First of all, the run function cannot be called directly, but must call the run function of an object. Second, the run function takes only a Lambda argument and provides the context of the calling object in the Lambda expression. Everything else is the same as with, including the fact that the last line of the Lambda expression is also returned as the return value.

Example code is as follows:

val result = obj.run {
      // Here is the context of obj
      "value" // Return value of the run function
}
Copy the code
fun run(a) {
    val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
    val result = StringBuilder().run {
        append("run\nstart\n")
        for (fruit in list) {
            append(fruit + "\n")
        }
        append("end")
        toString()
    }
    println(result)
}
Copy the code

apply

The apply and run functions are also very similar in that they are called on an object and take only a Lambda argument. They also provide the context of the calling object in the Lambda expression, but apply cannot specify a return value. Instead, it automatically returns the calling object itself.

Example code is as follows:

val result = obj.apply {
      // Here is the context of obj
}
// result == obj
Copy the code
fun apply(a) {
    val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
    val result = StringBuilder().apply {
        append("apply\nstart\n")
        for (fruit in list) {
            append(fruit + "\n")
        }
        append("end")
    }
    println(result.toString())
}
Copy the code

Defining static methods

Static methods are methods that can be invoked without creating an instance, and are supported by all major programming languages.

Generic utility classes are generally not necessary to create instances and are basically universal.

Kotlin does not directly define static method keywords, but does provide syntactic features to support the writing of similar static method calls, such as singleton classes, Companion Objects, etc. These syntactic features should be sufficient for normal development needs.

However, if you really need to define truly static methods, Kotlin still offers two ways to do it: annotations and top-level methods.

@ JvmStatic annotations

class Util {
    companion object {
        @JvmStatic
        fun doAction(a) {
            println("do action2")}}}Copy the code

The top method

Methods that are not defined in any class,

As long as you define a top-level method, it must be static

fun doSomething(a) {
    println("do something")}Copy the code

Call the Kotlin static method in Java

Call Kotlin’s top-level method in Java and you’ll find that when found, because Java has no concept of top-level methods, Java creates a Java class for StaticMethodKt and then calls the static method of doSomething().

public class JavaTest {
    publicvoid test() { Util1.doAction2(); StaticMethodKt.doSomething(); }}Copy the code

Delayed initialization and sealing classes

Lazy initialization of variables

Lazy initialization uses the lateInit keyword, which tells the Kotlin compiler that I will initialize this variable later so I don’t have to assign it to null in the first place.

This modifier can only be used for attributes in the class body.

class Dog() {
    lateinit var say: String
    fun call(a) {
        say = "Dog bark."}}Copy the code

Checks whether an LateInit var has been initialized

if (foo::bar.isInitialized) {
    println(foo.bar)
}
Copy the code

Kotlin provides a lazy() function that takes a lambda expression as an argument and returns a lazy object

The following is an example:

class Dog {
    val bark: String by lazy {
        println("Execute code block on first access")
        "Dog"}}Copy the code

Seal type

Sealed classes are used to represent a restricted class inheritance structure: when a value can have a finite number of types and no other types. In a sense, they are an extension of enumerated classes: the collection of values of enumerated types is also limited, but only one instance of each enumerated constant exists, whereas a subclass of a sealed class can have multiple instances of containable state.

To declare a sealed class, add the sealed modifier before the class name. Although a sealed class can have subclasses, all subclasses must be declared in the same file as the sealed class itself.

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
Copy the code
  • A sealed class is itselfIn the abstract, it cannot be instantiated directly and can have abstractions (abstractMembers).
  • Sealed classes do not allow non –privateConstructor (whose constructor defaults toprivate).
  • Note that classes (indirect successors) of extended sealed class subclasses can be placed anywhere without being in the same file.
  • The key benefit of using a sealed class is the usewhenexpressionIf you can verify that the statement covers all cases, you do not need to add another statement to the statementelseThe clause. Of course, this is only if you usewhenUseful as expressions (using results) rather than as statements.
fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // The 'else' clause is no longer needed because we have covered all cases
}
Copy the code

The sample

sealed class Apple {
    abstract fun taste(a)
}

open class RedFuji : Apple() {
    override fun taste(a) {
        println("The red Fuji apple is sweet and delicious!")}}data class Gala(var weight: Double) : Apple() {
    override fun taste(a) {
        println("Galala fruit, weight is$weight")}}fun main(a) {
    var ap1: Apple = RedFuji()
    var ap2: Apple = Gala(2.3)
    ap1.taste()
    ap2.taste()

    judge(ap1)
    judge(ap2)
}

fun judge(ap: Apple) {
    when (ap) {
        is RedFuji -> println("The red Fuji apple is sweet and delicious!")
        is Gala -> println("Gala fruit")}}Copy the code

Extend function and operator overloading

Extension function

Extension functions represent the ability to open a class and add new functions to it without modifying its source code.

The syntax structure is very simple, as follows:

fun ClassName.methodName(param1: Int, param2: Int): Int {
      return 0
}
Copy the code

To think about counting the number of characters in a string, you typically write a singleton or static pattern

object StringUtil {
    fun lettersCount(str: String): Int {
        var count = 0
        for (char in str) {
            if (char.isLetter()) {
                count++
            }
        }
        return count
    }
}
Copy the code

General extension functions are defined in top-level methods. This allows extension functions to have a global access domain, and extension functions can in many cases make apis cleaner, richer, and more object-oriented.

fun String.lettersCount(a): Int {
    var count = 0
    for (char in this) {
        if (char.isLetter()) {
            count++
        }
    }
    return count
}
Copy the code

call

fun main(a) {
    val str = "ABC123xyz! @ #"
    val count = StringUtil.lettersCount(str)
    println("count $count")


    println("ex count ${str.lettersCount()}")}Copy the code

Overloaded operator

Kotlin’s operator overloading allows us to add any two objects together, and to do much more.

Using the plus operator as an example, if you want to add two objects, the syntax is as follows:

class Obj {

      operator fun plus(obj: Obj): Obj {
            // Handle the adding logic}}Copy the code

The comparison between syntactic sugar expressions and the actual called functions is as follows:

Syntactic sugar expression Actually calling the function
a + b a.plus(b)
a – b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b)
a++ a.inc()
a– a.dec()
+a a.unaryPlus()
-a a.unaryMinus()
! a a.not()
a == b a.equals(b)
a > ba < ba >= b****a <= b a.compareTo(b)
a.. b a.rangeTo(b)
a[b] a.get(b)
a[b] = c a.set(b, c)
a in b b.contains(a)

Example For example two Money object operator overloads use the second is the operator keyword

class Money(val value: Int) {
    operator fun plus(money: Money): Money {
        val sum = value + money.value
        return Money(sum)
    }

    operator fun plus(newValue: Int): Money {
        val sum = value + newValue
        return Money(sum)
    }

}
Copy the code
fun main(a) {
    val money1 = Money(5)
    val money2 = Money(10)
    val money3 = money1 + money2
    println(money3.value)
    val money4 = money3 + 20
    println(money4.value)
    }
Copy the code

For other operators, overload the table by referring to the corresponding method

if ("hello".contains("he")) {
    println("hello contains in ")}if ("he" in "hello") {
    println("he in ")}Copy the code

Random string repetition, the idea is to repeat the string passed in N times, if we can write STR * N to indicate that the string is repeated N times

fun getRandomLengthString(str: String): String {
    val n = (1.20.).random()
    val builder = StringBuilder()
    repeat(n) {
        builder.append(str)
    }
    return builder.toString()
}
Copy the code

Kotlin already provides us with a repeat function for the string N times, which can be rewritten as follows

operator fun String.times(n: Int) = repeat(n)
Copy the code

Eventually we can abbreviate it as follows

fun getRandomLengthString1(str: String) = str * (1.20.).random()
Copy the code

Higher order functions in detail

Higher-order functions are closely related to Lambda.

Definition: A function is called a higher-order function if it takes another function as an argument, or if the type of the return value is another function.

fun example(func: (String.Int) - >Unit) {
      func("hello".123)}Copy the code

Lambda expressions call higher-order functions

Kotlin also supports a variety of other ways to call higher-order functions, such as Lambda expressions, anonymous functions, and member references.

Lambda expressions are also the most common way of calling higher-order functions.

The sample

val result1 = num1AndNum2(num1, num2) { n1, n2 ->
	n1 + n2
}
Copy the code

A higher-order function that mimics the functionality of the standard function Apply

Note that this function type parameter declaration is a bit different from the syntax we learned earlier:

It precedes the function type with a syntax of StringBuilder.

This is actually the syntax that defines the completion of higher-order functions, the ClassName in front of the function type. Indicates the class in which the function type is defined

The advantage of this definition is that the lambda expression passed in when we call the build function automatically has the StringBuilder context.

fun StringBuilder.build(block: StringBuilder. () - >Unit): StringBuilder {
    block()
    return this
}
Copy the code

The role of inline functions

Inline functions can completely eliminate the runtime overhead associated with using Lambda expressions. The way it works isn’t that complicated, except that the Kotlin compiler automatically replaces the code in the inline function at compile time to where it was called, so there’s no runtime overhead.

inline

Defining inline functions is as simple as adding a declaration with the inline keyword when defining higher-order functions, as shown below:

inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int.Int) - >Int): Int {
    val result = operation(num1, num2)
    return result
}
Copy the code

noinlline

Noinlline partially disables inlining

After using an inline modifier, all Lambda expressions or functions passed to the function are inlined, or if you want parameters of one or more function types in a function not to be inlined, you can use the noinline modifier

inline fun inlineTest(block: () -> Unit.noinline blokc2: () -> Unit){}Copy the code

There is another important difference between inline and non-inline functions: the Lambda referenced by an inline function can be used to return a function using the return keyword, whereas non-inline functions can only return locally.

It is good programming practice to write higher-order functions as inline functions. In fact, most higher-order functions can be declared directly as inline functions, but there are a few exceptions.

crossinline

Some inline functions may call lambda expression arguments that are passed to them not directly from the function body, but from another execution context, such as local objects or nested functions. In this case, non-local control flow is also not allowed in the lambda expression. To identify this case, the lambda expression argument needs to be marked with the Crossinline modifier

inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run(a) = body()
    }
    / /...
}
Copy the code

Application of higher order functions

Let’s simplify the following extension

val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
editor.putString("name"."yx")
editor.putInt("age".30)
editor.apply()
Copy the code

We can use higher-order functions to simplify the use of SharedPreferences, as follows:

The following is an example:

fun SharedPreferences.open(block: SharedPreferences.Editor. () - >Unit) {
    val editor = edit()
    editor.block()
    editor.apply()
}
Copy the code

Once you have defined the open function, it will be easier to store data in your project using SharedPreferences in the future, as follows:

getSharedPreferences("data", Context.MODE_PRIVATE).open {
    putString("name"."yx")
    putInt("age".30)}Copy the code

Generics and delegates

The generic

In normal programming mode, we need to specify a specific type for any variable. Generics allow us to write code without specifying a specific type, which makes the code much more extensible.

There are two main ways to define generics, one is to define generic classes, and the other is to define generic methods, using both syntactic structures. Of course, T in parentheses is not required, in fact you can use any letter or word, but in general, T is a generic way of writing.

If you wanted to define a generic class, you could write:

class MyClass<T> {

    fun method(param: T): T {
        return param
    }
    
}
Copy the code

When calling MyClass and method(), generics can be specified as concrete types, as follows:

val myClass = MyClass<Int> ()val result = myClass.method(123)
Copy the code

If you don’t want to define a generic class, but just want to define a generic method, just write the syntax structure for defining generics above the method, as follows:

class MyClass {

    fun <T> method(param: T): T {
        return param
    }

}
Copy the code

The call method also needs to be adjusted accordingly:

val myClass = MyClass()
val result = myClass.method<Int> (123)
Copy the code

As you can see, it is now time to specify a generic type when calling the method() method. In addition, Kotlin has a very good type inference mechanism. For example, if you pass in an Int parameter, it can automatically infer that the type of the generic is Int, so it can also omit the generic specification:

val myClass = MyClass()
val result = myClass.method(123)
Copy the code

Commissioned by class

The core idea of class delegation is to delegate the implementation of one class to another class. However, delegation also has some disadvantages. If there are few methods to implement in the interface, if there are dozens or hundreds of methods, each of which calls the corresponding method implementation in the auxiliary object, it can be very complicated to write. This problem can be solved in Kotlin with the class delegate functionality.

class MySet<T>(val helperSet: HashSet<T>) : Set<T> {
    override val size: Int = helperSet.size


    override fun contains(element: T): Boolean = helperSet.contains(element)

    override fun containsAll(elements: Collection<T>): Boolean = helperSet.containsAll(elements)

    override fun isEmpty(a): Boolean = helperSet.isEmpty()

    override fun iterator(a): Iterator<T> = helperSet.iterator()
}
Copy the code
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
    
    fun helloWorld(a) = println("Hello World")

    override fun isEmpty(a) = false

}
Copy the code

MySet is now a new data structure class that not only is never empty, but also prints helloWorld(), and the rest of the Set interface functions are consistent with HashSet. This is what Kotlin’s class delegate can do.

Attribute to entrust

The core idea of delegating attributes is to delegate the implementation of an attribute (field) to another class.

Let’s look at the syntax structure of the delegate attribute, as follows:

class MyClass {
    
    var p by Delegate()
    
}
Copy the code

Here we use the by keyword to connect the p property on the left to the Delegate instance on the right, which represents delegating the implementation of the P property to the Delegate class. The Delegate class’s getValue() method is automatically called when the p property is called, and the Delegate class’s setValue() method is automatically called when assigning a value to the P property.

class MyClassProp {
    var p by Delegate()
}

class Delegate {
    var propValue: Any? = null

    operator fun getValue(myClassProp: MyClassProp, property: KProperty< * >): Any? {
        return propValue
    }

    operator fun setValue(myClassProp: MyClassProp, property: KProperty<*>, any: Any?). {
        propValue = any
    }
}
Copy the code

Application of delegation

Implement a Lazy function of your own

val p by lazy{ ... }
Copy the code
  • Lazy is a higher-order function
  • The lazy function creates and returns a Delegate object
  • When we call the p property, we’re actually calling the Delegate object’s getValue() method
  • The getValue method in turn calls the Lambda expression passed in by the lazy function
  • The p attribute results in the last line of code in the Lambda expression
class Later<T>(val block: () -> T) {
    var value: Any? = null
    operator fun getValue(any: Any? , property:KProperty< * >): T {
        if (value == null) {
            value = block()
        }
        return value as T
    }
}

fun <T> later(block: () -> T) = Later(block)

val p by later {
    println("First load")
    "123"
}

fun main(a) {
    println("p$p")
    println("p$p")}Copy the code