Write Kotlin with Kotlin

Recently, I have been reading some books and documents related to Kotlin. I hope that I can write Java with Kotlin no longer, so I compiled them into notes.

Basic grammar

The var and val

Var is used to declare variable variables, and val is used to declare value variables (variables that implement the equivalent of Java’s final modifier). One of Kotlin’s programming principles is to declare variables with val whenever possible to avoid some of the side effects of changing the object referenced by the variable.

A side effect of a program is that the code is given the same input every time it executes, but the output is not necessarily the same. The side effect is often associated with mutable data and shared state, such as the following code:

var a = 0
fun count(x: Int){
    a = a + 1
    println(x + a)
}
Copy the code

When we execute count(1) multiple times, each time we print a different value, this is because the count function changes the value of the global shared state A.

Variable variables are a very important requirement in programming. In the scenario without VAR, it is sometimes difficult to implement the program, such as iterating over a list. With var, we only need a temporary variable to record the sum accumulated in the iteration. We may need to record the value of the recursive iteration list in the function stack through recursion, and then add it up successively. This limitation is obviously not as good as var in terms of program writing, performance and memory footprint, so Kotlin also needs to provide the var keyword.

function

Function parameters:

Write normal parameters directly.

Variables are declared using varargs, similar to Java… The final input parameter is an Array

Array.

fun funcMultiParams(vararg params: Int, single: Int) {}
funcMultiParams(1.2.3, single = 4)
Copy the code

In Java, mutable arguments can only be the last argument, which Kotlin does not. As shown above, you need to specify that the fourth argument is assigned to single, so that the compiler knows that the first three arguments in the argument list are assigned to params and that the last parameter belongs to single.

Function variable:

Functions are first-class citizens of Kotlin. Functions can be defined in files, classes, or methods. Functions themselves are data types, and can be passed to another function like normal variables, or returned as a value from a function.

// Define a variable functionVar
// It takes an input parameter of Int and returns an Int? The function of
var functionVar: (Int) - >Int?
Copy the code

Since a data type can be passed and returned as a normal object, Kotlin clearly supports higher-order functions as well.

Higher-order functions

The so-called higher-order function refers to that the parameter or return value of this function can be a function. Kotlin’s support for higher-order function gives program design a higher level of abstraction.

If we need to filter a List without higher-order functions or the filter API provided by List, there are many filtering conditions. The simplest way to write it is to encapsulate a function, iterate the List, and then call various strategies to determine whether to add it to result:

fun filterList(list: List<String>): List<String> {
    val result = mutableListOf<String>()
    list.forEach {
        if (filterCondition(it)) {
            result.add(it)
        }
    }
    return result
}

fun filterCondition(value: String): Boolean {
    return if (conditionA) {
        true
    } else if (conditionB) {
        true
    } else {
        false}}Copy the code

The filterList isn’t a universal tool, because the filtering strategy relies on the filterCondition. It’s possible to pass a filterCondition into a filterList as an interface, but Kotlin doesn’t need that. Kotlin’s implementation of the filter series of Collection is basically as follows:

public inline fun <T>可迭代<T>.filter(predicate: (T) - >Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}
Copy the code

Declare variables called predicate: (T) -> Boolean, filterTo calls the predicate during iterating through the list to determine whether the filter conditions are met.

We have already described how to declare variables of function types, but we cannot declare all functions as variables. How can a normal function be passed as a variable? We can obtain the function variable of an object by using the :: operator.

class Case {
    fun test(a) {}
    fun receiveFuncInner(a){
        // The current semantic context is already in this, you can omit this
        receiveFunc(::test)
    }
}

fun main(a) {
    val case = Case()
    / / an error 1
    receiveFunc(case.test())
    receiveFunc(case::test)
}

fun receiveFunc(func: () -> Unit) {}
Copy the code

ReceiveFunc requires a function entry, and the return value of the test() call is Unit.

But if we change the return type of the test function to:

fun test(a): () - >Unit {
		return{}}Copy the code

This example shows that function types can also be used as return values. Above we directly return a closure, equivalent to returning a function of type ()->Unit.

In fact, a more simplified way to write higher-order functions is as follows:

var highLevelFunc: (Int) - > ((Int) - >Unit)
Copy the code

Anonymous functions and Lambda

Functions don’t have to have names. Use anonymous functions like this:

// Pass an anonymous function directly to receiveFunc
receiveFunc(fun(a){})Copy the code

Now that we’re at anonymous functions, let’s go straight to Lambda expressions.

Let’s simplify the anonymous function even further:

fun receiveFunc(func: (Int) - >Int) {}
receiveFunc(fun(param: Int): Int {
		return param + 1
})
Copy the code

Drop the function definition, drop the return, and write the last line directly as the return value. The function input parameter is written to the function body in a special format:

receiveFunc { param ->
		print(param)
		param + 1
}
Copy the code

If we look at Java bytecode in Lambda notation above:

receiveFunc((Function1)null.INSTANCE);
Copy the code

You found that you passed in an instance of Function1, which is some of the interfaces that come with the Kotlin SDK.

public interface Function1<in P1, out R> : Function<R> {
    public operator fun invoke(p1: P1): R
}
Copy the code

The purpose of these interfaces is to be compatible with Java Lambda expressions in Kotlin.

Case ::test(); case::test();

receiveFunc((Function0)(new Function0(var0) {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke() {
            this.invoke();
            return Unit.INSTANCE;
         }

         public final void invoke() {
            ((Case)this.receiver).test(); }}));Copy the code

The same is true for converting the function object to an anonymous object of type Function0.

We see that the actual Java implementation of a Function object is an implementation of a Function interface. The only method provided by this interface is invoke, which represents a Function call, so a call to a Function variable can be:

fun receiveFunc(func: () -> Unit) {
    func()
    func.invoke()
}
Copy the code

Currie Currying

Currying is the process of converting a function that originally takes multiple arguments into a series of functions that only take a single function, with the function being called one by one to obtain the final return value.

Let’s say we want to implement a sum of three numbers:

fun nocurrying(a: Int, b: Int, c: Int): Int {
    return a + b + c
}
// The following function does not compile because the body of the function is in the scope of A and BC cannot be found
fun currying(a: Int): (b: Int) -> (c: Int) - >Int)) {return a + b + c
}
// But you can use anonymous functions
fun currying2(a: Int) = fun(b: Int) = fun(c: Int) = a + b + c
// Since anonymous functions are possible, Lambda is easier to implement:
fun currying(a: Int) = { b: Int ->
    { c: Int ->
        a + b + c
    }
}
// A call to currying is:
currying(1) (2) (3)
Copy the code

It is also possible to use extension functions to convert a normal function to a currying function. Functions with three parameters can be of type Function3:

fun nocurrying(a: Int, operation: String, b: Int): Int {
    print("$a $operation $b")
    return 0
}
// Extend Function3 a currying conversion function
fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.curried(a) =
    fun(p1: P1) = fun(p2: P2) = fun(p3: P3) = this(p1, p2, p3)
/ / call
::nocurrying.curried()(1) ("+") (3)
Copy the code

The Unit type:

If a function has no return value, its return value type is Unit.

Kotlin has no concept of a basic data type, so Unit cannot be equal to the void keyword, but rather is analogous to the void wrapper type.

Expressions:

A function is an expression, if-else, try-catch, and so on. The difference between an expression and a statement is that an expression returns a value. More specifically, the result of an expression executed on the right can be assigned to the left.

Such as:

If (x > 1) 2 else 1 // if-else {x:Int -> 2x} // Lambda expression for(x in) of type (Int) -> Int List){println(x)}// for loop expressionCopy the code

Expressions in Kotlin are very similar in that many statements can be converted to expressions. This part of the IDE will have intelligence in many cases, just pay attention to it.

Infix expression:

An infix expression refers to an operator in the middle of an expression. The operator in the middle is called an infix function.

I mention this in particular because infix expressions can make our code more natural and elegant in some cases.

Infix expressions are typical in Kotlin for example:

val map = mapOf<Int.Int> (1 to 2)
Copy the code

There are two syntactic sugars wrapped by Kotlin. The first is to generate a Pair via the infix function to, and the second is to provide a Key and Value to convert a Pair to a Map via mapOf.

To implementation:

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
Copy the code

An extension function declared through Infix, so that you no longer need to go through the Pair constructor, but instead create a Pair object in a more natural way.

string

There are many strings apis, see documentation:

Kotlinlang.org/api/latest/…

Native string: The “”” “XXX “”” “” is supported in higher Java versions, which directly preserves the content format of the string. It is not like the original string concatenation, which needs to handle newlines, tabs, etc. Kotlin supports it directly.

String template: “the value of a is $a”, which effectively replaces Java’s string concatenation with +.

Equals: Content equals equals, references to objects equals ===.

Object-oriented:

Class and object declarations:

A little.

Lazy initialization:

Lazy initialization of val can be used by lazy:

val any:Any by lazy {
    Any()
}
Copy the code

Lazy is declared as follows:

public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }
Copy the code

Actual indicates that this is a platform-specific approach, that different platforms have different implementations, that the three modes represent thread safety, and that the initializations can be performed in parallel, returning the first initialized object. NONE indicates that the thread is unsafe.

In general, we can initialize some fields using NONE mode if we can ensure that they are in the main thread, or wrap a layer:

fun <T> lazyNone(initializer: () -> T): Lazy<T> = lazy(LazyThreadSafetyMode.NONE, initializer)
Copy the code

In general, var declares a variable and must be assigned immediately. We might write code like this, even if notNull is an object that cannot be null:

var notNull: Any? = null
Copy the code

If we wish to delay initialization of the var declaration variable, Delegates. NotNull:

var anyy: Any by Delegates.notNull<Any>()
Copy the code

Note that anyy must be truly initialized before use, otherwise exceptions will be thrown,

Visibility modifier

Resources modified by the Internal keyword are only visible within modules. Modules refer to, for example, gradle Project, where files in a module are compiled together. Java’s Defaut visibility actually allows you to define a package with the same name to call, even if the two classes are no longer under the same package.

package com.a;
class A {}
// In another module, declare A package of the same name to access A
package com.a;
class B {
    A a = null;
}
Copy the code

Classes that are private are visible only in the same Kotlin file.

Multiple inheritance solutions

Neither Java nor Kotlin allow multiple inheritance because of the diamond inheritance problem that multiple inheritance causes, which can easily bog down code maintenance, but there are ways to achieve this effect.

The essence of inheritance is to reuse the parent’s ability description, or directly reuse the parent’s ability, so much inheritance is to reuse multiple “parent” ability

This can be achieved indirectly in the following ways:

  1. The default method and properties of interface.
  2. By combining multiple inner classes, each of which inherits a parent class, the outer class can be combined with the ability to resemble multiple inheritance.
  3. Implemented by delegation.

Delegate implementation examples are as follows:

interface CanFly {
    fun fly(a)
}

open class Flyer : CanFly {
    override fun fly(a){}}interface CanEat {
    fun eat(a)
}

open class Eater : CanEat {
    override fun eat(a){}}class Bird(fly: CanFly, eat: CanEat) : CanFly by fly, CanEat by eat
Copy the code

Note that delegates can only delegate interfaces. Although 2 and 3 seem to implement multiple inheritance, they are more like combining multiple “parent classes” to achieve the effect of multiple inheritance indirectly.