Public number: byte array, hope to help you 🤣🤣

A, Hello World

According to international practice, learning a new language usually starts with Hello World, and this is no exception

package main

fun main(args: Array<String>) {
    println("Hello World")}Copy the code

From this simple function, you can list several differences between Kotlin and Java

  1. Functions can be defined in the outermost layer of a file, without the need to put them in a class
  2. Declare a function with the keyword fun
  3. The parameter type is written after the variable name, which helps omit the type declaration when the type is automatically derived
  4. Arrays are classes. Unlike Java, Kotlin has no special syntax for declaring array types
  5. useprintlnInstead of aSystem.out.println, which is a wrapper around Java standard library functions provided by the Kotlin library
  6. You can omit the semicolon at the end of the code

In addition, the latest version of Kotlin can omit arguments to the main method

Second, the Package

Kotlin files begin with a package statement, and all declarations (classes, functions, and attributes) defined in the file are put into this package. If the declarations defined in other files also have the same packages, this file can use them directly, or import them if the packages are different

Package declarations should be at the top of the source file, followed by import statements

package base

import java.text.SimpleDateFormat
import java.util.*
Copy the code

Kotlin does not discriminate between classes or functions being imported, allowing any kind of declaration to be imported using the import keyword. It is also possible to import all declarations defined in a particular package by adding.* to the package name, which makes not only the classes defined in the package visible, but also the top-level functions and attributes

package learn.package2

val index = 10

fun Test(status: Boolean) = println(status)

class Point(val x: Int.val y: Int) {

    val isEquals1: Boolean
        get() {
            return x == y
        }

    val isEquals2
        get() = x == y

    var isEquals3 = false
        get() = x > y
        set(value) { field = ! value } }Copy the code
package learn.package1

import learn.package2.Point
import learn.package2.Test
import learn.package2.index

fun main(a) {
    val point = Point(10, index)
    Test(true)}Copy the code

While the Java language dictates that classes be placed in a folder directory structure that matches the package structure, Kotlin allows multiple classes to be placed in the same file, with a filename of any choice. Kotlin also doesn’t impose any restrictions on the layout of source files on disk. The package hierarchy doesn’t have to follow the directory hierarchy, but it’s better to follow the Java directory layout and put the source files in the appropriate directories based on the package structure

If a package name conflicts, you can use the AS keyword to locally rename the conflicting items to disambiguate them

import learn.package1.Point
import learn.package2.Point as PointTemp //PointTemp can be used to represent learn.package2.point
Copy the code

Kotlin has a similar concept for renaming existing types called type aliases. A type alias is used to provide an alternative name for an existing type, and if the type name is long, it can be easily remembered by introducing a shorter or more abbreviated name

A type alias does not introduce a new type but still corresponds to its underlying type, so the class type output in the following code is consistent

class Base {

    class InnerClass

}

typealias BaseInner = Base.InnerClass

fun main(a) {
    val baseInner1 = Base.InnerClass()
    val baseInner2 = BaseInner()
    println(baseInner1.javaClass) //class test2.Base$InnerClass
    println(baseInner2.javaClass) //class test2.Base$InnerClass
}
Copy the code

Variables and data types

In Java, most variables are mutable (non-final), meaning that any code that has access to a variable can modify it. In Kotlin, variables can be divided into variable variables (VAR) and immutable variables (val)

There are two keywords for declaring variables:

  • Val (value/varible+final) — Immutable reference Variables declared with val cannot be reassigned after initialization, and correspond to final variables in Java
  • Var (variable) — Variable reference. The value of the var variable can be changed, corresponding to non-final variables in Java

Immutable variables cannot change their state after assignment, so immutable variables are thread-safe because they cannot change, and all threads access the same object, so no access control is required. Developers should use immutable variables whenever possible to make the code more functional

The programming world also advocates a development principle that uses Val, immutable objects, and pure functions whenever possible. This way you can avoid side effects

fun main(a) {
    // A read-only variable is a variable whose value cannot be changed after being assigned
    // Declare an immutable variable of type integer
    val intValue: Int = 100

    // Declare a variable of type double
    var doubleValue: Double = 100.0
}
Copy the code

When declaring a variable, we usually do not need to specify the type of the variable explicitly. This can be inferred automatically by the compiler based on the context. If a read-only variable is declared without an initial value, the type of the variable must be specified and the variable must be initialized under each branch condition before use, otherwise an exception will be reported at compile time

fun main(a) {
    val intValue = 1002222222222
    val doubleValue = 100.0
    val longValue = 100L

    If a read-only variable has no initial value when declared, the variable type must be specified
    val intValue2: Int
    if (false) {
        intValue2 = 10
    }
    println(intValue2) //error, Variable 'intValue2' must be initialized
}
Copy the code

3.1. Basic data types

Unlike Java, Kotlin does not distinguish between the base data type and its wrapper class. In Kotlin, everything is an object, and its member functions and properties can be called on any variable. Kotlin doesn’t have primitive primitive types like those in Java, but types like byte, char, INTEGER, float, or Boolean remain, but all exist as objects

For primitive types, Kotlin has a few special features compared to Java

  • Numbers, characters, and Booleans can be represented as native type values at run time, but they look like normal classes to the developer
  • There is no implicitly widened conversion for numbers, whereas ints can be implicitly converted to longs in Java
  • All variables initialized with an integer value that does not exceed the maximum value of Int are automatically inferred to be Int. If the initial value exceeds the maximum value, it is inferred to be Long. If you want to explicitly specify a value of Long, you can append the L suffix to the value
  • Characters cannot be treated as numbers
  • Octal is not supported
    // In Kotlin, types such as int, long, float, etc. still exist, but as objects

    val intIndex: Int = 100
    // Equivalent, the compiler automatically does type inference
    val intIndex = 100

    // Numeric types are not automatically converted; explicit type conversions are required
    val doubleIndex: Double = intIndex.toDouble()
    // The following code prompts an error that requires an explicit type conversion
    //val doubleIndex: Double = intIndex

    val intValue: Int = 1
    val longValue: Long = 1
    // The following code prompts an error because the two data types are inconsistent and need to be converted to the same type before comparison
    //println(intValue == longValue)

    //Char cannot be treated directly as a number; it needs to be actively converted
    val ch: Char = 'c'
    val charValue: Int = ch.toInt()
    // The following code prompts an error
    //val charValue: Int = ch

    / / binary
    val value1 = 0b00101
    // Hexadecimal
    val value2 = 0x123
Copy the code

In addition, Kotlin’s nullable types cannot be represented by Java’s primitive data types, because NULL can only be stored in variables of Java’s reference types, which means that whenever a nullable version of the primitive data type is used, it will be compiled to the corresponding wrapper type

    // Basic data type
    val intValue_1: Int = 200
    / / wrapper classes
    val intValue_2: Int? = intValue_1
    val intValue_3: Int? = intValue_1
    //== compares numeric equality, so the result is true
    println(intValue_2 == intValue_3)
    //=== = compares references for equality, so the result is false
    println(intValue_2 === intValue_3)
Copy the code

If the value of intValue_1 is 100, you will see that intValue_2 === intValue_3 is compared to true, which leads to Java’s reuse of wrapped class objects

3.2. Strings

Kotlin, like Java, represents strings as strings. Strings are immutable, and you can access individual contained characters using the index operator [], iterate over strings with a for loop, and concatenate strings with +

    val str = "leavesC"
    println(str[1])
    for (c in str) {
        println(c)
    }
    val str1 = str + " hello"
Copy the code

Kotlin supports referencing local variables in string literals by prefixing the variable name with the character $, as well as containing curly bracketed expressions that are automatically evaluated and merged into the string

    val intValue = 100
    // Can include variables directly
    println("intValue value is $intValue") //intValue value is 100
    // Can also contain expressions
    println("(intValue + 100) value is ${intValue + 100}")   //(intValue + 100) value is 200
Copy the code

If you need to represent a literal ($) character in a raw string (which does not support backslash escape), you can use the following syntax:

    val price = "The ${'$'}100.99"
    println(price)  / / $100.99
Copy the code

3.3, arrays,

Arrays in Kotlin are classes with type parameters, whose element types are specified as the corresponding type parameters, represented by the Array class, which defines the get and set functions (which are converted to [] by operator overload convention) and the size attribute, among other things

There are several ways to create arrays:

  1. The arrayOf function creates an array containing the elements specified as arguments to the function
  2. ArrayOfNulls creates an array of a given size with null elements. This can only be used to create arrays containing nullable element types
  3. Call the constructor of the Array class, passing in the size of the Array and a lambda expression that creates each Array element
    // An array of strings containing the given element
    val array1 = arrayOf("leavesC"."Leaf"."https://github.com/leavesC")

    array1[0] = "leavesC"
    println(array1[1])
    println(array1.size)

    // A character array of size 10 with all initial elements null
    val array2 = arrayOfNulls<String>(10)

    // Create an array of strings from "a" to "z"
    val array3 = Array(26) { i -> ('a' + i).toString() }
Copy the code

Note that the type parameter of an Array type always becomes an object type, so declaring Array< Int > will be an Array containing the boxing type (java.lang.INTEGER). If you want to create an array of unboxed primitive data types, you must use a special class for an array of primitive data types

To represent arrays of primitive data types, Kotlin provides several corresponding classes for each primitive data type and makes special optimizations. For example, there are types such as IntArray, ByteArray, BooleanArray, etc. These types are compiled into ordinary Arrays of Java primitive data types, such as int[], byte[], Boolean [], etc. The values in these arrays are stored without boxing. But in the most efficient way possible. Note that IntArray and others are not subclasses of Array

To create an array of primitive data types, there are several ways:

  1. Pass the array size to the constructor of a class of the corresponding type (such as IntArray), which returns an array initialized with the default values of the corresponding base data type
  2. Pass the array size and the lambda used to initialize each element to the constructor of a class of the corresponding type, such as IntArray
  3. The factory function (such as charArrayOf) is passed the value of a variable length parameter to get an array of specified element values
    // Specify the size of the array, and the elements will be the default values for the corresponding base data type (int defaults to 0).
    val intArray = IntArray(5)
    // Specify the array size and the lambda used to initialize each element
    val doubleArray = DoubleArray(5) { Random().nextDouble() }
    // Take the values of variable-length parameters to create an array of those values
    val charArray = charArrayOf('H'.'e'.'l'.'l'.'o')
Copy the code

3.4, Any and Any?

The Any type is the supertype of all kotlin non-null types, including basic data types like Int

If you assign a value of the base data type to a variable of type Any, it is automatically boxed

val any: Any = 100
println(any.javaClass) //class java.lang.Integer
Copy the code

If you want a variable to store all possible values, including NULL, you need to use Any?

val any: Any? = null
Copy the code

3.5, the Unit

The Unit type in Kotlin is similar to void in Java and can be used when the function returns no value

fun check(a): Unit{}// If the return value is Unit, you can omit this declaration
fun check(a){}Copy the code

Unit is a complete type and can be used as a type parameter, but void is not

interface Test<T> {
    fun test(a): T
}

class NoResultClass : Test<Unit> {
    
    // Returns Unit, but the type specification can be omitted, and the function does not need to return explicitly
    override fun test(a){}}Copy the code

3.6, Nothing

The Nothing type has no value and only makes sense when used as a function return value, or as a type parameter to the return value of a generic function. Nothing can be used to indicate that a function will not terminate normally, helping the compiler diagnose the code

The compiler knows that a function that returns a value of type Nothing never terminates properly, so the compiler will infer that name1’s type is non-null because name1’s branch handling when it is null will always throw an exception

data class User(val name: String?)

fun fail(message: String): Nothing {
    throw IllegalStateException(message)
}

fun main(a) {
    val user = User("leavesC")
    val name = user.name ?: fail("no name")
    println(name) //leavesC

    val user1 = User(null)
    val name1 = user1.name ?: fail("no name")
    println(name1.length) //IllegalStateException
}
Copy the code

Four, functions,

Functions in Kotlin begin with the keyword fun, followed by the function name, followed by a list of arguments wrapped in parentheses, followed by the return value type if the function has a return value, separated from the argument list by a colon

        // Fun is used to declare a function, getNameLastChar is the function name
        // Empty parentheses indicate that the function takes no arguments, and Char indicates that the return type of the function is a character
        fun getNameLastChar(a): Char {
            return name.get(name.length - 1)}Copy the code
        // Takes two different types of arguments, one String and one Int
        // The return value is Int
        fun test1(str: String, int: Int): Int {
            return str.length + int
        }
Copy the code

In addition, the return value type of the expression function body can be omitted, and the return value type can be inferred automatically. Such functions defined with single-line expressions and equal signs are called expression function bodies. However, in the general case of a code block function body that returns a value, the return type and return statement must be explicitly written

        //getNameLastChar's return type and return keyword can be omitted
        // The return value type can be inferred by the compiler from the context
        // Therefore, the function can be shortened to the following form
        fun getNameLastChar(a) = name.get(name.length - 1)
Copy the code

If the function has no meaningful return value, it can be declared as Unit, or it can be omitted

All three of the following are equivalent

        fun test(str: String, int: Int): Unit {
            println(str.length + int)
        }

        fun test(str: String, int: Int) {
            println(str.length + int)
        }

        fun test(str: String, int: Int) = println(str.length + int)
Copy the code

4.1 named Parameters

To make the code more readable, Kotlin allows you to use named parameters. That is, when a function is called, the name of the function’s parameters can be specified together to express the meaning and function of the parameters, but once you specify the name of one parameter, all subsequent parameters need to be named

fun main(a) {
    // Error: After specifying the name of one parameter, all subsequent parameters need to be named
    //compute(index = 110, "leavesC")
    compute(index = 120, value = "leavesC")
    compute(130, value = "leavesC")}fun compute(index: Int, value: String){}Copy the code

4.2 default Parameter Values

You can specify default values for parameters when declaring functions to avoid creating overloaded functions

fun main(a) {
    compute(24)
    compute(24."leavesC")}fun compute(age: Int, name: String = "leavesC"){}Copy the code

For this example, if the usual calling syntax dictates that the arguments are given in the order defined in the function declaration, only the last arguments can be omitted

fun main(a) {
    // Error, cannot omit parameter name
    // compute(24)
    / / compute (24100).
    // The value argument can be omitted
    compute("leavesC".24)}fun compute(name: String = "leavesC", age: Int, value: Int = 100) {}
Copy the code

If you use named parameters, you can omit any parameters that have default values, and you can pass in the desired parameters in any order

fun main(a) {
    compute(age = 24)
    compute(age = 24, name = "leavesC")
    compute(age = 24, value = 90, name = "leavesC")
    compute(value = 90, age = 24, name = "leavesC")}fun compute(name: String = "leavesC", age: Int, value: Int = 100){}Copy the code

4.3. Variable parameters

Mutable arguments allow us to pack any number of arguments into an array and pass them to a function. Kotlin’s syntax is different from Java’s, declaring mutable arguments by using the vararg keyword instead

For example, the following function calls are all correct

fun main(a) {
    compute()
    compute("leavesC")
    compute("leavesC"."Leaves should be leaves.")
    compute("leavesC"."Leaves should be leaves."."Leaf")}fun compute(vararg name: String) {
    name.forEach { println(it) }
}
Copy the code

Whereas in Java, arrays can be passed directly to mutable arguments, Kotlin requires explicit unpacking of arrays so that each array element can be called as a separate argument in a function. This feature is called the expansion operator, and is used by prepping array arguments with a *

fun main(a) {
    val names = arrayOf("leavesC"."Leaves should be leaves."."Leaf")
    compute(* names)
}

fun compute(vararg name: String) {
    name.forEach { println(it) }
}
Copy the code

4.4. Local functions

Kotlin supports nesting functions within functions, and the nested functions are called local functions

fun main(a) {
    compute("leavesC"."country")}fun compute(name: String, country: String) {
    fun check(string: String) {
        if (string.isEmpty()) {
            throw IllegalArgumentException("Parameter error")
        }
    }
    check(name)
    check(country)
}
Copy the code

Expressions and conditional loops

5.1. Statements and Expressions

It is important to distinguish between “statement” and “expression”. Statements are code that can be executed independently and have practical effects, in the form of assignment logic, printing operations, flow control, etc. Flow control (if, while, for) in Java are all statements. An expression can be a value, a variable, a constant, an operator, or a combination of them. An expression can be thought of as a statement containing a return value

For example, the following assignment operations, flow controls, and printouts are statements that exist as a whole and do not contain a return value

    val a = 10
    for (i in 0..a step 2) {
        println(i)
    }
Copy the code

Let’s look at some more examples of expressions

1       // The literal expression returns 1
++1     // increment, return 2
// Unlike Java, if in Kotlin exists as an expression that can have a return value
fun getLength(str: String?).: Int {
    return if (str.isNullOrBlank()) 0 else str.length
}
Copy the code

5.2. If expression

The branch of if can be a code block, with the final expression as the return value of that block

    val maxValue = if (20 > 10) {
        println("maxValue is 20")
        20
    } else {
        println("maxValue is 10")
        10
    }
    println(maxValue) / / 20
Copy the code

The following code explicitly shows that the return value of if can be used to replace the ternary operator in Java, so Kotlin does not have a ternary operator

    val list = listOf(1.4.10.4.10.30)
    val value = if (list.size > 0) list.size else null
    println(value)  / / 6
Copy the code

If the if expression branch is used to execute a command, then the return value type is Unit, and the if statement looks like Java

    val value1 = if (list.size > 0) println("1") else println("2")
    println(value1.javaClass)   //class kotlin.Unit
Copy the code

If you treat an if as an expression rather than a statement (for example, returning its value or assigning it to a variable), the expression needs to have an else branch

5.3. When expressions

The WHEN expression is similar to switch/ Case in Java, but much more powerful. When can be used as either an expression or a statement. When compares parameters to all of the branch criteria in order until one of the branches meets the criteria, and then it runs the expression on the right. If when is used as an expression, the value of the eligible branch is the value of the entire expression; if used as a statement, the value of individual branches is ignored. The difference with Java’s Switch/Case is that the argument to the When expression can be of any type, and the branch can also be a condition

As with if, each branch of the WHEN expression can be a code block, and its value is the value of the last expression in the code block, which will be evaluated to the else branch if none of the other branches satisfy the condition

If when is used as an expression, there must be an else branch, unless the compiler can detect that all possible cases have been covered. If many branches need to be treated in the same way, you can group multiple branch conditions together, separated by commas

    val value = 2
    when (value) {
        in 4.9. -> println("in 4.. 9") // interval judgment
        3 -> println("value is 3")    // Equality judgment
        2.6 -> println("value is 2 or 6")    // Multi-value equality judgment
        is Int -> println("is Int")   // Type judgment
        else -> println("else")       // If none of the above conditions are met, else is executed
    }
Copy the code
fun main(a) {
    // Returns when
    fun parser(obj: Any): String =
            when (obj) {
                1 -> "value is 1"
                "4" -> "value is string 4"
                is Long -> "value type is long"
                else -> "unknown"
            }
    println(parser(1))
    println(parser(1L))
    println(parser("4"))
    println(parser(100L))
    println(parser(100.0))
}

value is 1
value type is long
value is string 4
value type is long
unknown
Copy the code

In addition, the When loop can take no parameters

    when {
        1 > 5 -> println("1 > 5")
        3 > 1 -> println(3 > 1 "")}Copy the code

5.4. For loop

The most similar form of the for loop in Java is

    val list = listOf(1.4.10.34.10)
    for (value in list) {
        println(value)
    }
Copy the code

Traversal by index

    val items = listOf("H"."e"."l"."l"."o")
    // Iterate through the List by index
    for (index in items.indices) {
        println("${index}The corresponding values are:${items[index]}")}Copy the code

You can also get the current index and corresponding values in each loop

    val list = listOf(1.4.10.34.10)
    for ((index, value) in list.withIndex()) {
        println("index : $index , value :$value")}Copy the code

You can also customize the loop interval

    for (index in 2.10.) {
        println(index)
    }
Copy the code

5.5. While and do/while loops

While and do/while differ little from those in Java

    val list = listOf(1.4.15.2.4.10.0.9)
    var index = 0
    while (index < list.size) {
        println(list[index])
        index++
    }
Copy the code
    val list = listOf(1.4.15.2.4.10.0.9)
    var index = 0
    do {
        println(list[index])
        index++
    } while (index < list.size)
Copy the code

5.6. Returns and jumps

Kotlin has three structured jump expressions:

  • By default, return returns from the function that most directly surrounds it or from an anonymous function
  • Break terminates the loop that most directly surrounds it
  • Continue the next loop that most directly surrounds it

In Kotlin, any expression can be marked with a label, which is formatted as an identifier followed by an @ sign. For example, ABC @ and fooBar@ are valid labels

fun main(a) {
    fun1()
}

fun fun1(a) {
    val list = listOf(1.4.6.8.12.23.40)
    loop@ for (it in list) {
        if (it == 8) {
            continue
        }
        if (it == 23) {
            break@loop
        }
        println("value is $it")
    }
    println("function end")}Copy the code
value is 1
value is 4
value is 6
value is 12
function end
Copy the code

Kotlin has function literals, local functions, and object expressions. So Kotlin’s functions can be nested

The tag-restricted return allows us to return from outer functions, and one of the most important uses is to return from lambda expressions. It is often more convenient to use an implicit label that has the same name as the function that accepts the lambda

fun main(a) {
    fun1()
    fun2()
    fun3()
    fun4()
    fun5()
}

fun fun1(a) {
    val list = listOf(1.4.6.8.12.23.40)
    list.forEach {
        if (it == 8) {
            return
        }
        println("value is $it")
    }
    println("function end")

// value is 1
// value is 4
// value is 6
}

fun fun2(a) {
    val list = listOf(1.4.6.8.12.23.40)
    list.forEach {
        if (it == 8) {
            return@fun2
        }
        println("value is $it")
    }
    println("function end")

// value is 1
// value is 4
// value is 6
}

// The local returns used in fun3() and fun4() are similar to using continue in a regular loop
fun fun3(a) {
    val list = listOf(1.4.6.8.12.23.40)
    list.forEach {
        if (it == 8) {
            return@forEach
        }
        println("value is $it")
    }
    println("function end")
    
// value is 1
// value is 4
// value is 6
// value is 12
// value is 23
// value is 40
// function end
}

fun fun4(a) {
    val list = listOf(1.4.6.8.12.23.40)
    list.forEach loop@{
        if (it == 8) {
            return@loop
        }
        println("value is $it")
    }
    println("function end")

// value is 1
// value is 4
// value is 6
// value is 12
// value is 23
// value is 40
// function end
}

fun fun5(a) {
    listOf(1.2.3.4.5).forEach(fun(value: Int) {
        if (value == 3) {
            // Local returns to the caller of the anonymous function, namely the forEach loop
            return
        }
        println("value is $value")
    })
    println("function end")}Copy the code

Six, interval

The Ranges expression uses a.. Operator to declare a closed interval, which is used to define the implementation of a RangTo method

The following three forms of declaration are equivalent

    var index = 5
    
    if (index >= 0 && index <= 10) {}if (index in 0.10.) {}if (index in 0.rangeTo(10)) {}Copy the code

When numeric ranges are iterated over, the compiler optimizes them by converting them to the same bytecode as the Java for loop that uses index

Ranges grow by default, so code like the following will not be executed

    for (index in 10.. 0) {
        println(index)
    }
Copy the code

You can change this to decrement by using the downTo function

    for (index in 10 downTo 0) {
        println(index)
    }
Copy the code

We can use step in ranges to define the incrementing or incrementing length of each loop:

    for (index in 1.8. step 2){
        println(index)
    }
    for (index in 8 downTo 1 step 2) {
        println(index)
    }
Copy the code

If you want to declare an open interval, use the until function:

    for (index in 0 until 4){
        println(index)
    }
Copy the code

The extension function reversed() can be used to return a sequence with an interval reversed

    val rangeTo = 1.rangeTo(3)
    for (i in rangeTo) {
        println(i) //1  2  3
    }
    for (i in rangeTo.reversed()) {
        println(i) //3  2  1
    }
Copy the code

7. Modifier

7.1 Final and OEPN

Classes and methods in Kotlin are final by default, that is, non-inheritable. If you want to allow subclasses of a class to be created, you need to use the open modifier to identify that class. In addition, you need to add the open modifier to every property and method that you want to override

open class View {
    open fun click(a){}// cannot be overridden in subclasses
    fun longClick(a){}}class Button : View() {
    override fun click(a) {
        super.click()
    }
}
Copy the code

If you override a member of a base class or interface, the overridden member is also open by default. For example, if the Button class is open, its subclasses can also override their click() method

open class Button : View() {
    override fun click(a) {
        super.click()
    }
}

class CompatButton : Button() {
    override fun click(a) {
        super.click()
    }
}
Copy the code

If you do not want a subclass of the class to override the implementation of the method, you can explicitly mark the overridden member as final

open class Button : View() {
    override final fun click(a) {
        super.click()
    }
}
Copy the code

7.2, public

The public modifier is the lowest-limiting modifier and is the default modifier. If a member defined as public is included in a private class, the member is not visible outside the class

7.3, protected

The protected modifier can only be used on members of a class or interface. In Java, you can access a protected member from the same package, but for Kotlin, the protected member is only visible in the class and its subclasses. In addition, protected does not apply to top-level declarations

7.4, internal

A package member, defined as internal, is visible to the entire Module in which it resides. If it is a member of another domain, it depends on the visibility of that domain. For example, if we write a private class, the visibility of its internal modified function is limited to the visibility of the class in which it resides

We can access the internal modifier in the same Module, but no other Module can access the internal modifier. This modifier can be used in the open source library. This avoids confusion with external reference libraries

According to Jetbrains, a module should be a single functional unit, thought of as a collection of kotlin files compiled together, that can be independently compiled, run, tested, and debugged. Equivalent to the Module referenced by the main project in Android Studio, a different project in Eclipse in a workspace

7.5, private

The private modifier is the most restrictive modifier. Kotlin allows the use of private visibility in top-level declarations, including classes, functions, and attributes. This means that it is visible only in its own file, so if you declare a class private, you cannot use it outside of where the class is defined. In addition, if you use the private modifier in a class, access is restricted to that class and subclasses that inherit from that class cannot use it. So if classes, objects, interfaces, and so on are defined as private, they are visible only to the file in which they are defined. If defined in a class or interface, they are visible only to that class or interface

7.6,

The modifier Members of the class The top statement
Public (default) Visible everywhere Visible everywhere
internal Visible in module Visible in module
protected Visible in subclasses
private Seen in class Visible in the file

Viii. Air safety

8.1. Cavitation

In Kotlin, the type system classifies a reference as either null-capable (null-capable references) or non-null-capable (non-null references). For example, a regular variable of type String cannot point to NULL

    var name: String = "leavesC"
    // Error compiling
    //name = null
Copy the code

If you want a variable to store null references, you explicitly place a question mark after the type name

    var name: String? = "leavesC"
    name = null
Copy the code

A question mark can be appended to any type to indicate that variables of that type can store null references: Int? , Doubld? And Long? Etc.

Kotlin’s explicit support for nullable types helps prevent exception problems caused by NullPointerException. The compiler does not allow nullable variables to be called directly without null checking their properties. This enforcement forces developers to consider the range of variables that can be assigned and branch for each case early in the code

fun check(name: String?).: Boolean {
    //error, the compiler does not allow the name attribute to be called without a null check
    return name.isNotEmpty()
}
Copy the code

The correct way to do this is to explicitly null check

fun check(name: String?).: Boolean {
    if(name ! =null) {
        return name.isNotEmpty()
    }
    return false
}
Copy the code

8.2. Safe call operator:? .

Safe call operators:? . Allows a null check and a method call to be combined into one operation. If the variable value is not empty, the method or property will be called, otherwise null will be returned

For example, the following two forms are exactly the same:

fun check(name: String?). {
    if(name ! =null) {
        println(name.toUpperCase())
    } else {
        println(null)}}fun check(name: String?).{ println(name? .toUpperCase()) }Copy the code

8.3. Elvis operator:? :

Elvis operator:? : Used instead of? The Elvis operator accepts two operands. If the first is not null, the result is the value of the operation, and if the first is null, the result is the second operand

For example, the following two forms are exactly the same:

fun check(name: String?). {
    if(name ! =null) {
        println(name)
    } else {
        println("default")}}fun check(name: String?).{ println(name ? :"default")}Copy the code

8.4. Safe conversion operator: as?

Safe conversion operator: as? Used to convert a value to the specified type and return NULL if the value does not fit that type

fun check(any: Any?). {
    val result = any as? String println(result ? : println("is not String"))}Copy the code

8.5 non-null assertion:!!

Non-null assertions are used to convert any value to a non-null type, and if a non-null assertion is made on a null value, an exception is thrown

fun main(a) {
    var name: String? = "leavesC"
    check(name) / / 7

    name = null
    check(name) //kotlinNullPointerException
}

fun check(name: String?).{ println(name!! .length) }Copy the code

8.6. Extension of nullable types

Defining extension functions for nullable types is a more powerful way to handle null values, allowing calls that receive null to be handled in that function, rather than calling its methods after ensuring that the variable is not null

For example, the following methods can be called normally without null-pointer exceptions

    val name: String? = null
    println(name.isNullOrEmpty()) //true
Copy the code

IsNullOrEmpty () is a nullable CharSequence. Extension function defined in a method that already handles the null case of the method caller

@kotlin.internal.InlineOnly
public inline funCharSequence? .isNullOrEmpty(a): Boolean {
    contract {
        returns(false) implies (this@isNullOrEmpty! =null)}return this= =null || this.length == 0
}
Copy the code

8.7. Platform type

The platform type is one of Kotlin’s balanced designs for Java. Kotlin divides the types of objects into nullable and non-nullable types. However, all object types of the Java platform are nullable. When referencing Java variables in Kotlin, if all variables are classified as nullable, there will be many more NULL checks. If both types are considered non-nullable, it is easy to write code that ignores the risks of NPE. To balance the two, Kotlin introduced platform types, where Java variable values referenced in Kotlin can be treated as nullable or non-nullable types, leaving it up to the developer to decide whether or not to null-check

Nine, type check and conversion

9.1. Type Checking

Is with the! The is operator is used at run time to check whether an object conforms to a given type:

fun main(a) {
    val strValue = "leavesC"
    parserType(strValue) //value is String , length : 7
    val intValue = 100
    parserType(intValue) //value is Int , toLong : 100
    val doubleValue = 100.22
    parserType(doubleValue) //value ! is Long
    val longValue = 200L
    parserType(longValue) //unknown
}

fun parserType(value: Any) {
    when (value) {
        is String -> println("value is String , length : ${value.length}")
        is Int -> println("value is Int , toLong : ${value.toLong()}")!is Long -> println("value ! is Long")
        else -> println("unknown")}}Copy the code

9.2. Intelligent transformation

In many cases, explicit conversion operators are not required in Kotlin because the compiler tracks is checks for immutable values as well as explicit conversions and automatically inserts secure conversions when needed

For example, in the following example, when a value is judged to be a String, the value can be treated as a String variable and its internal properties can be called

fun main(a) {
    val strValue = "leavesC"
    parserType(strValue) //value is String , length : 7

    val intValue = 100
    parserType(intValue) //value is Int , toLong : 100

    val doubleValue = 100.22
    parserType(doubleValue) //value ! is Long

    val longValue = 200L
    parserType(longValue) //unknown
}

fun parserType(value: Any) {
    when (value) {
        is String -> println("value is String , length : ${value.length}")
        is Int -> println("value is Int , toLong : ${value.toLong()}")!is Long -> println("value ! is Long")
        else -> println("unknown")}}Copy the code

The compiler specifies that variables be intelligently converted to the appropriate type, depending on the context

    if (value !is String) return
    // If value is not a String, it is returned directly, so you can access its length property directly
    println(value.length)

    / / | | the value on the right side of the automatically be implicitly converted to strings, so you can directly access the length attribute
    if (value !is String || value.length > 0) {}// The value to the right of && is automatically converted implicitly to a string, so its length property can be accessed directly
    if (value is String && value.length > 0) {}Copy the code

Unsafe conversion operators

If conversion is not possible, the conversion operator as throws an exception. Therefore, we call them unsafe conversion operators

fun main(a) {
    parserType("leavesC") //value is String , length is 7
    parserType(10) // A ClassCastException will be thrown
}

fun parserType(value: Any) {
    val tempValue = value as String
    println("value is String , length is ${tempValue.length}")}Copy the code

Note that NULL cannot be converted to a String variable because the type is not nullable

So the following transformation throws an exception

    val x = null
    val y: String = x as String TypeCastException is thrown
Copy the code

To match security, types that can be converted are declared nullable

    val x = null
    val y: String? = x as String?
Copy the code

9.4. Secure conversion operators

You can use the secure conversion operator as? To avoid throwing an exception during conversion, which returns NULL on failure

    val x = null
    val y: String? = x as? String
Copy the code

Although the above example as? To the right of is a non-null String, but the result of the conversion is nullable

Ten, class,

10.1. Basic Concepts

The idea of a class is to encapsulate data and the code that processes it into a single entity. In Java, data is stored in a private field that is accessed or modified by providing accessor methods: getters and setters

The Point class contains a lot of repetitive code: assigning parameters to fields with the same name through a constructor and obtaining property values through a getter

public final class Point {

   private final int x;
   
   private final int y;
   
   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }

   public final int getX(a) {
      return this.x;
   }

   public final int getY(a) {
      return this.y; }}Copy the code

Using Kotlin to declare the Point class requires only one line of code, which is exactly the same

class Point(val x: Int.val y: Int)
Copy the code

Kotlin also uses the keyword class to declare a class. The class declaration consists of the class name, its header (specifying its type parameters, main constructor, and so on), and the body of the class surrounded by curly braces. Both the head and body are optional. In addition, classes in Kotlin are publish (public) and final (non-inheritable) by default

Kotlin distinguishes between primary constructors (declared outside the class body) and secondary constructors (declared inside the class body). A class can have one primary constructor and multiple secondary constructors, as well as allowing additional initialization logic to be added to the init code block

10.2. Primary constructor

The primary constructor is part of the class header, followed by the class name (and optional type arguments). The arguments to the primary constructor can be either mutable (var) or read-only (val).

class Point constructor(val x: Int.val y: Int) {}Copy the code

If the main constructor does not have any annotations or visibility modifiers, omit the constructor keyword

class Point(val x: Int.val y: Int) {}// If the class body is not included, the curly braces can be omitted
class Point(val x: Int.val y: Int)
Copy the code

If the constructor has annotations or visibility modifiers, the constructor keyword is required and those modifiers precede it

class Point public @Inject constructor(val x: Int.val y: Int) {}Copy the code

The primary constructor cannot contain any code. Initialized code can be placed in initializer blocks prefixed with the init keyword. Initializer blocks contain the code that was executed when the class was created. You can also declare more than one block of initialization statements in a class, if necessary. Note that the constructor argument, if modified with val/var, is declaring a global property of the same name inside the class. Without the val/var modifier, the constructor can only be referenced when the init function block and the global property are initialized

In addition, to create an instance of a class, you don’t need to use the New keyword in Java, just call the constructor like a normal function

class Point(val x: Int.val y: Int) {

    init {
        println("initializer blocks , x value is: $x , y value is: $y")}}fun main(a) {
    Point(1.2) // initializer blocks , x value is: 1 , y value is: 2
}
Copy the code

The arguments to the main constructor can also be used in the property initializer declared in the body of the class

class Point(val x: Int.val y: Int) {

    private val localX = x + 1

    private val localY = y + 1

    init {
        println("initializer blocks , x value is: $x , y value is: $y")
        println("initializer blocks , localX value is: $localX , localY value is: $localY")}}fun main(a) {
    Point(1.2)
    //initializer blocks , x value is: 1 , y value is: 2
    //initializer blocks , localX value is: 2 , localY value is: 3
}
Copy the code

10.3. Subconstructors

Classes can also declare subconstructors that contain the prefix constructor. If a class has a primary constructor, each subconstructor needs to delegate directly to the primary constructor or indirectly to another subconstructor, specifying this keyword

class Point(val x: Int.val y: Int) {

    private val localX = x + 1

    private val localY = y + 1

    init {
        println("initializer blocks , x value is: $x , y value is: $y")
        println("initializer blocks , localX value is: $localX , localY value is: $localY")}constructor(base: Int) : this(base + 1, base + 1) {
        println("constructor(base: Int)")}constructor(base: Long) : this(base.toInt()) {
        println("constructor(base: Long)")}}fun main(a) {
    Point(100)
    //initializer blocks , x value is: 101 , y value is: 101
    //initializer blocks , localX value is: 102 , localY value is: 102
    //constructor(base: Int)
    Point(100L)
    //initializer blocks , x value is: 101 , y value is: 101
    //initializer blocks , localX value is: 102 , localY value is: 102
    //constructor(base: Int)
    //constructor(base: Long)
}
Copy the code

The code in the initialization block is actually part of the primary constructor, and the primary constructor is delegated as the first statement of the secondary constructor, so all the code in the initialization block is executed before the secondary constructor body. Even if the class does not have a primary constructor, this delegate occurs implicitly and the initialization block is still executed. If a non-abstract class does not declare any (primary or secondary) constructors, a public primary constructor with no arguments is generated by default

10.4, attributes,

In Java, the combination of a field and its accessor is called a property. In Kotlin, properties are a first-class language feature, completely replacing fields and accessor methods. Declaring a property in a class uses the same val and var keywords as declaring a variable. The val variable has only a getter, and the var variable has both a getter and a setter

fun main(a) {
    val user = User()
    println(user.name)
    user.age = 200
}

class User() {

    val name: String = "leavesC"

    var age: Int = 25

}
Copy the code

10.5. Custom Accessors

The default implementation logic for accessors is simple: create a field that stores the value, and getters that return the value of the property and setters that update the value of the property. You can also customize accessors if necessary

For example, three properties with custom accessors are declared below

class Point(val x: Int.val y: Int) {

    val isEquals1: Boolean
        get() {
            return x == y
        }

    val isEquals2
        get() = x == y

    var isEquals3 = false
        get() = x > y
        set(value) { field = ! value } }Copy the code

If you only need to change the visibility of an accessor or add annotations to it, you can define the accessor without defining its implementation

fun main(a) {
    val point = Point(10.10)
    println(point.isEquals1)
    // The following code will report an error
    //point.isEquals1 = true
}

class Point(val x: Int.val y: Int) {

    var isEquals1: Boolean = false
        get() {
            return x == y
        }
        private set
    
}
Copy the code

10.6. Delay initialization

Generally, attributes of non-empty types must be initialized in the constructor, but this is inconvenient for projects using a dependency injection framework like Dagger2. To deal with this, you can mark the attribute with the LateInit modifier, which tells the compiler that the attribute will be initialized at a later time

Attributes or variables modified with LateInit must be of a non-null type and cannot be primitive

class Point(val x: Int.val y: Int)

class Example {

    lateinit var point: Point

    var point2: Point

    constructor() {
        point2 = Point(10.20)}}Copy the code

If an uninitialized lateInit variable is accessed, an exception message is thrown with the specific reason (the variable is not initialized)

Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property point has not been initialized
Copy the code

11. Classification of classes

11.1. Abstract Classes

Classes declared as abstract can contain member methods that have no implementation body and are also marked with abstract. Such classes are called abstract classes, and methods that contain no implementation body are called abstract methods

Furthermore, we do not need to annotate an abstract class or method with open, because this is declared by default

abstract class BaseClass {
    abstract fun fun1(a)
}

class Derived : BaseClass() {
    override fun fun1(a){}}Copy the code

11.2. Data classes

Data classes are a very powerful class that avoids repeating the template code used in Java to store state but manipulate very simple POJOs, often providing only simple getters and setters for accessing their properties

Defining a new data class is very simple, for example

data class Point(val x: Int.val y: Int)
Copy the code

By default, the data class generates the following methods for all properties declared in the main constructor

  • Getters, setters (var required)
  • ComponentN (). Correspond in the order in which the main constructor attributes are declared
  • copy()
  • toString()
  • hashCode()
  • equals()

To ensure consistent and meaningful behavior in generated code, data classes must meet the following requirements:

  • The main constructor needs to take one argument
  • All arguments to the main constructor need to be marked as val or var
  • Data classes cannot be abstract, open, sealed, or internal

You can decompilate the Java implementation of the Point class using IDEA to see its internal implementation

public final class Point {
   private final int x;
   private final int y;

   public final int getX(a) {
      return this.x;
   }

   public final int getY(a) {
      return this.y;
   }

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }

   public final int component1(a) {
      return this.x;
   }

   public final int component2(a) {
      return this.y;
   }

   @NotNull
   public final Point copy(int x, int y) {
      return new Point(x, y);
   }

   // $FF: synthetic method
   // $FF: bridge method
   @NotNull
   public static Point copy$default(Point var0, int var1, int var2, int var3, Object var4) {
      if ((var3 & 1) != 0) {
         var1 = var0.x;
      }

      if ((var3 & 2) != 0) {
         var2 = var0.y;
      }

      return var0.copy(var1, var2);
   }

   public String toString(a) {
      return "Point(x=" + this.x + ", y=" + this.y + ")";
   }

   public int hashCode(a) {
      return this.x * 31 + this.y;
   }

   public boolean equals(Object var1) {
      if (this! = var1) {if (var1 instanceof Point) {
            Point var2 = (Point)var1;
            if (this.x == var2.x && this.y == var2.y) {
               return true; }}return false;
      } else {
         return true; }}}Copy the code

Data classes simplify many common operations that can be easily performed: formatting output variable values, mapping objects to variables, comparing equality between variables, copying variables, and so on

fun main(a) {
    val point1 = Point(10.20)
    val point2 = Point(10.20)
    println("point1 toString() : $point1") //point1 toString() : Point(x=10, y=20)
    println("point2 toString() : $point2") //point2 toString() : Point(x=10, y=20)

    val (x, y) = point1
    println("point1 x is $x,point1 y is $y") //point1 x is 10,point1 y is 20

    // In Kotlin, "==" corresponds to Java's equals method
    // "===" is equivalent to the Java "==" method
    println("point1 == point2 : ${point1 == point2}") //point1 == point2 : true
    println("point1 === point2 : ${point1 === point2}") //point1 === point2 : false

    val point3 = point1.copy(y = 30)
    println("point3 toString() : $point3") //point3 toString() : Point(x=10, y=30)
}
Copy the code

Note that the toString(), equals(), hashCode(), copy() and other methods of the data class only take into account the properties declared in the main constructor, so there may be some unexpected results when comparing two data class objects

data class Point(val x: Int) {

    var y: Int = 0

}

fun main(a) {
    val point1 = Point(10)
    point1.y = 10

    val point2 = Point(10)
    point2.y = 20

    println("point1 == point2 : ${point1 == point2}") //point1 == point2 : true
    println("point1 === point2 : ${point1 === point2}") //point1 === point2 : false
}
Copy the code

11.3. Sealing

Sealed classes are used to limit the number of subclasses that may be created by a class. Direct subclasses of a class may only be defined in the same file as Sealed. Indirect inheritors of Sealed classes may be defined in other files. Avoid potential bugs due to code changes, and the constructors of sealed classes can only be private

For example, for the View class, its subclasses can only be defined in the same file. The Sealed modifier also implicitly indicates that the class is open, so there is no need to explicitly add the open modifier

sealed class View {

    fun click(a){}}class Button : View() {}class TextView : View() {}Copy the code

Since the Sealed subclasses are controllable to the compiler, there is no need to provide the else default branch if all of the Sealed subclasses are handled in the WHEN expression. Even if a View subclass is added later due to business changes, the compiler will detect that the check method lacks branch checking, so the check method is type-safe

fun check(view: View): Boolean {
    when (view) {
        is Button -> {
            println("is Button")
            return true
        }
        is TextView -> {
            println("is TextView")
            return true}}}Copy the code

11.4. Enumeration classes

Kotlin also provides an implementation of enumerations, which require more use of the class keyword than Java does to declare enumerations

enum class Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
Copy the code

Enumerations can declare parameters

enum class Day(val index: Int) {
    SUNDAY(0), MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6)}Copy the code

In addition, enumerations can also implement interfaces

interface OnChangedListener {

    fun onChanged(a)

}

enum class Day(val index: Int) : OnChangedListener {
    SUNDAY(0) {
        override fun onChanged(a) {

        }
    },
    MONDAY(1) {
        override fun onChanged(a){}}}Copy the code

Enumerations also contain some common functions

fun main(a) {
    val day = Day.FRIDAY
    / / get the value
    val value = day.index  / / 5
    // Get the corresponding enumeration value from String
    val value1 = Day.valueOf("SUNDAY") //SUNDAY
    // Get an array containing all enumerated values
    val value2 = Day.values()
    // Get the enumeration name
    val value3 = Day.SUNDAY.name //SUNDAY
    // Get the location of the enumeration declaration
    val value4 = Day.TUESDAY.ordinal / / 2
}
Copy the code

11.5. Anonymous Inner Classes

Use object expressions to create anonymous inner class instances

interface OnClickListener {

    fun onClick(a)

}

class View {

    fun setClickListener(clickListener: OnClickListener){}}fun main(a) {
    val view = View()
    view.setClickListener(object : OnClickListener {
        override fun onClick(a){}})}Copy the code

11.6. Nested Classes

In Kotlin, classes redefined inside classes default to nested classes, which do not contain implicit references to external classes

class Outer {

    private val bar = 1

    class Nested {
        fun foo1(a) = 2
        / / error
        //fun foo2() = bar}}fun main(a) {
    val demo = Outer.Nested().foo1()
}
Copy the code

The above code is decompiled through IDEA to see its internal Java implementation

Foo2 () cannot access a non-static member of an external class. It does not need to declare Outer to point to a Nested class

public final class Outer {
   private final int bar = 1;

   public static final class Nested {
      public final int foo1() {
         return 2; }}}public final class MainkotlinKt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args"); int demo = (new Outer.Nested()).foo1(); }}Copy the code

11.7. Inner Classes

If you need to access a member of an external class, you use the inner modifier to mark the nested class. This is called an inner class. Inner classes implicitly hold references to external classes

class Outer {

    private val bar = 1

    inner class Nested {
        fun foo1(a) = 2
        fun foo2(a) = bar
    }
}

fun main(a) {
    val demo = Outer().Nested().foo2()
}
Copy the code

Take a look at the internal Java implementation

When a Nested class is declared by using inner, it is declared as a non-static inner class. Therefore, foo2() can access the non-static members of its Outer class

public final class Outer {
   private final int bar = 1;

   public final class Nested {
      public final int foo1() {
         return 2;
      }

      public final int foo2() {
         return Outer.this.bar; }}}public final class MainkotlinKt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args"); int demo = (new Outer().new Nested()).foo2(); }}Copy the code
Class A is declared in class B In Java In the kotlin
Nested classes (do not store references to external classes) static class A class A
Inner class (stores references to external classes) class A inner class A

Xii. Interface

12.1. Abstract methods vs. default methods

The interface in Kotlin is similar to that in Java 8. It can contain the definition of abstract methods and the implementation of non-abstract methods. The default keyword is not required to annotate non-abstract methods with default implementation, but override is required to annotate abstract methods of the interface

fun main(a) {
    val view = View()
    view.click()
    view.longClick()
}

class View : Clickable {
    
    override fun click(a) {
        println("clicked")}}interface Clickable {
    fun click(a)
    fun longClick(a) = println("longClicked")}Copy the code

If a class implements more than one interface, and the interface contains a method with a default implementation and the same signature, the compiler will require the developer to explicitly implement that method, optionally calling the corresponding implementation of the different interfaces in that method

class View : Clickable.Clickable2 {

    override fun click(a) {
        println("clicked")}override fun longClick(a) {
        super<Clickable>.longClick()
        super<Clickable2>.longClick()
    }
}

interface Clickable {
    fun click(a)
    fun longClick(a) = println("longClicked")}interface Clickable2 {
    fun click(a)
    fun longClick(a) = println("longClicked2")}Copy the code

12.2. Abstract Properties

An interface can contain abstract property declarations. The interface does not define whether the abstract property should be stored in a support field or retrieved through a getter. The interface itself does not contain any state, so only the class that implements the interface will store the value if needed

As an example, both the Button and TextView classes implement the Clickable interface and provide a way to get a statusValue

The Button class provides a custom getter to retrieve the statusValue on each access, so the value may be inconsistent multiple times because the getRandom() method is called each time

The statusValue property of the TextView class has a support field to store the data obtained at class initialization, so its value is not retrieved after initialization, that is, getRandom() in the TextView class is called only once

fun main(a) {
    val button = Button()
    println(button.statusValue)
    val textView = TextView()
    println(textView.statusValue)
}

class Button : Clickable {

    override val statusValue: Int
        get() = getRandom()

    private fun getRandom(a) = Random().nextInt(10)}class TextView : Clickable {

    override val statusValue: Int = getRandom()

    private fun getRandom(a) = Random().nextInt(10)}interface Clickable {

    val statusValue: Int

}
Copy the code

In addition to being able to declare abstract properties, interfaces can also contain properties with getters and setters, as long as they do not reference a support field (which requires storing state in the interface, which is not allowed)

interface Clickable {

    val statusValue: Int

    val check: Boolean
        get() = statusValue > 10
    
}
Copy the code

Xiii. Succession

In Kotlin all classes have a common superclass, Any, which is the default superclass for classes that do not have a superclass declaration. Note that Any is not java.lang.object, which has no attributes or functions other than equals(), hashCode(), and toString()

To declare an explicit superclass, place the superclass name after the colon in the class header

open class Base(a)class SubClass() : Base()
Copy the code

The open annotation on a class, as opposed to final in Java, allows other classes to inherit from the class. By default, all classes in Kotlin are final

If a derived class has a primary constructor, its base type must directly or indirectly call the primary constructor of the base class

open class Base(val str: String)

class SubClass(val strValue: String) : Base(strValue)

class SubClass2 : Base {

    constructor(strValue: String) : super(strValue)

    constructor(intValue: Int) : super(intValue.toString())

    constructor(doubValue: Double) : this(doubValue.toString())

}
Copy the code

13.1. Coverage method

Unlike Java, Kotlin needs to explicitly annotate overwritable and overwritten members:

open class Base() {
    open fun fun1(a){}fun fun2(a){}}class SubClass() : Base() {
    override fun fun1(a) {
        super.fun1()
    }
}
Copy the code

A function marked open can be overridden by a subclass that uses override to override a function of the same signature as its parent. The member marked override is itself open, that is, it can be overridden by subclasses. Subclasses are not allowed to define functions with the same signature if the parent class does not annotate the function with open. For a final class (one that does not use the open annotation), it makes no sense to use open to mark properties and methods

13.2. Attribute coverage

Attribute override is similar to method override. Properties declared as open in a superclass must be redeclared in a derived class beginning with Override if they are to be overridden, and they must have compatible types

Each declared property can be overridden by a property with an initializer or a property with a getter method

open class Base {
    open val x = 10

    open val y: Int
        get() {
            return 100}}class SubClass : Base() {
    override val x = 100

    override var y = 200
}

fun main(a) {
    val base = Base()
    println(base.x) / / 10
    println(base.y) / / 100

    val base1: Base = SubClass()
    println(base1.x) / / 100
    println(base1.y) / / 200

    val subClass = SubClass()
    println(subClass.x) / / 100
    println(subClass.y) / / 200
}
Copy the code

Alternatively, it is possible to override a val attribute with a var attribute, but not vice versa. Because a val property essentially declares a getter method, overwriting it as var simply declares an additional setter method in a subclass

You can use the Override keyword in the main constructor as part of the property declaration

open class Base {
    open val str: String = "Base"
}

class SubClass(override val str: String) : Base()

fun main(a) {
    val base = Base()
    println(base.str) //Base

    val subClass = SubClass("leavesC")
    println(subClass.str) //leavesC
}
Copy the code

13.3. Invoke the superclass implementation

Derived classes can call the implementation of functions and attribute accessors for their superclasses through the super keyword

open class BaseClass {
    open fun fun1(a) {
        println("BaseClass fun1")}}class SubClass : BaseClass() {

    override fun fun1(a) {
        super.fun1()
    }

}
Copy the code

The inner class itself can call functions that call the outer class directly

open class BaseClass2 {
    private fun fun1(a) {
        println("BaseClass fun1")}inner class InnerClass {
        fun fun2(a) {
            fun1()
        }
    }

}
Copy the code

However, if you want to access the superclass of the outer class from within an inner class, you need to do so through the super keyword qualified by the name of the outer class

open class BaseClass {
    open fun fun1(a) {
        println("BaseClass fun1")}}class SubClass : BaseClass() {

    override fun fun1(a) {
        println("SubClass fun1")}inner class InnerClass {

        fun fun2(a) {
            super@SubClass.fun1()
        }

    }

}

fun main(a) {
    val subClass = SubClass()
    val innerClass = subClass.InnerClass()
    //BaseClass fun1
    innerClass.fun2()
}
Copy the code

If a class inherits multiple implementations of the same member from its immediate superclass and the implementation’s interface, it must override that member and provide its own implementation to disambiguate it

To indicate the implementation to inherit from which supertype, specify super qualified by the supertype name in Angle brackets, such as super< BaseClass >

open class BaseClass {
    open fun fun1(a) {
        println("BaseClass fun1")}}interface BaseInterface {
    // Interface members are open by default
    fun fun1(a) {
        println("BaseInterface fun1")}}class SubClass() : BaseClass(), BaseInterface {
    override fun fun1(a) {
        // Call SubClass fun1()
        super<BaseClass>.fun1()
        // Call BaseInterface fun1()
        super<BaseInterface>.fun1()
    }
}
Copy the code

Xiv. Collection

14.1. Read-only Collections and mutable Collections

Another feature of Kotlin’s collection design that differs from Java is: Kotlin interface to access data and modifying the Collection of data interface is separated, kotlin. Collections. The Collection interface provides the traversal Collection element, obtain set size, whether the Collection contains an element, such as operation, but the interface does not provide a method of adding and removing elements. Kotlin. Collections. MutableCollection interface inheritance in kotlin. Collections. The Collection interface, extend out to add, remove, empty element method

Just like Kotlin’s distinction between Val and var, the separation of the read-only and mutable collections interfaces improves code controllability. If a function accepts a Collection as a parameter, it knows that the function is not modifying the Collection, but merely reading the data

The following functions are used to create collections of different types

A collection of elements read-only variable
List listOf MutableListOf, arrayListOf
Set setOf MutableSetOf, hashSetOf, linkedSetOf, sortedSetOf
Map mapOf MutableMapOf, hashMapOf, linkedMapOf, sortedMapOf
    val list = listOf(10.20.30.40)
    // Does not contain the add method
    //list.add(100)
    println(list.size)
    println(list.contains(20))

    val mutableList = mutableListOf("leavesC"."Leaves should be leaves."."Leaf")
    mutableList.add("Ye")
    println(mutableList.size)
    println(mutableList.contains("leavesC"))
Copy the code

14.2. Collections and Java

Because Java does not distinguish between read-only collections and mutable collections, Java code can modify a collection even if kotlin declares it read-only, and a collection in Java code is unknown to Kotlin, which can treat it as either read-only or mutable. The contained elements can also be null or non-null

For example, in Java code names is a variable of type List< String >

public class JavaMain {

    public static List<String> names = new ArrayList<>();

    static {
        names.add("leavesC");
        names.add("Ye"); }}Copy the code

There are four ways to refer to the variable names in Kotlin

    vallist1: List<String? > = JavaMain.namesval list2: List<String> = JavaMain.names

    val list3: MutableList<String> = JavaMain.names

    vallist4: MutableList<String? > = JavaMain.namesCopy the code

14.3 variability of read-only Sets

Read-only collections are not necessarily immutable. For example, suppose you have an object with a read-only interface that has two different references, one read-only and one mutable. When the mutable reference modifiable the object, this is equivalent to “the read-only collection has been modified” for the read-only reference, so the read-only collection is not always thread-safe. If you need to process data in a multi-threaded environment, you need to ensure that access to the data is properly synchronized or that you use a data structure that supports concurrent access

For example, list1 and list1 refer to the same collection object, and list3’s changes to the collection affect list1 as well

    val list1: List<String> = JavaMain.names
    val list3: MutableList<String> = JavaMain.names
    list1.forEach { it -> println(it) } //leavesC Ye
    list3.forEach { it -> println(it) } //leavesC Ye
    for (index in list3.indices) {
        list3[index] = list3[index].toUpperCase()
    }
    list1.forEach { it -> println(it) } //LEAVESC YE
Copy the code

14.4. Set and emptiness

The nullability of sets can be divided into three types:

  1. Can contain collection elements that are null
  2. The collection itself can be null
  3. Collections themselves can be null and can contain collection elements that are NULL

For example, intList1 can contain collection elements that are null, but the collection itself cannot point to NULL; IntList2 cannot contain collection elements that are null, but collections themselves can point to NULL; IntList3 can contain collection elements that are null, and the collection itself can point to NULL

    //List
    val intList1: List<Int? > = listOf(10.20.30.40.null)
    //List
      
       ? Is a null-able list
      
    var intList2: List<Int>? = listOf(10.20.30.40)
    intList2 = null
    //List
      
       ? Is a null-capable list that can hold an Int? Type values
      ?>
    var intList3: List<Int? >? = listOf(10.20.30.40.null)
    intList3 = null
Copy the code