This is the 28th day of my participation in the August Challenge. For the Nuggets’ August challenge,

This article introduces the basics of the Kotlin language

About the Kotlin

Kotlin is a statically typed programming language running on the Java virtual machine, known as the Swift of the Android world, designed and open sourced by JetBrains.

Kotlin can be compiled to Java bytecode or JavaScript, making it easy to run on devices without a JVM.

In Google I/O 2017, Google announced Kotlin as the official Android development language.

website

assigned

Why can Kotlin write an Android app?

Because code written in the Java language eventually needs to be compiled into a.class file to execute, code written in Kotlin will eventually be compiled into a.class file. As far as the Android operating system is concerned, it doesn’t have to care what language the program is developed in, as long as it ends up executing legitimate. Class files.

Why are we using Kotlin for Android? 1. Meaningless semicolons at the end of every line of code. 2, The switch statement only supports the int condition (java1.8 also supports String), and the case must end with a break. Not a full-object language, but a semi-object-oriented language. 4, does not support string embedded expression, concatenation string complex.

My first Kotlin program

Kotlin program files end with.kt, such as hello.kt, app.kt.

The following test demos are available

The Kotlin online tool writes tests

We’ll start with HelloWord

package hello                      // Optional head
 
fun main(args: Array<String>) {    // package-level visible function that takes an array of strings as an argument
   println("Hello World!")         // The semicolon can be omitted
}
Copy the code

object-oriented

class Greeter(val name: String) {
   fun greet(a) { 
      println("Hello, $name")}}fun main(args: Array<String>) {
   Greeter("World!").greet()          // Create an object without the new keyword
}
Copy the code

Why Kotlin?

  • Brevity: Greatly reduces the amount of boilerplate code.
  • Safety: Avoid whole class errors such as null pointer exceptions.
  • Interoperability: Leverage existing libraries of the JVM, Android, and browser.
  • Tool-friendly: Build with any Java IDE or use the command line.

Since I learned Kotiln mainly for Android as a foundation

So I will mainly introduce Kotlin Android environment building

Kotlin Android environment setup

Install the Kotlin plug-in

The Kotlin plugin will be built into Android Studio starting with version 3.0 (Preview).

Open the Settings panel and find the Plugins on the right side (Ctrl+, Command +). Type “Kotlin” in the search box to find the Plugins. Click On Search in Repositories and install it. Once installed, you will need to restart Android Studio.

Create a new project

Select Start a new Android Studio project or File | new project, most of the options are the default values, you just need to press “enter” key several times.

Android Studio 3.0 provides options to enable Kotlin support in the current dialog box. If checked, you can skip the “Configuring Kotlin in the Project” step.

Select the Android version:

Select the Activity style you want to create:

After the Activity:

In Android Studio 3.0, you have the option of using Kotlin to create an activity, so the “Converting Java code to Kotlin (Converting Java code to Kotlin)” step is not required.

In earlier versions, the activity was created using Java and then converted using an automatic conversion tool.


Convert the Java code to Kotlin

Open Android Studio again, create a new Android project, and add a default MainActivity

Open the MainActivity. Java File, bring up the Code through the menu bar | Convert Java File to Kotlin File:

Once the transformation is complete, you can see the activity written using Kotlin.

Kotlin is configured in the project

When you start editing this file, Android Studio will tell you that Kotlin has not been configured for the current project. Just follow the prompts or you can select Tools from the menu bar

Select the latest version in the following dialog box.

After the Kotlin configuration is complete, the build.gradle file for the application is updated. You can see the new Apply plugin: ‘Kotlin-Android’ and its dependencies.

To synchronize the Project, click “Sync Now” in the prompt box or use Sync Project with Gradle Files.

Kotlin basic syntax

Kotlin files are suffixed with.kt.

Package declaration

Code files typically start with a package declaration:

com.breeze.main


import java.util.*

fun test() {}
class Breeze {}
Copy the code

Kotlin source files do not need matching directories and packages, and source files can be placed in any file directory.

In this example, the full name of test() is com.breeze.main.test, and the full name of breeze is com.breeze.main.breeze.

If no package is specified, the default package is used.

The default import

There are multiple packages imported into each Kotlin file by default:

  • kotlin.*
  • kotlin.annotation.*
  • kotlin.collections.*
  • kotlin.comparisons.*
  • kotlin.io.*
  • kotlin.ranges.*
  • kotlin.sequences.*
  • kotlin.text.*

The function definitions

The function definition uses the keyword fun and the parameter format is: parameter: type

fun sum(a: Int, b: Int): Int {   // Int argument, return value Int
    return a + b
}
Copy the code

Expression as function body, return type automatically inferred:

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

public fun sum(a: Int, b: Int): Int = a + b   // The public method must specify the return type
Copy the code

A function that returns no value (similar to void in Java) :

fun printSum(a: Int, b: Int): Unit { 
    print(a + b)
}


// If it returns Unit, it can be omitted (also for public methods) :
public fun printSum(a: Int, b: Int) { 
    print(a + b)
}
Copy the code

Variable length argument function

Variable length arguments to a function can be identified by the vararg keyword:

Fun vars(vararg v:Int){for(vt in v){print(vt)}} fun main(args: Array<String>) {vars(1,2,3,4,5)Copy the code

Lambda (anonymous function)

Examples of lambda expressions:

Play.kotlinlang.org/#eyJ2ZXJzaW…

/ / test
fun main(args: Array<String>) {
    val sumLambda: (Int.Int) - >Int = {x,y -> x+y}
    println(sumLambda(1.2))  3 / / output
}
Copy the code

Define constants and variables

Variable variable definition: var keyword

Var < identifier > : < type > = < initial value >Copy the code

Definition of immutable variable: the val keyword, a variable that can only be assigned once (similar to a final variable in Java)

Val < identifier > : < type > = < initialization value >Copy the code

Constants and variables can have no initialized value, but must be initialized before reference

The compiler supports automatic type determination, that is, the declaration can not specify the type, the compiler to determine.

Val a: Int = 1 val b = 1 Var x = 5 // The system automatically concludes that the variable type is Int x += 1 // The variable can be modifiedCopy the code

annotation

Kotlin supports single-line and multi-line comments, as shown in the following example:

// This is a single-line comment /* This is a multi-line block comment. * /Copy the code

Unlike Java, block annotations in Kotlin allow nesting.


String template

$represents a variable name or value

$varName represents the variable value

${varname.fun ()} denotes the return value of the variable’s method:

var a = 1
// Simple name in template:
val s1 = "a is $a" 

a = 2
// Any expression in the template:
val s2 = "${s1.replace("is"."was")}, but now is $a"
Copy the code

NULL checking mechanism

Kotlin’s empty safety design for the declaration can be empty parameters, in the use of empty judgment processing, there are two processing methods, after the field plus!! Throw an empty exception, as in Java, followed by? Do not do processing return value null or cooperate? : Short judgment processing

// Type after? Indicates nullable
var age: String? = "23" 
// Throw a null pointer exception
valages = age!! .toInt()// Returns null without processing
valages1 = age? .toInt()// If age is empty, -1 is returned
valages2 = age? .toInt() ? : -1
Copy the code

When a reference may be null, the corresponding type declaration must be explicitly marked as nullable.

Returns null if the string content in STR is not an integer:

fun parseInt(str: String): Int? {/ /... }Copy the code

The following example shows how to use a function that can return null:

Play.kotlinlang.org/#eyJ2ZXJzaW…

fun main(args: Array<String>) {
  if (args.size < 2) {
    print("Two integers expected")
    return
  }
  val x = parseInt(args[0])
  val y = parseInt(args[1])
  // Using 'x * y' directly causes an error because they may be null.
  if(x ! =null&& y ! =null) {
    // The types of x and y are automatically converted to non-NULL variables after null-checking
    print(x * y)
  }
}
Copy the code

Type detection and automatic type conversion

We can use the is operator to detect whether an expression is an instanceof a type (similar to the instanceof keyword in Java).

fun getStringLength(obj: Any): Int? {
  if (obj is String) {
    // Obj is automatically converted to String
    return obj.length 
  }

  // There is another method here, unlike Java instanceof, which uses! is
  // if (obj ! is String){
  // // XXX
  // }

  // obj here is still a reference of type Any
  return null
}
Copy the code

or

fun getStringLength(obj: Any): Int? {
  if (obj !is String)
    return null
  // In this branch, the type 'obj' is automatically converted to 'String'
  return obj.length
}
Copy the code

Maybe even

fun getStringLength(obj: Any): Int? {
  // On the right side of the && operator, the type of 'obj' is automatically converted to 'String'
  if (obj is String && obj.length > 0)
    return obj.length
  return null
}
Copy the code

interval

Interval expressions are given by operators of the form.. The rangeTo function is complemented by in and! The in formation.

An interval is defined for any comparable type, but for integer primitive types, it has an optimized implementation. Here are some examples of using ranges:

for (i in 1.4.) print(i) / / output "1234"

for (i in 4.1.) print(i) // Output nothing

if (i in 1.10.) { // The same thing as 1 <= I && I <= 10
    println(i)
}

// Use step to specify the step size
for (i in 1.4. step 2) print(i) / / output "13"

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


// Use until to exclude the end element
for (i in 1 until 10) {   // I in [1, 10]
     println(i)
}
Copy the code

The instance test

fun main(args: Array<String>) {
    print("Loop output:")
    for (i in 1.4.) print(i) / / output "1234"
    println("\n----------------")
    print("Set step size:")
    for (i in 1.4. step 2) print(i) / / output "13"
    println("\n----------------")
    print("Using downTo:")
    for (i in 4 downTo 1 step 2) print(i) / / output "42"
    println("\n----------------")
    print("Use until:")
    // Use until to exclude the end element
    for (i in 1 until 4) {   // I in [1, 4]
        print(i)
    }
    println("\n----------------")}Copy the code

Output result:

Loop output: 1234 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - setting step: 13 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - use downTo: 42 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- use until: 123Copy the code

Kotlin basic data type

Kotlin’s basic numeric types include Byte, Short, Int, Long, Float, Double, and so on. Unlike Java, a character is not a numeric type, but a separate data type.

type Bit width
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

The literal constants

Here are all types of literal constants:

  • Decimal: 123
  • Long integers end with a capital L: 123L
  • Hexadecimal starts with 0x: 0x0F
  • Base 2 starts with 0B: 0b00001011
  • Note: Base 8 is not supported

Kotlin also supports traditional symbolic floating-point values:

  • 5. annual rainfall123.5.123.5 e10
  • Floats use the f or f suffix:123.5 f

You can use underscores to make numeric constants more readable:

val oneMillion = 1 _000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
Copy the code

Compare two numbers

There are no underlying data types in Kotlin, only encapsulated numeric types. For every variable you define, Kotlin actually wraps an object for you, so that there are no null Pointers. The same goes for numeric types, so when comparing two numbers, there is a difference between comparing data size and comparing whether two objects are the same.

In Kotlin, three equal signs === represent the address of the object being compared, and two == represent the size of two values.

fun main(args: Array<String>) {
    val a: Int = 10000
    println(a === a) // true, value equal, object address equal

    // After boxing, two different objects are created
    val boxedA: Int? = a
    val anotherBoxedA: Int? = a

    // The value is the same as that of 10000
    println(boxedA === anotherBoxedA) // false, value equal, object address different
    println(boxedA == anotherBoxedA) // true, the value is equal
}
Copy the code

Type conversion

Because of the different representations, a smaller type is not a subtype of a larger type, and a smaller type cannot be implicitly converted to a larger type. This means that we cannot assign a Byte value to an Int variable without an explicit conversion.

val b: Byte = 1 // OK, literals are statically checked
val i: Int = b / / error
Copy the code

We can use the toInt() method instead.

val b: Byte = 1 // OK, literals are statically checked
val i: Int = b.toInt() // OK
Copy the code

Each data type has the following methods that can be converted to other types:

toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
Copy the code

Automatic type conversion can be used in some cases, if the correct data type can be inferred from the context and the mathematical operators are overloaded accordingly. For example the following is true:

val l = 1L + 3 // Long + Int => Long
Copy the code

An operator

There are a number of bitwise operators available for Int and Long, respectively:

SHL (bits) - Left-shifted (Java's <<) SHR (bits) - right-shifted (Java's >>) USHR (bits) - unsigned right-shifted (Java's >>>) and(bits) - with or(bits) - or Xor (bits) - XOR or INV () - ReverseCopy the code

character

Unlike Java, a Char in Kotlin cannot be manipulated directly with numbers; it must be enclosed in single quotes. Ordinary characters like ‘0’, ‘a’.

fun check(c: Char) {
    if (c == 1) { // Error: type incompatible
        / /...}}Copy the code

Character literals are enclosed in single quotes: ‘1’. Special characters can be escaped with backslashes. These escape sequences are supported: \t, \b, \n, \r, ‘, “, \ and $. Other characters are encoded using Unicode escape sequence syntax: ‘\uFF00’.

We can explicitly convert characters to Int numbers:

fun decimalDigitValue(c: Char): Int {
    if (c !in '0'.'9')
        throw IllegalArgumentException("Out of range")
    return c.toInt() - '0'.toInt() // Explicitly convert to a number
}
Copy the code

When nullable references are required, things like numbers and characters are boxed. The boxing operation does not preserve identity.


Boolean

Boolean is represented by Boolean type, which has two values: true and false.

Empty reference booleans will be boxed if needed.

The built-in Boolean operations are:

| | - short circuit logic or && - short circuit logic and! Non - logicCopy the code

An array of

Array is implemented by Array class, and there is a size attribute and get and set methods, because [] overrides get and set methods, so we can easily get or set the value of the corresponding position of the Array by subscript.

Arrays can be created in two ways: using the function arrayOf(); The other is to use factory functions. As shown below, we create two arrays in two different ways:

fun main(args: Array<String>) {
    / / [1, 2, 3]
    val a = arrayOf(1.2.3)
    / / 4-trichlorobenzene [0]
    val b = Array(3, { i -> (i * 2)})// Read the contents of the array
    println(a[0])    // Output: 1
    println(b[1])    // Output: 2
}
Copy the code

As mentioned above, the [] operator represents calling the member functions get() and set().

Note: Unlike Java, arrays in Kotlin are uncovariant.

In addition to Array, there are ByteArray, ShortArray, and IntArray, which are used to represent arrays of various types without boxing, and therefore are more efficient. They are used in the same way as Array:

val x: IntArray = intArrayOf(1.2.3)
x[0] = x[1] + x[2]
Copy the code

string

Like Java, strings are immutable. The square brackets [] syntax makes it easy to get a character in a string, or to iterate through the for loop:

for (c in str) {
    println(c)
}
Copy the code

Kotlin supports three quote “”” extended strings, and supports multi-line strings, such as:

Play.kotlinlang.org/#eyJ2ZXJzaW…

fun main(args: Array<String>) {
    val text = """ Multi-line string Multi-line string
    println(text)   // The output has some leading whitespace
}
Copy the code

Strings can be removed with the trimMargin() method.

fun main(args: Array<String>) {
    val text = "" |" multi-line string rookie tutorial | | multi-line string | Breeze ", "".trimMargin()
    println(text)    // The leading space was removed
}
Copy the code

The default | prefix is used as the boundary, but you can choose the other characters and passed as a parameter, such as trimMargin (” > “).


String template

Strings can contain template expressions, which are little pieces of code that evaluate and merge the results into the string. The template expression starts with a dollar character ($) and consists of a simple name:

fun main(args: Array<String>) {
    val i = 10
    val s = "i = $i" // I = 10
    println(s)
}
Copy the code

Or any expression expanded with curly braces:

fun main(args: Array<String>) {
    val s = "breeze"
    val str = "$s.length is ${s.length}" // Evaluate to "breeze. Length is 6"
    println(str)
}
Copy the code

Templates are supported inside both native and escaped strings. If you need to represent the literal $character in a native string (which does not support backslash escape), you can use the following syntax:

fun main(args: Array<String>) {
    val price = "" "The ${'$'}9.99 "" "
    println(price)  // Evaluates to $9.99
}
Copy the code

Kotlin conditional control

IF expression

An if statement contains a Boolean expression and one or more statements.

// Traditional usage
var max = a 
if (a < b) max = b

/ / use the else
var max: Int
if (a > b) {
    max = a
} else {
    max = b
}
 
// as an expression
val max = if (a > b) a else b
Copy the code

We can also assign the result of an IF expression to a variable.

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

This also means that I don’t need a ternary operator like Java does, because we can use it to simply implement:

val c = if (condition) a else b
Copy the code

The instance

Play.kotlinlang.org/#eyJ2ZXJzaW…

fun main(args: Array<String>) {
    var x = 0
    if(x>0){
        println("X" (greater than zero)}else if(x==0){
        println("X equals 0")}else{
        println("X" (less than zero)}var a = 1
    var b = 2
    val c = if (a>=b) a else b
    println("Has a value of c$c")}Copy the code

The output is:

X is equal to the0A value of c2
Copy the code

Using interval

Uses the in operator to detect whether a number is in a specified interval of the format x.. Y:

The instance

fun main(args: Array<String>) {
    val x = 5
    val y = 9
    if (x in 1.8.) {
        println("X is in the interval.")}}Copy the code

The output is:

X is in the intervalCopy the code

When the expression

When compares its parameters to all the branch conditions in order until a branch satisfies the condition.

When can be used as either an expression or a statement. If it is used as an expression, the value of the eligible branch is the value of the entire expression; if it is used as a statement, the value of the individual branches is ignored.

When is similar to the switch operator in other languages. Its simplest form is as follows:

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else- > {// Notice this block
        print("X is not one, x is not two.")}}Copy the code

In when, else is the same as the default of switch. If none of the other branches meet the criteria, the else branch will be evaluated.

If many branches need to be treated in the same way, you can group multiple branch conditions together, separated by commas:

when (x) {
    0.1 -> print("x == 0 or x == 1")
    else -> print("otherwise")}Copy the code

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

when (x) {
    in 1.10. -> print("x is in the range")
    in validNumbers -> print("x is valid")!in 10.20. -> print("x is outside the range")
    else -> print("none of the above")}Copy the code

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

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

When can also be used to replace if-else if chains. If no arguments are provided, all branch conditions are simple Boolean expressions, and a branch is executed when its condition is true:

when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")}Copy the code

The instance

Play.kotlinlang.org/#eyJ2ZXJzaW…

fun main(args: Array<String>) {
    var x = 0
    when (x) {
        0.1 -> println("x == 0 or x == 1")
        else -> println("otherwise")}when (x) {
        1 -> println("x == 1")
        2 -> println("x == 2")
        else- > {// Notice this block
            println("X is not one, x is not two.")}}when (x) {
        in 0.10. -> println("X is within that range.")
        else -> println("X is not within that range.")}}Copy the code

Output result:

x == 0 or x == 1X is not1, nor is it2X is within that rangeCopy the code

When uses the in operator to determine whether the collection contains an instance:

Play.kotlinlang.org/#eyJ2ZXJzaW…

fun main(args: Array<String>) {
    val items = setOf("apple"."banana"."kiwi")
    when {
        "orange" in items -> println("juicy")
        "apple" in items -> println("apple is fine too")}}Copy the code

Output result:

apple is fine too
Copy the code

Kotlin cycle control

The For loop

The for loop can iterate over any object that provides an iterator with the syntax:

for (item in collection) print(item)
Copy the code

The body of a loop can be a code block:

for (item: Int in ints) {
    / /...
}
Copy the code

As mentioned above, for can loop over any object that provides an iterator.

If you want to traverse an array or a list by index, you can do this:

for (i in array.indices) {
    print(array[i])
}
Copy the code

Note that this “walking over an interval” compiles into an optimized implementation without creating additional objects.

Or you can use the withIndex library function:

for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")}Copy the code

The instance

Iterate over the collection:

fun main(args: Array<String>) {
    val items = listOf("apple"."banana"."kiwi")
    for (item in items) {
        println(item)
    }

    for (index in items.indices) {
        println("item at $index is ${items[index]}")}}Copy the code

Output result:

apple
banana
kiwi
item at 0 is apple
item at 1 is banana
item at 2 is kiwi
Copy the code

While with the do… The while loop

While is the most basic loop, and its structure is:

while(Boolean expression) {// Loop content
}
Copy the code

The do… While loop For a while statement, if the condition is not met, the loop cannot be entered. But sometimes we need to do it at least once, even if the conditions are not met.

The do… The while loop is similar to the while loop, except that do… The while loop is executed at least once.

do {
       // Code statement
}while(Boolean expression);Copy the code

The instance

fun main(args: Array<String>) {
    println(- while the use of -- -- -- -- --")
    var x = 5
    while (x > 0) {
        println( x--)
    }
    println("----do... While the use of -- -- -- -- --")
    var y = 5
    do {
        println(y--)
    } while(y>0)}Copy the code

Output result:

5
4
3
2
1
----do.whileUse the -- -- -- -- -5
4
3
2
1
Copy the code

Return and jump

Kotlin has three structured jump expressions:

  • return. The default is to return from the function that most directly surrounds it or from an anonymous function.
  • break. Terminate the loop that most directly surrounds it.
  • continue. Continue the next cycle that most directly surrounds it.

Kotlin supports the traditional break and continue operators in loops.

fun main(args: Array<String>) {
    for (i in 1.10.) {
        if (i==3) continue  // if I is 3, skip the current loop and continue the next loop
        println(i)
        if (i>5) break   // if I is 6, the loop is broken}}Copy the code

Output result:

1
2
4
5
6
Copy the code

Break and Continue tags

Any expression in Kotlin can be labeled with a label. Labels are in the format of an identifier followed by an @ sign, such as ABC @ and fooBar@. To label an expression, we simply label it.

loop@ for (i in 1.100.) {
    / /...
}
Copy the code

Now, we can restrict break or continue with tags:

loop@ for (i in 1.100.) {
    for (j in 1.100.) {
        if(...)break@loop}}Copy the code

The break restricted by the tag jumps to the execution point just after the loop specified by the tag. Continue continues the next iteration of the loop specified by the tag.

Return at the label

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 the outer function. One of the most important uses is to return from lambda expressions. Think back to when we wrote this:

fun foo(a) {
    ints.forEach {
        if (it == 0) return
        print(it)
    }
}
Copy the code

This return expression returns from foo, the function that most directly surrounds it. (Note that this nonlocal return only supports lambda expressions passed to inline functions.) If we need to return from a lambda expression, we must label it and restrict the return.

fun foo(a) {
    ints.forEach lit@ {
        if (it == 0) return@lit
        print(it)
    }
}
Copy the code

Now, it only returns from the lambda expression. It is often more convenient to use implicit labels. The label has the same name as the function that accepts the lambda.

fun foo(a) {
    ints.forEach {
        if (it == 0) return@forEach
        print(it)
    }
}
Copy the code

Alternatively, we can replace lambda expressions with an anonymous function. A return statement inside an anonymous function is returned from the anonymous function itself

fun foo(a) {
    ints.forEach(fun(value: Int) {
        if (value == 0) return
        print(value)
    })
}
Copy the code

When a return value is required, the parser preferentially selects the tag-bound return, i.e

return@a 1
Copy the code

Return 1 from the tag @a instead of an expression for the tag tag (@a 1).

Kotlin classes and objects

The class definition

The Kotlin class can contain: constructors and initialization code blocks, functions, attributes, inner classes, and object declarations.

Kotlin uses the keyword class to declare a class, followed by the class name:

class Breeze {  // The class name is Breeze
    // Braces are the body of the class
}
Copy the code

We can also define an empty class:

class Empty
Copy the code

Member functions can be defined in a class:

class Breeze() {
    fun foo(a) { print("Foo")}// Member functions
}
Copy the code

Attributes of a class

Attribute definitions

Attributes of a class can be declared mutable using the keyword var, or immutable using the read-only keyword val.

class Breeze {
    varName: String =...varUrl: String =...varCity: String =... }Copy the code

We can use constructors to create class instances just like we would use normal functions:

val site = Breeze() // Kotlin has no new keyword
Copy the code

To use an attribute, you simply refer to it by name

site.name           // Use the. Sign to reference
site.url
Copy the code

Classes in Koltin can have a primary constructor, and one or more secondary constructors, which are part of the class header and come after the class name:

class Person constructor(firstName: String) {}
Copy the code

If the main constructor does not have any annotations and does not have any visibility modifiers, the constructor keyword can be omitted.

class Person(firstName: String) {
}
Copy the code

Getter and setter

Complete syntax for property declarations:

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]
 
Copy the code

Getters and setters are optional

If the property type can be inferred from the initialization statement or from the class’s member functions, the type can be omitted, and val does not allow setter functions to be set because it is read-only.

var allByDefault: Int? // error: an initialization statement is required. Getter and setter methods are implemented by default
var initialized = 1    // The type is Int and the getter and setter are implemented by default
val simple: Int?       Getter is implemented by default, but must be initialized in the constructor
val inferredType = 1   // The type is Int and the getter is implemented by default
Copy the code

The instance

The following example defines a Person class with two mutable variables, lastName, which modifies the getter method, and no, which modifies the setter method.

class Person {

    var lastName: String = "zhang"
        get() = field.toUpperCase()   // Convert the assigned variable to uppercase
        set

    var no: Int = 100
        get() = field                // Back-end variables
        set(value) {
            if (value < 10) {       // Return the value if the value passed is less than 10
                field = value
            } else {
                field = -1         // Returns -1 if the value passed is greater than or equal to 10}}var heiht: Float = 145.4 f
        private set
}

/ / test
fun main(args: Array<String>) {
    var person: Person = Person()

    person.lastName = "wang"

    println("lastName:${person.lastName}")

    person.no = 9
    println("no:${person.no}")

    person.no = 20
    println("no:${person.no}")}Copy the code

The output is:

lastName:WANG
no:9
no:-1
Copy the code

Classes in Kotlin cannot have fields. The Backing Fields mechanism is provided, with the Backing Fields declared using the field keyword, which can only be used by accessors to properties, as in the example above:

var no: Int = 100
        get() = field                // Back-end variables
        set(value) {
            if (value < 10) {       // Return the value if the value passed is less than 10
                field = value
            } else {
                field = -1         // Returns -1 if the value passed is greater than or equal to 10}}Copy the code

Non-empty attributes must be initialized when they are defined, and Kotlin provides a way to delay initialization by using the lateInit keyword:

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup(a) {
        subject = TestSubject()
    }

    @Test fun test(a) {
        subject.method()  // dereference directly}}Copy the code

The main constructor

The primary constructor cannot contain any code, and the initialization code can be placed in the initialization code section, which is prefixed with the init keyword.

class Person constructor(firstName: String) {
    init {
        println("FirstName is $firstName")}}Copy the code

Note: The arguments to the primary constructor can be used in the initialization code snippet or in the property initialization code defined by the class body N. A concise syntax for defining properties and initializing property values (either var or val) via the primary constructor:

class People(val firstName: String, val lastName: String) {
    / /...
}
Copy the code

If the constructor has annotations or visibility modifiers, the constructor keyword is required and the annotations and modifiers come before it.

The instance

Create a Breeze class and pass in the site name via the constructor:

class Breeze  constructor(name: String) {  // The class name is Breeze
    // Braces are the body of the class
    var url: String = "http://www.Breeze.com"
    var country: String = "CN"
    var siteName = name

    init {
        println("Initialize the site name:${name}")}fun printTest(a) {
        println("I'm a function of class.")}}fun main(args: Array<String>) {
    val Breeze =  Breeze("Rookie Tutorial")
    println(Breeze.siteName)
    println(Breeze.url)
    println(Breeze.country)
    Breeze.printTest()
}
Copy the code

The output is:

HTTP: initializing a web site//www.Breeze.comCN I'm a function of classCopy the code

subconstructor

Classes can also have secondary constructors, which need to be prefixed by constructor:

class Person { 
    constructor(parent: Person) {
        parent.children.add(this)}}Copy the code

If a class has a primary constructor, each subconstructor must, either directly or indirectly, proxy the primary constructor through another subconstructor. Proiding another constructor in the same class using the this keyword:

class Person(val name: String) {
    constructor (name: String, age:Int) : this(name) {
        // Initialize...}}Copy the code

If a non-abstract class does not declare a constructor (primary constructor or secondary constructor), it produces a constructor with no arguments. The constructor is public. If you don’t want your class to have a public constructor, you have to declare an empty main constructor:

class DontCreateMe private constructor () {}Copy the code

Note: In the JVM virtual machine, if all arguments to the main constructor have default values, the compiler generates an additional constructor with no arguments, which uses the default values directly. This makes it easier for Kotlin to use libraries like Jackson or JPA that use no-argument constructors to create class instances.

class Customer(val customerName: String = "")
Copy the code

The instance

class Breeze  constructor(name: String) {  // The class name is Breeze
    // Braces are the body of the class
    var url: String = "http://www.Breeze.com"
    var country: String = "CN"
    var siteName = name

    init {
        println("Initialize the site name:${name}")}// subconstructor
    constructor (name: String, alexa: Int) : this(name) {
        println("Alexa ranking$alexa")}fun printTest(a) {
        println("I'm a function of class.")}}fun main(args: Array<String>) {
    val Breeze =  Breeze("Rookie Tutorial".10000)
    println(Breeze.siteName)
    println(Breeze.url)
    println(Breeze.country)
    Breeze.printTest()
}
Copy the code

The output is:

Initialization site name: Rookie tutorial Alexa ranking10000HTTP://www.Breeze.comCN I'm a function of classCopy the code

An abstract class

Abstract is one of the characteristics of object-oriented programming. A class itself, or some members of a class, can be declared abstract. An abstract member has no concrete implementation in a class.

Note: There is no need to annotate the open annotation for abstract classes or abstract members.

open class Base {
    open fun f(a){}}abstract class Derived : Base() {
    override abstract fun f(a)
}
Copy the code

Nested classes

We can nest a class within another class, as shown in the following example:

class Outer {                  / / outside class
    private val bar: Int = 1
    class Nested {             / / nested classes
        fun foo(a) = 2}}fun main(args: Array<String>) {
    val demo = Outer.Nested().foo() // Call format: external class. Nested classes. Nested class methods/attributes
    println(demo)    / / = = 2
}
Copy the code

The inner class

Inner classes are represented by the inner keyword.

The inner class has a reference to the object of the outer class, so the inner class can access the attributes and functions of the member of the outer class.

class Outer {
    private val bar: Int = 1
    var v = "Member Attributes"
    /** nested inner class **/
    inner class Inner {
        fun foo(a) = bar  // Access the external class member
        fun innerTest(a) {
            var o = this@Outer // Get the member variables of the external class
            println("Inner classes can refer to members of outer classes, for example:" + o.v)
        }
    }
}

fun main(args: Array<String>) {
    val demo = Outer().Inner().foo()
    println(demo) / / 1
    val demo2 = Outer().Inner().innerTest()   
    println(demo2)   // An inner class can refer to a member of an outer class, such as a member attribute
}
Copy the code

To disambiguously access this from an external scope, we use this@label, where @label is a label that refers to the source of this.


Anonymous inner class

Use object expressions to create anonymous inner classes:

class Test {
    var v = "Member Attributes"

    fun setInterFace(test: TestInterFace) {
        test.test()
    }
}

/** * defines the interface */
interface TestInterFace {
    fun test(a)
}

fun main(args: Array<String>) {
    var test = Test()

    /** * Uses object expressions to create interface objects, which are instances of anonymous inner classes. * /
    test.setInterFace(object : TestInterFace {
        override fun test(a) {
            println("Object expression creates instance of anonymous inner class")}}}Copy the code

Class modifier

Class modifiers include classModifier and _accessModifier_:

  • ClassModifier: Indicates the attribute modifier of a class.

    abstract    / / abstract classes
    final       // Class is not inheritable, default property
    enum        / / the enumeration classes
    open        // Classes are inheritable and final by default
    annotation  / / comment
    Copy the code
  • AccessModifier: Access permission modifier

    private    // Only visible in the same file
    protected  // Visible in the same file or subclass
    public     // All calls are visible
    internal   // visible in the same module
    Copy the code

The instance

// File name: example.kt
package foo

private fun foo(a) {} // visible in example.kt

public var bar: Int = 5 // This property can be seen everywhere

internal val baz = 6    // Visible in the same module
Copy the code

Kotlin inheritance

All classes in Kotlin inherit from this Any class, which is the superclass of all classes, and the default superclass for classes that do not have a supertype declaration:

class Example/ / fromAnyImplicit inheritance
Copy the code

Any provides three functions by default:

equals()

hashCode()

toString()
Copy the code

Note: Any is not java.lang.object.

If a class is to be inherited, it can be decorated with the open keyword.

open class Base(p: Int)           // Define the base class

class Derived(p: Int) : Base(p)
Copy the code

The constructor

Subclasses have primary constructors

If a subclass has a primary constructor, the base class must be initialized immediately in the primary constructor.

open class Person(var name : String, var age : Int) {/ / the base class

}

class Student(name : String, age : Int.var no : String, var score : Int) : Person(name, age) {

}

/ / test
fun main(args: Array<String>) {
    val s =  Student("Breeze".18."S12346".89)
    println("Student name:${s.name}")
    println("Age:${s.age}")
    println("Student Number:${s.no}")
    println("The result:${s.score}")}Copy the code

Output result:

Name: Breeze Age:18Student ID: S1234689
Copy the code

Subclasses have no main constructor

If the subclass does not have a primary constructor, then the base class must be initialized with the super keyword in each secondary constructor, or another constructor must be proxy. When initializing a base class, different constructors of the base class can be called.

class Student : Person {

    constructor(ctx: Context) : super(ctx) {
    } 

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
    }
}
Copy the code

The instance

/** User base class **/
open class Person(name:String){
    /** Secondary constructor **/
    constructor(name:String,age:Int) :this(name){
        / / initialization
        println("------- base class secondary constructor ---------")}}/** Subclasses inherit from the Person class **/
class Student:Person{

    /** Secondary constructor **/
    constructor(name:String,age:Int,no:String,score:Int) :super(name,age){
        println("------- inherits class secondary constructor ---------")
        println("Student name:${name}")
        println("Age:${age}")
        println("Student Number:${no}")
        println("The result:${score}")}}fun main(args: Array<String>) {
    var s =  Student("Breeze".18."S12345".89)}Copy the code

Output result:

-- -- -- -- -- -- -- a secondary structure function of the base class -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - a secondary structure function of the derived class -- -- -- -- -- -- -- -- -- the student name: Breeze age: 18 students number: S12345 grade: 89Copy the code

rewrite

When you declare a function with fun ina base class, the function defaults to final and cannot be overridden by subclasses. If subclasses are allowed to override the function, then add the open modifier manually. The subclass override method uses the override keyword:

/** User base class **/
open class Person{
    open fun study(a){       // Allow subclass overrides
        println("I graduated.")}}/** Subclasses inherit from the Person class **/
class Student : Person() {

    override fun study(a){    // Override the method
        println("I'm in college.")}}fun main(args: Array<String>) {
    val s =  Student()
    s.study();

}
Copy the code

The output is:

I'm in college.Copy the code

If there are multiple identical methods (inherited or implemented from other classes, such as class A or B), then the method must be overridden to use the super stereotype to selectively call the implementation of the parent class.

open class A {
    open fun f (a) { print("A")}fun a(a) { print("a")}}interface B {
    fun f(a) { print("B")}// Interface member variables are open by default
    fun b(a) { print("b")}}class C() : A() , B{
    override fun f(a) {
        super<A>.f()/ / call A.f ()
        super<B>.f()/ / call b. ()}}fun main(args: Array<String>) {
    val c =  C()
    c.f();

}
Copy the code

C inherits from either a() or b(). Not only can C inherit functions from a or B, but C can inherit functions that are common to both a() and B(). At this point, the function has only one implementation in. To disambiguate, the function must call the implementation of the function in A() and B() and provide its own implementation.

The output is:

AB
Copy the code

Attribute to rewrite

Attribute overrides use the override keyword. The attribute must have compatible types. Each declared attribute can be overridden using an initializer or getter:

open class Foo {
    open val x: Int get{... }}class Bar1 : Foo() {
    override val x: Int = ……
}
Copy the code

You can override a val property with a var property, but not the other way around. Because the val property itself defines getter methods, rewriting to var declares an additional setter method in the derived class

You can use the override keyword as part of the attribute declaration in the main constructor:

interface Foo {
    val count: Int
}

class Bar1(override val count: Int) : Foo

class Bar2 : Foo {
    override var count: Int = 0
}
Copy the code

A few points to add:

When a subclass extends from its parent, it cannot have a variable with the same name as the parent unless the variable is private or open and the subclass overrides it with override:

open class Person(var name: String, var age: Int) {    
    open var sex: String = "unknow"    
    init {        
        println("Base class initialization")}}// a hides constructor for subclasses whose name is predated by var is not permitted and calls 'name' hides member of supertype and needs 'override'
class Student(var name: String, age: Int.var no: String, var score: Int) : Person(name, age) {
    override var sex: String = "male"
}
Copy the code

In the code snippet above, adding the var keyword to the first field name in the subclass Student main constructor returns an error.

2. Subclasses need not call methods of the same name shared by their parent class and interface

To quote from the article: “C inherits from a() or b(). Not only can C inherit functions from A or B, but also C can inherit functions shared by a() and B(). At this point, there is only one implementation of the function in. To disambiguate, the function must call the implementation of the function in A() and B() and provide its own implementation.

It is not necessary to call the implementation of this function in A() and B() as follows:

open class A {
    open fun f(a) {
        println("A")}fun a(a) {
        println("a")}}interface B {
    fun f(a) {
        println("B")}fun b(a) {
        println("b")}}class C : A(), B {
    override fun f(a) {
        // super<A>.f()
        // super<B>.f()
        println("C")}}Copy the code

Comment out super.f() and super.f() as shown in the code snippet.

3. My guess is that a subclass cannot override var in its parent class with val: When a subclass overwrites the parent class property, it means that it must rewrite the getter and setter methods of the property. However, val in a subclass cannot have setter methods, so it cannot “override” the setter method of var in the parent class, which is equivalent to narrowing the scope of use of the corresponding property in the parent class, and is not allowed. Just like we can’t rewrite a public method in a parent class as a private method.

When a variable is being initialized, the backing field field must be present in the variable. The default getter and setter methods for the variable have a backing field field. But if we overwrite the getter and setter methods for this variable, and the field keyword is not present in the getter and setter methods, the compiler will report an error. Filed field Initializer is not allowed here because this property has no backing field If a variable is initialized, the field must be declared. If a variable is initialized, the field must be declared.

var aaa: Int = 0
get() {
    field Var aaa: Int = 0 will return an error. Do not initialize it unless you remove the = 0 part
    return 0
}
set(value) {}
Copy the code

Kotlin interface

The Kotlin interface is similar to Java 8 in that it uses the interface keyword to define interfaces, allowing methods to have default implementations:

interface MyInterface {
    fun bar(a)    / / unrealized
    fun foo(a) {  / / has been realized
      // Optional method body
      println("foo")}}Copy the code

Implementing an interface

A class or object can implement one or more interfaces.

class Child : MyInterface {
    override fun bar(a) {
        / / the method body}}Copy the code

The instance

interface MyInterface {
    fun bar(a)
    fun foo(a) {
        // Optional method body
        println("foo")}}class Child : MyInterface {
    override fun bar(a) {
        / / the method body
        println("bar")}}fun main(args: Array<String>) {
    val c =  Child()
    c.foo();
    c.bar();
 
}
Copy the code

The output is:

foo
bar
Copy the code

Attributes in interfaces

Attributes in an interface can only be abstract and cannot be initialized. The interface does not store attribute values. When implementing an interface, attributes must be overridden:

interface MyInterface{
    var name:String // Name attribute, abstract
}
 
class MyImpl:MyInterface{
    override var name: String = "runoob" // Override attributes
}
Copy the code

The instance

interface MyInterface {
    var name:String // Name attribute, abstract
    fun bar(a)
    fun foo(a) {
        // Optional method body
        println("foo")}}class Child : MyInterface {
    override var name: String = "breeze" // Override attributes
    override fun bar(a) {
        / / the method body
        println("bar")}}fun main(args: Array<String>) {
    val c =  Child()
    c.foo();
    c.bar();
    println(c.name)
 
}
Copy the code

The output is:

foo
bar
breeze
Copy the code

Kotlin extension

Kotlin can extend the properties and methods of a class without inheriting or using the Decorator pattern.

Extension is a static behavior that has no effect on the extended class code itself.


Extension function

An extension function can add a new method to an existing class without modifying the original class.

fun receiverType.functionName(params){
    body
}
Copy the code
  • ReceiverType: indicates the receiver of the function, that is, the object to which the function is extended
  • FunctionName: The name of the extension function
  • Params: Extension function parameter, can be NULL

The following examples extend the User class:

class User(var name:String)

/** Extension function **/
fun User.Print(a){
    print(The user name"$name")}fun main(arg:Array<String>){
    var user = User("Breeze")
    user.Print()
}
Copy the code

Example execution output is as follows:

The user name BreezeCopy the code

Add a swap function to MutableList:

// Extend the function swap to swap values in different places
fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1]     // This should be a list of objects
    this[index1] = this[index2]
    this[index2] = tmp
}

fun main(args: Array<String>) {

    val l = mutableListOf(1.2.3)
    // Positions 0 and 2 are swapped
    l.swap(0.2) // the 'this' inside 'swap()' will point to the value of 'l'

    println(l.toString())
}
Copy the code

Example execution output is as follows:

[3.2.1]
Copy the code

The this keyword refers to the Receiver Object (that is, the object instance specified before the dot when the extension function is called).


Extension functions are resolved statically

Extension functions are statically parsed, not virtual members of the receiver type. When an extension function is called, it is determined by the object expression on which the function is called, not by the dynamic type:

open class C

class D: C(a)fun C.foo(a) = "c"   // Extend function foo

fun D.foo(a) = "d"   // Extend function foo

fun printFoo(c: C) {
    println(c.foo())  // The type is class C
}

fun main(arg:Array<String>){
    printFoo(D())
}
Copy the code

Example execution output is as follows:

c
Copy the code

If the extension function is the same as the member function, the member function takes precedence over the extension function.

class C {
    fun foo(a) { println("Member function")}}fun C.foo(a) { println("Extension function")}fun main(arg:Array<String>){
    var c = C()
    c.foo()
}
Copy the code

Example execution output is as follows:

A member functionCopy the code

Extend an empty object

Within the extension function, this can be used to determine whether the receiver is NULL, so that the extension function can be called even if the receiver is NULL. Such as:

funAny? .toString(a): String {
    if (this= =null) return "null"
    // After null detection, "this" is automatically converted to a non-null type, so the following toString()
    // parse as a member function of Any class
    return toString()
}
fun main(arg:Array<String>){
    var t = null
    println(t.toString())
}
Copy the code

Example execution output is as follows:

null
Copy the code

Extended attributes

In addition to functions, Kotlin also supports attributes to extend attributes:

val <T> List<T>.lastIndex: Int
    get() = size - 1
 
Copy the code

Extended attributes can be defined in a class or kotlin file, not in a function. Since a property has no backing field, it is not allowed to be initialized and can only be defined by an explicitly supplied getter/setter.

Val foo. bar = 1 // Error: Extended properties cannot have initializersCopy the code

An extended attribute can only be declared as val.


Extensions of associated objects

If a class definition has a companion object, you can also define extension functions and attributes for the companion object.

The associated object calls the associated object with the “class name.” form. The extension function declared by the associated object is called with the class name qualifier:

class MyClass {
    companion object{}// Will be called "Companion"
}

fun MyClass.Companion.foo(a) {
    println("Extension functions accompanying objects")}val MyClass.Companion.no: Int
    get() = 10

fun main(args: Array<String>) {
    println("no:${MyClass.no}")
    MyClass.foo()
}
Copy the code

Example execution output is as follows:

no:10The extension function that accompanies the objectCopy the code

Extended scope

Usually extension functions or attributes are defined under the top-level package:

package foo.bar

fun Baz.goo(a) { …… } 
Copy the code

To use an extension outside the defined package, import the extension’s function name to use:

package com.example.usage

import foo.bar.goo // Import all extensions named goo
                   / / or
import foo.bar.*   // Import everything from foo.bar

fun usage(baz: Baz) {
    baz.goo()
}
Copy the code

Extensions are declared as members

Within a class you can declare extensions for another class.

In this extension, there are multiple implicit receivers, where instances of the class in which the extension method definition is defined are called distribution receivers and instances of the target type of the extension method are called extension receivers.

class D {
    fun bar(a) { println("D bar")}}class C {
    fun baz(a) { println("C baz")}fun D.foo(a) {
        bar()   / / call db ar
        baz()   / / call mount az
    }

    fun caller(d: D) {
        d.foo()   // Call the extension function}}fun main(args: Array<String>) {
    val c: C = C()
    val d: D = D()
    c.caller(d)

}
Copy the code

Example execution output is as follows:

D bar
C baz
Copy the code

Within class C, an extension to class D is created. At this point, C is the distribution receiver and D is the extension receiver. From the above example, it is clear that in the extension function, the member function that dispatches the receiver can be called.

If you are calling a function that exists on both the distributor and the extension receiver, the extension receiver takes precedence. To reference members of the distributor, you can use the qualified this syntax.

class D {
    fun bar(a) { println("D bar")}}class C {
    fun bar(a) { println("C bar")}// The same name as bar of class D

    fun D.foo(a) {
        bar()         // Call d.bar () to extend receiver preference
        this@C.bar()  / / call mount ar ()
    }

    fun caller(d: D) {
        d.foo()   // Call the extension function}}fun main(args: Array<String>) {
    val c: C = C()
    val d: D = D()
    c.caller(d)

}
Copy the code

Example execution output is as follows:

D bar
C bar
Copy the code

Extension functions defined as members can be declared open and can be overridden in subclasses. That is, the dispatch of such extension functions is virtual to the distribution recipient, but static to the extension recipient.

open class D {}class D1 : D() {}open class C {
    open fun D.foo(a) {
        println("D.foo in C")}open fun D1.foo(a) {
        println("D1.foo in C")}fun caller(d: D) {
        d.foo()   // Call the extension function}}class C1 : C() {
    override fun D.foo(a) {
        println("D.foo in C1")}override fun D1.foo(a) {
        println("D1.foo in C1")}}fun main(args: Array<String>) {
    C().caller(D())   // output "d.foo in C"
    C1().caller(D())  // print "d.foo in C1" -- distribute receiver virtual parsing
    C().caller(D1())  // output "d.foo in C" -- extend receiver static parsing

}
Copy the code

Example execution output is as follows:

D.foo in C
D.foo in C1
D.foo in C
Copy the code

A member in an associated object is equivalent to a static member in Java. Its life cycle is always with the class. Variables and functions can be defined inside the associated object, which can be referred to directly by the class name.

For associated object extension functions, there are two forms, one is to extend inside the class, one is to extend outside the class, the two forms of extension functions do not affect each other (even the name can be the same), even if the name is the same, they are completely different functions, and have the following characteristics:

  • (1) The adjoint object function of the in-class extension and the adjoint object of the out-of-class extension can have the same name. They are two independent functions and do not affect each other.
  • (2) When the adjoint object function of the in-class extension has the same name as the adjoint object of the out-class extension, other functions in the class preferentially refer to the adjoint object function of the in-class extension, that is, for other member functions in the class, the in-class extension masks the out-class extension;
  • (3) The adjoint object functions extended within a class can only be referenced by functions within the class, not by functions outside the class or functions within the adjoint object;
  • (4) The adjoint object functions extended outside the class can be referenced by functions inside the adjoint object.

For example:

class MyClass {
    companion object {
        val myClassField1: Int = 1
        var myClassField2 = "this is myClassField2"
        fun companionFun1(a) {
            println("this is 1st companion function.")
            foo()
        }
        fun companionFun2(a) {
            println("this is 2st companion function.")
            companionFun1()
        }
    }
    fun MyClass.Companion.foo(a) {
        println("Extension function accompanying object (internal)")}fun test2(a) {
        MyClass.foo()
    }
    init {
        test2()
    }
}
val MyClass.Companion.no: Int
    get() = 10
fun MyClass.Companion.foo(a) {
    println("Foo comes with an extension function outside the object")}fun main(args: Array<String>) {
    println("no:${MyClass.no}")
    println("field1:${MyClass.myClassField1}")
    println("field2:${MyClass.myClassField2}")
    MyClass.foo()
    MyClass.companionFun2()
}
Copy the code

Running results:

no:10
field1:1
field2:this isMyClassField2 foo comes with the object external extension functionthis is 2st companion function.
this is 1st companionFunction.foo comes with an extension function outside the objectCopy the code

Kotlin data class and seal class


Data classes

Kotlin can create a class that contains only data with the keyword data:

data class User(val name: String, val age: Int)
Copy the code

The compiler automatically extracts the following functions from the main constructor based on all declared attributes:

  • equals() / hashCode()
  • toString()Format such as"User(name=John, age=42)"
  • componentN() functionsCorresponding to attributes, in the order they are declared
  • copy()function

These functions are no longer generated if they are already explicitly defined in a class or inherit from a superclass.

In order for the generated code to be consistent and meaningful, the data class needs to meet the following criteria:

  • The main constructor takes at least one argument.
  • All primary constructors must have arguments identified asvalorvar ;
  • Data classes cannot be declared asabstract.open.sealedorinner;
  • Data classes cannot inherit from other classes (but can implement interfaces).

copy

Copy using the copy() function, we can copy objects and modify some properties. For the User class above, the implementation would look like this:

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
Copy the code

The instance

Copy the User data class using the copy class and modify the age property:

data class User(val name: String, val age: Int)


fun main(args: Array<String>) {
    val jack = User(name = "Jack", age = 1)
    val olderJack = jack.copy(age = 2)
    println(jack)
    println(olderJack)

}
Copy the code

The output is:

User(name=Jack, age=1)
User(name=Jack, age=2)
Copy the code

Data classes and deconstruction declarations

Component functions allow data classes to be used in destructuring declarations:

val jane = User("Jane".35)
val (name, age) = jane
println("$name.$age years of age") // prints "Jane, 35 years of age"
Copy the code

Standard data class

The standard library provides Pair and Triple. In most cases, naming data classes is a better design choice because the code is more readable and provides meaningful names and attributes.


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.

Declare a sealed class, using the sealed modifier. The sealed class can have subclasses, but all subclasses must be embedded within the sealed class.

Sealed cannot modify interface,abstract class(will report warning, but will not compile errors)

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

fun eval(expr: Expr): Double = when (expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
}
Copy the code

The key benefit of using a sealed class is that when you use a WHEN expression, you don’t need to add an else clause to the statement if you can verify that the statement covers all cases.

fun eval(expr: Expr): Double = when(expr) {
    is Expr.Const -> expr.number
    is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
    Expr.NotANumber -> Double.NaN
    // The 'else' clause is no longer needed because we have covered all cases
}
Copy the code

notes

For example, if we have a View in Android and we want to use the when statement to do two things on the view: show and hide, we can do this:

sealed class UiOp {
    object Show: UiOp()
    object Hide: UiOp()
} 
fun execute(view: View, op: UiOp) = when (op) {
    UiOp.Show -> view.visibility = View.VISIBLE
    UiOp.Hide -> view.visibility = View.GONE
}
Copy the code

Function actually can use enumerated above, but if we now want to add two operations: vertical and horizontal translation translation, and will carry some data, such as translation how much distance, animation data types in the process of translation, with the enumeration apparently is not so good, done, then the advantage of seal type can play, such as:

sealed class UiOp {
    object Show: UiOp()
    object Hide: UiOp()
    class TranslateX(val px: Float): UiOp()
    class TranslateY(val px: Float): UiOp()
}

 

fun execute(view: View, op: UiOp) = when (op) {
    UiOp.Show -> view.visibility = View.VISIBLE
    UiOp.Hide -> view.visibility = View.GONE
    is UiOp.TranslateX -> view.translationX = op.px // The when branch not only tells the view to move horizontally, but also how far it needs to move, something that traditional Java ideas like enumerations can't easily implement
    is UiOp.TranslateY -> view.translationY = op.px
}
Copy the code

In the code above, TranslateX is a class that can carry more than one message. It can also tell the view how many pixels it needs to translate horizontally, and it can even tell the type of animation it needs to translate.

In addition, if the branch of the WHEN statement does not need to carry information other than “show or hide the view” (that is, only the branch of the WHEN statement needs to carry no additional data), the singleton can be created using the object keyword, and the WHEN clause does not need the IS keyword. Subclasses of sealed classes are defined only when additional information is needed, and with sealed classes there is no need to use the else clause. Whenever we add another subclass or singleton to a sealed class, the compiler gives us a hint in the WHEN statement, so we can catch the error at compile time. This is something that switch-case statements and enumerations didn’t do in the past.

Finally, we can even wrap this set of operations into a function that we can call later, as follows:

// Encapsulate a list of UI actions
class Ui(val uiOps: List = emptyList()) {
    operator fun plus(uiOp: UiOp) = Ui(uiOps + uiOp)
}

// Define a set of operations
val ui = Ui() +
        UiOp.Show +
        UiOp.TranslateX(20f) +
        UiOp.TranslateY(40f) +
        UiOp.Hide
// Define the function to call
fun run(view: View, ui: Ui) {
    ui.uiOps.forEach { execute(view, it) }
}

run(view, ui) // Final call
Copy the code

For example, if we have a View in Android and we want to use the when statement to do two things on the view: show and hide, we can do this:

sealed class UiOp {
    object Show: UiOp()
    object Hide: UiOp()
} 
fun execute(view: View, op: UiOp) = when (op) {
    UiOp.Show -> view.visibility = View.VISIBLE
    UiOp.Hide -> view.visibility = View.GONE
}
Copy the code

Function actually can use enumerated above, but if we now want to add two operations: vertical and horizontal translation translation, and will carry some data, such as translation how much distance, animation data types in the process of translation, with the enumeration apparently is not so good, done, then the advantage of seal type can play, such as:

sealed class UiOp {
    object Show: UiOp()
    object Hide: UiOp()
    class TranslateX(val px: Float): UiOp()
    class TranslateY(val px: Float): UiOp()
}

 

fun execute(view: View, op: UiOp) = when (op) {
    UiOp.Show -> view.visibility = View.VISIBLE
    UiOp.Hide -> view.visibility = View.GONE
    is UiOp.TranslateX -> view.translationX = op.px // The when branch not only tells the view to move horizontally, but also how far it needs to move, something that traditional Java ideas like enumerations can't easily implement
    is UiOp.TranslateY -> view.translationY = op.px
}
Copy the code

In the code above, TranslateX is a class that can carry more than one message. It can also tell the view how many pixels it needs to translate horizontally, and it can even tell the type of animation it needs to translate.

In addition, if the branch of the WHEN statement does not need to carry information other than “show or hide the view” (that is, only the branch of the WHEN statement needs to carry no additional data), the singleton can be created using the object keyword, and the WHEN clause does not need the IS keyword. Subclasses of sealed classes are defined only when additional information is needed, and with sealed classes there is no need to use the else clause. Whenever we add another subclass or singleton to a sealed class, the compiler gives us a hint in the WHEN statement, so we can catch the error at compile time. This is something that switch-case statements and enumerations didn’t do in the past.

Finally, we can even wrap this set of operations into a function that we can call later, as follows:

// Encapsulate a list of UI actions
class Ui(val uiOps: List = emptyList()) {
    operator fun plus(uiOp: UiOp) = Ui(uiOps + uiOp)
}

// Define a set of operations
val ui = Ui() +
        UiOp.Show +
        UiOp.TranslateX(20f) +
        UiOp.TranslateY(40f) +
        UiOp.Hide
// Define the function to call
fun run(view: View, ui: Ui) {
    ui.uiOps.forEach { execute(view, it) }
}

run(view, ui) // Final call
Copy the code

Kotlin generic

Generics, or “parameterized types”, parameterize types and can be used on classes, interfaces, and methods.

Like Java, Kotlin provides generics to ensure type safety and eliminate the hassle of type coercion.

Declare a generic class:

class Box<T>(t: T) {
    var value = t
}
Copy the code

To create an instance of a class we need to specify a type parameter:

Val box: box <Int> = box <Int>(1) // Or val box = box (1) // The compiler does type inference, 1 is Int, so the compiler knows we are talking about box <Int>.Copy the code

The following example passes integer data and a string to the generic class Box:

class Box<T>(t : T) {
    var value = t
}

fun main(args: Array<String>) {
    var boxInt = Box<Int>(10)
    var boxString = Box<String>("Breeze")

    println(boxInt.value)
    println(boxString.value)
}
Copy the code

The output is:

10
Breeze
Copy the code

Define generic type variables that specify type parameters completely, or omit type parameters if the compiler can automatically assume them.

Kotlin generic functions are declared in the same way as Java, with type arguments placed before the function name:

Fun <T> boxIn(value: T) = Box(value) val box4 = boxIn<Int>(1) val box5 = boxIn(1Copy the code

When calling a generic function, you can omit the generic parameter if you can infer the type parameter.

The following example creates the generic function doPrintln, which does what it needs to do based on the type passed in:

fun main(args: Array<String>) {val age = 23 val name = "Breeze" val bool = true doPrintln(age) // doPrintln(name) // Character String DoPrintln (bool)} fun <T> doPrintln(content: T) {when (content) {is Int -> println(" integer number $content") is String -> println(" String converts to uppercase: ${content.toupperCase ()}") else -> println("T is not an integer, is not a string ")}}Copy the code

The output is:

Integer number 23 string conversion to uppercase: Breeze T is not an integer and is not a stringCopy the code

Generic constraint

We can use generic constraints to specify the types allowed for a given parameter.

Used in Kotlin: To constrain the type upper limit of a generic type.

The most common constraint is upper bound:

Fun <T: Comparable<T>> sort(list: list <T>) {//... }Copy the code

The Comparable subtype can be used instead of T. Such as:

Sort (listOf(1, 2, 3)) // OK. Type sort(listOf(HashMap<Int, String>())) HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>Copy the code

The default upper bound is Any? .

For multiple upper bound constraints, we can use the WHERE clause:

fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
    where T : CharSequence,
          T : Comparable<T> {
    return list.filter { it > threshold }.map { it.toString() }
}
Copy the code

Type variable

There is no wildcard type in Kotlin. There are two other things: declaration-site variance and type projections.

Declaration type changes

Type variations at declarations use covariant annotation modifiers: in, out, consumer in, producer out.

Using out makes a type parameter covariant. The covariant type parameter can only be used as output, as return value types but not as input types:

Class Breeze<out A>(val A: A) {fun foo(): A {return A}} fun main(args: Array<String>) {var strCo: Breeze<String> = Breeze("a") var anyCo: Breeze<Any> = Breeze<Any>("b") anyCo = strCo println(anyco.foo ())Copy the code

In inverts a type parameter that can only be used as input, as the type of the input parameter but not as the type of the return value:

Class Breeze<in A>(A: A) {fun foo(A: A) {}} fun main(args: Array<String>) { var strDCo = Breeze("a") var anyDCo = Breeze<Any>("b") strDCo = anyDCo }Copy the code

The asterisk projection

Sometimes, you might want to indicate that you don’t know anything about a type parameter, but still want to be able to use it safely. By “safe use” I mean defining a type projection of a generic type that requires all entity instances of the generic type to be subtypes of the projection.

Kotlin provides a syntax for this problem called star-projection:

  • If the type is defined as Foo, where T is a covariant type argument, the upper bound is TUpper, and Foo<> is equivalent to Foo. It says that when T is unknown, you can safely get from Foo<> reads a value of type TUpper.
  • Suppose the type is defined as Foo, where T is a reverse-covariant type parameter, and Foo<> is equivalent to Foo. It says that when T is unknown, you cannot safely call Foo<> write anything.
  • If the type is defined as Foo, where T is a covariant type argument and the upper bound is TUpper, Foo<*> is equivalent to Foo for reading values and Foo for writing values.

If more than one type parameter exists in a generic type, each type parameter can be projected separately. For example, if the type is defined as interface Function<in T, out U>, the following asterisk projections can occur:

  1. Function<*, String> = Function
  2. Function

    = Function

    ;
    ,>
    ,>
  3. Function<. > , 代表 Function<in Nothing, out Any?> .

Note: Asterisk casts are very similar to Java raw types, but can be used safely

And the thing about asterisk projection is that * refers to all types, the same thing as Any, right?

It is easy to understand that…

class A<T>(val t: T, val t2 : T, val t3 : T) class Apple(var name : String) fun main(args: Array<String>) {// Use class val a1: A<*> = A(12, "String", Apple(" Apple ")) val a2: A<Any? > = A(12, "String", Apple(" Apple ")) // the same as a1 val Apple = a1. T3 // The argument type is Any println(Apple) val apple2 = Apple as Apple // strong conversion to Apple class Println (apple2.name) // Use array val l:ArrayList<*> = arrayListOf("String",1,1.2f,Apple(" Apple ")) for (item in l){println(item) }}Copy the code

Kotlin enumeration class

The most basic use of enumeration classes is to implement a type-safe enumeration.

Enumeration constants are separated by commas and each enumeration constant is an object.

enum class Color{
    RED,BLACK,BLUE,GREEN,WHITE
}
Copy the code

Enumeration initialization

Each enumeration is an instance of an enumeration class that can be initialized:

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}
Copy the code

The default name is the enumeration character name and the value starts at 0. If you need to specify a value, you can use its constructor:

enum class Shape(value:Int){
    ovel(100),
    rectangle(200)
}
Copy the code

Enumerations also support the ability to declare their own anonymous classes and corresponding methods, as well as methods that override base classes. Such as:

enum class ProtocolState {
    WAITING {
        override fun signal() = TALKING
    },

    TALKING {
        override fun signal() = WAITING
    };

    abstract fun signal(): ProtocolState
}
Copy the code

If an enumeration class defines any members, separate the enumeration constant definitions in the member definition with a semicolon


Using enumerated constants

Enumeration classes in Kotlin have synthetic methods that allow you to iterate over defined enumeration constants and get enumeration constants by their names.

Enumclass.valueof (value: String): EnumClass // If name is the enumeration value, IllegalArgumentException EnumClass.values() will be thrown: Array<EnumClass> // Returns an enumeration value in the form of an ArrayCopy the code

To get enumeration information:

Val name: String // Gets the enumeration name val ordinal: Int // Gets the order in which the enumeration values are defined in all enumeration arraysCopy the code

The instance

enum class Color{
    RED,BLACK,BLUE,GREEN,WHITE
}

fun main(args: Array<String>) {
    var color:Color=Color.BLUE

    println(Color.values())
    println(Color.valueOf("RED"))
    println(color.name)
    println(color.ordinal)

}
Copy the code

Since Kotlin 1.1, constants in an enumerated class can be accessed generically using the enumValues

() and enumValueOf

() functions:

enum class RGB { RED, GREEN, BLUE } inline fun <reified T : Enum<T>> printAllValues() { print(enumValues<T>().joinToString { it.name }) } fun main(args: Array<String>) {printAllValues<RGB>() // RED, GREEN, BLUE}Copy the code

Kotlin object expression and object declaration

Kotlin uses object expressions and object declarations to create an object that makes a slight change to a class without declaring a new subclass.


Object expression

An object that implements an anonymous inner class through an object expression is used in method arguments:

window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { // ... } override fun mouseEntered(e: MouseEvent) { // ... }})Copy the code

Objects can inherit from a base class, or implement other interfaces:

Open class A(x: Int) {public open val y: Int = x} interface B {... } val ab: A = object : A(1), B { override val y = 15 }Copy the code

If the supertype has a constructor, arguments must be passed to it. Multiple supertypes and interfaces can be separated by commas.

Object expressions can override the class definition to get an object directly:

Fun main(args: Array<String>) {val site = object {var name: String = "" var url: String = "www.Breeze.com" } println(site.name) println(site.url) }Copy the code

Note that anonymous objects can be used as types declared only in local and private scopes. If you use an anonymous object as the return type of a public function or as the type of a public property, the actual type of the function or property will be the supertype declared by the anonymous object, or Any if you don’t declare Any. Members added to anonymous objects will not be accessible.

Class C {// Private function, so its return type is anonymous object type private fun foo() = object {val x: String = "x"} // Public function, so its return type is Any fun publicFoo() = object {val x: String = "x"} fun bar() {val x1 = foo().x // no problem val x2 = publicFoo().Copy the code

Other variables in scope can be easily accessed in object representation:

fun countClicks(window: JComponent) { var clickCount = 0 var enterCount = 0 window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { clickCount++ } override fun mouseEntered(e: MouseEvent) {enterCount++}}) //... }Copy the code

Object statement

Kotlin uses the object keyword to declare an object.

In Kotlin we can easily obtain a singleton through object declarations.

Object DataProviderManager {fun registerDataProvider(provider: DataProvider) {//... } val allDataProviders: Collection<DataProvider> get() = //... }Copy the code

To reference this object, we simply use its name:

DataProviderManager. RegisterDataProvider (...)Copy the code

Of course you can define a variable to get the object, but when you define two different variables to get the object, you can’t get two different variables. In other words, in this way, we get a singleton.

var data1 = DataProviderManager
var data2 = DataProviderManager
data1.name = "test"
print("data1 name = ${data2.name}")  
Copy the code

The instance

In the following example, both objects output the same URL:

Object Site {var url:String = "" val name: String = ""} fun main(args: Array<String>) { var s1 = Site var s2 = Site s1.url = "www.Breeze.com" println(s1.url) println(s2.url) }Copy the code

The output is:

www.Breeze.com
www.Breeze.com
Copy the code

Objects can have supertypes:

Object DefaultListener: MouseAdapter() {override fun mouseClicked(e: MouseEvent) { } Override fun mouseEntered(e: MouseEvent) {//... }}Copy the code

Unlike an object expression, when an object is declared inside another class, the object cannot be accessed by an instance of an external class, but only by the class name. Likewise, the object cannot be accessed directly by the methods and variables of the external class.

Class Site {var name = "www.Breeze.com" fun showName(){print{"desk legs $name"} // Error, cannot access methods and variables of external class}}} Fun main(args: Array<String>) {var site = site () site.desktop. url // error, object site.desktop. url cannot be accessed through an instance of an external class // Correct}Copy the code

Associated object

An object declaration within a class can be marked with the Companion keyword, so that it is associated with an external class through which we can directly access the object’s internal elements.

class MyClass { companion object Factory { fun create(): MyClass = MyClass()}} val instance = myclass.create () // Access the inner element of the objectCopy the code

We can omit the object name of this object and use Companion instead of the object name to declare:

class MyClass {
    companion object {
    }
}

val x = MyClass.Companion
Copy the code

Note: Only one internal object can be declared within a class, and the keyword Companion can be used only once.

The members of the companion object look like static members of other languages, but at run time they are still instance members of the real object. For example, you can also implement interfaces:

interface Factory<T> {
    fun create(): T
}


class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass = MyClass()
    }
}
Copy the code

Semantic differences between object expressions and object declarations

There is an important semantic difference between object expressions and object declarations:

  • Object expressions are executed immediately where they are used
  • Object declarations are lazily initialized when they are first accessed
  • The initialization of the associated object matches the semantics of the Java static initializer when the corresponding class is loaded (parsed)

Kotlin commissioned

Delegation pattern is a basic skill in software design pattern. In the delegate pattern, two objects participate in processing the same request, and the receiving object delegates the request to the other object.

Kotlin directly supports the delegation model, which is more elegant and concise. Kotlin implements delegation through the keyword BY.


Commissioned by class

Class delegates are methods defined in one class that are actually implemented by calling methods of objects of another class.

In the following example, the Derived class Derived inherits all the methods of the interface Base and delegates an object from the passed Base class to execute those methods.

Interface Base {fun print()} class BaseImpl(val x: Int) : Base {override fun print() {print(x)}} class Derived(b: Base) : Base by fun main(args: Array<String>) {val b = BaseImpl(10) Derived(b).print()Copy the code

In the Derived declaration, the BY clause says that b is stored inside an object instance of Derived, and that the compiler will generate all methods inherited from the Base interface and forward the calls to B.


Attribute to entrust

Attribute delegation means that the value of an attribute of a class is not directly defined in the class, but entrusted to a proxy class, so as to achieve unified management of the attributes of the class.

Attribute delegate syntax format:

Var < attribute name >: < type > by < expression >Copy the code
  • Var /val: property type (variable/read-only)
  • Attribute name: Attribute name
  • Type: The data type of the property
  • Expression: delegate proxy class

The expression after the by keyword is the delegate, and the property’s get() method (and set() method) will be delegated to the object’s getValue() and setValue() methods. The property delegate does not have to implement any interface, but it must provide the getValue() function (and, for the var attribute, the setValue() function).

Define a delegated class

This class needs to contain the getValue() and setValue() methods, and the arguments thisRef is the object of the delegate class and prop is the object of the delegate property.

Import kotlin.reflect.KProperty class Example {var p: String by Delegate()} class Delegate {operator fun getValue(thisRef: Any? , property: KProperty<*>): String {return "$thisRef, where ${property.name} attribute "} operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {println("$thisRef ${property.name} attribute = $value")}} fun main(args: Array<String>) {val e = Example() println(e.p) Call getValue() function e.p = "Breeze" // Call setValue() function println(e.p)}Copy the code

The output is:

Example@433c675d, where the p attribute is delegated to Example@433c675d, is assigned to Breeze Example@433c675d, where the p attribute is delegatedCopy the code

The standard delegation

Kotlin’s library already has a number of factory methods built into it to delegate attributes.

The delay attribute Lazy

Lazy () is a function that takes a Lambda expression as an argument and returns an instance of lazy that can be used as a delegate to implement the delay attribute: The first call to get() executes the LAMda expression passed to lazy() and records the result, and subsequent calls to get() simply return the result of the record.

val lazyValue: String by lazy { println("computed!" ) // First call output, second call does not execute "Hello"} fun main(args: Array<String>) {println(lazyValue) {println(lazyValue)}Copy the code

Execution output:

computed!
Hello
Hello
Copy the code

Observable property Observable

Observables can be used to implement the observer pattern.

The Delegates.Observable () function receives two parameters: the first is the initialization value and the second is the handler for the attribute value change event.

A handler that executes the event after an attribute is assigned and takes three parameters: the assigned attribute, the old value, and the new value:

import kotlin.properties.Delegates class User { var name: {if ($old -> $new)}} Fun main(args: $Delegates: {if ($old -> $new)}}... Array<String>) {val user = user () user.name = "first assignment" user.name = "second assignment"}Copy the code

Execution output:

Old value: initial value -> New value: first assignment Old value: first assignment -> new value: second assignmentCopy the code

Store attributes in a map

A common use case is to store property values in a map. This is often seen in applications like parsing JSON or doing other “dynamic” things. In this case, you can implement delegate properties using the mapping instance itself as the delegate.

class Site(val map: Map<String, Any? >) { val name: String by map val url: String by map } fun main(args: Array<String>) {// The constructor takes a mapping parameter val site = site (mapOf("name" to" Println (site.name) println(site.url)}Copy the code

Execution output:

Rookie tutorial www.Breeze.comCopy the code

If you use the var attribute, you need to change the Map to a MutableMap:

class Site(val map: MutableMap<String, Any? >) { val name: String by map val url: String by map } fun main(args: Array<String>) { var map:MutableMap<String, Any? Word-wrap: break-word! Important; "> = mutableMapOf("name" to" "url" to "www.Breeze.com" ) val site = Site(map) println(site.name) println(site.url) println("--------------") map.put("name", "Google") map.put("url", "www.google.com") println(site.name) println(site.url) }Copy the code

Execution output:

Novice tutorial www.Breeze.com -- -- -- -- -- -- -- -- -- -- -- -- -- -- Google www.google.comCopy the code

Not Null

NotNull is suitable for situations where attribute values cannot be determined during initialization.

class Foo {
    var notNullBar: String by Delegates.notNull<String>()
}

foo.notNullBar = "bar"
println(foo.notNullBar)
Copy the code

Note that an exception will be thrown if the property is accessed before the assignment.


Local delegate property

You can declare local variables as delegate properties. For example, you can lazily initialize a local variable:

fun example(computeFoo: () -> Foo) { val memoizedFoo by lazy(computeFoo) if (someCondition && memoizedFoo.isValid()) { memoizedFoo.doSomething() }}Copy the code

The memoizedFoo variable is only evaluated on the first access. If someCondition fails, this variable is not evaluated at all.


Attribute delegate requirements

For a read-only attribute (that is, the val attribute), its delegate must provide a function called getValue(). This function takes the following arguments:

  • ThisRef – must be the same as the attribute owner type (for extended attributes – the extended type) or its supertype
  • Property — Must be type KProperty<*> or its supertype

This function must return the same type (or subtype) as the property.

For a mutable property (that is, the var property), in addition to the getValue() function, its delegate must also provide a function called setValue() that takes the following arguments:

Property — must be of type KProperty<*> or its supertype New value — must be of the same type as the property or its supertype.


Translation rules

Behind the implementation of each delegate property, the Kotlin compiler generates and delegates a secondary property to it. For example, for the property prop, generate the hidden property prop$delegate, and the accessor’s code simply delegates to this additional property:

class C { var prop: Class C {private val prop$delegate = MyDelegate() var prop: class C {private val prop$delegate = MyDelegate() var prop: Type get() = prop$delegate.getValue(this, this::prop) set(value: Type) = prop$delegate.setValue(this, this::prop, value) }Copy the code

The Kotlin compiler provides all the necessary information about prop in parameters: The first parameter, this, refers to an instance of the external class C and this:: Prop is a reflection object of type KProperty that describes the prop itself.


Provide commissioned

By defining the provideDelegate operator, you extend the logic for creating a property implementation to delegate. If the object used to the right of BY defines provideDelegate as a member or extension function, that function is called to create the property delegate instance.

One possible use scenario for provideDelegate is to check for consistency when a property is created, not just in its getter or setter.

For example, if you want to check the property name before binding, you could write:

class ResourceLoader<T>(id: ResourceID<T>) { operator fun provideDelegate( thisRef: MyUI, prop: KProperty<*> ): ReadOnlyProperty<MyUI, T> {checkProperty(thisRef, prop.name)} private fun checkProperty(thisRef: MyUI, name: String) {... Fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> {...... } class MyUI { val image by bindResource(ResourceID.image_id) val text by bindResource(ResourceID.text_id) }Copy the code

The provideDelegate argument is the same as getValue:

  • ThisRef – must be the same as the attribute owner type (for extended attributes – the extended type) or its supertype
  • Property — Must be type KProperty<*> or its supertype.

During the creation of the MyUI instance, the provideDelegate method is called for each property and the necessary validation is performed immediately.

Without this ability to intercept the binding between a property and its delegate, you would have to explicitly pass the property name to achieve the same functionality, which is not very convenient:

Class MyUI {val image by bindResource(resourceid.image_id, "image") val text by bindResource(ResourceID.text_id, "text") } fun <T> MyUI.bindResource( id: ResourceID<T>, propertyName: String): ReadOnlyProperty<MyUI, T> {checkProperty(this, propertyName) // Create delegate}Copy the code

In the generated code, the provideDelegate method is called to initialize the auxiliary Prop $delegate property. Compare the code generated for the property declaration val Prop: Type by MyDelegate() with the code generated above (when the provideDelegate method does not exist) :

class C { var prop: Type by MyDelegate()} // This code is generated by the compiler when the "provideDelegate" function is available: Class C {// Call "provideDelegate" to create additional "delegate" properties private val prop$delegate = MyDelegate().provideDelegate(this, this::prop) val prop: Type get() = prop$delegate.getValue(this, this::prop) }Copy the code

Note that the provideDelegate method only affects the creation of auxiliary properties, not the code generated for getters or setters.