Android development knowledge learning - Kotlin advanced

secondary structure

The declaration prefix is ​​modified with construct

class Person {
    
     
    constructor(parent: Person){
    
    
   }
}

If there is a primary constructor, each secondary constructor needs to be delegated to the primary constructor, either directly or through another constructor.

class constructor Person(val name:String) {
    
    
   constructor(name: String,parent: Person):this(name){
    
    
         }  
}

main constructor

Main constructor: It is part of the class header, following the class name (can take parameters), without any annotations and visibility modifiers. like:

class User (username : String?,password : String?) {
    
    
}

There is no code in the main constructor, and the initialization code is placed in the code block of the keyword init; it can also be used in the attribute initializer declared within the class body.

class User (username : String?,password : String?) {
    
    
      init {
    
    
              //执行你的操作
          }
}
class User (username : String?) {
    
    }
       var username=username.getname()
}

init code block

The main structure cannot contain any code, and the initialization code can be placed in the init code block

class CodeView constructor(context: Context) :
TextView(context) {
    
    
init {
    
    
//...
}
}

During initialization, initialization blocks are executed in the order they appear in the file.
Initialization blocks in Kotlin are executed in the order they appear in the file. Some specific initialization operations can be performed there.

For example, here is an example of a Kotlin class containing two initialization blocks:

class MyClass {
    
    
    var myProperty1: String? = null

    init {
    
    
        println("Initializing myProperty1")
        myProperty1 = "Hello"
    }

    init {
    
    
        println("Initializing myProperty2")
        // 在这里可以进行其他初始化操作
    }
}

In the above example, the MyClass class contains two initialization blocks. The first initialization block initializes the myProperty1 property and sets it to "Hello". The second initialization block just prints a message and does not initialize any properties. Since the initialization blocks are executed in the order they appear in the file, the first initialization block is executed first, followed by the second initialization block.

When an instance of MyClass is created, the output will be the following:

Initializing myProperty1
Initializing myProperty2

This indicates that the first initialization block executes first, then the second initialization block.

Constructed properties

In Kotlin, a constructed property is a special property that is initialized via a constructor when a class is instantiated. Constructed properties can be defined by adding the var/val keyword in front of the main constructor parameter, making it a member variable at the same time.

Here is an example using constructed properties:

class Person(var name: String, var age: Int) {
    
    
    init {
    
    
        println("Person instance is created with name: $name and age: $age")
    }
}

fun main() {
    
    
    val person = Person("John Doe", 30)
    println(person.name) // 输出: John Doe
    println(person.age) // 输出: 30
}

In the above example, the Person class has two constructed properties, name and age, which are declared in the constructor of the class and defined using the var keyword, making them both member variables. In the init block we can access these properties and perform initialization operations. In the main function, we create a Person instance and access its properties.

data class

Automatically generated in data class

  • toString()

  • hashCode()

  • equals()

  • copy() (shallow copy)
    Insert image description here

  • componentN()

The benefits of using data classes include: simplifying code and reducing errors

equality

  • == Structural equality (call equals() to compare)
    Insert image description here

  • === Reference (address value) equality
    Insert image description here

deconstruct

You can "deconstruct" an object into many variables

val (username,password) = User

Corresponding Java code

val username = User.component1()
val password = User.component2()

Elvis operator

The operation of if null can be simplified through the operation of ?:

// lesson.date 为空时使用默认值
val date = lesson.date?: "日日期待定"

// lesson.state 为空时提前返回函数
val state = lesson.state?: return

// lesson.content 为空时抛出异常
val content = lesson.content ?: throw IllegalArgumentException("content expected")

when operator

The when expression can accept a return value, and multiple branches with the same processing method can be put together and separated by commas.

val colorRes = when (lesson.state) {
    
    
Lesson.State.PLAYBACK, null -> R.color.playback
Lesson.State.LIVE -> R.color.live
Lesson.State.WAIT -> R.color.wait
}

when expressions can be used in place of if-ese-ifchains. If no parameters are provided, all branch conditions are Boolean expressions

val colorRes = when {
    
    
(lesson.state == Lesson.State.PLAYBACK) ->
R.color.playback
(lesson.state == null) -> R.color.playback
(lesson.state == Lesson.State.LIVE) -> R.color.live
(lesson.state == Lesson.State.WAIT) -> R.color.wait
else -> R.color.playback
}

operator

By operatormodifying the "specific function name" function, for example plus, getyou can achieve the effect of overloading the operator

expression translated as
a + b a.plus(b)
a - b a.minus(b)
a * b a.times(b)
a / b a.div(b)
fun main() {
    
    
    val num1 = ComplexNumber(2.0, 3.0)
    val num2 = ComplexNumber(4.0, 5.0)
    
    val result1 = num1 + num2 // 使用重载的加法运算符
    val result2 = num1 - num2 // 使用重载的减法运算符
    val result3 = num1 * num2 // 使用重载的乘法运算符
    val result4 = num1 / num2 // 使用重载的除法运算符
    
    println("Result 1: $result1")
    println("Result 2: $result2")
    println("Result 3: $result3")
    println("Result 4: $result4")

Lambda

Lambda expressions are widely used, and they make it easier to write concise, elegant code.
If the last parameter of the function is a lambda, the lambda expression can be placed outside the parentheses:

lessons.forEach(){
    
     lesson : Lesson ->
// ...
}

Insert image description here

If your function has only one lambda parameter, the parentheses can be omitted:

lessons.forEach {
    
     lesson : Lesson ->
// ...
}

Insert image description here

If the expression has only one parameter, it can be omitted and accessed lambdathrough implicitit

lessons.forEach {
    
     // it
// ...
}

Insert image description here

Insert image description here

cycle

Through the standard function repeat():

repeat(100) {
    
    
   println(it)
}

through interval

for (i in 0..99) {
    
    
}
// until 不包括右边界
for (i in 0 until 100) {
    
    
}

infix function

Must be a member function or extension function.
Must accept only one parameter and cannot have a default value.

// until() 函数的源码
public infix fun Int.until(to: Int): IntRange {
    
    
if (to <= Int.MIN_VALUE) return IntRange.EMPTY
return this .. (to - 1).toInt()
}

Kotlin's infix function is a special function whose name is the operation it expresses, which can simplify the code. They are often used to simplify operations between two objects.
Here is a simple infix function example:

infix fun Int.add(other: Int): Int {
    
    
    return this + other
}

This function adds two integers and returns their sum. By using the infix keyword, we can call this function directly between two integers without creating a new function object. For example:

val result = 2 + 3 // 这里使用了 infix 函数
println(result) // 输出:5

In this example, we call the add function directly between 2 and 3, instead of using the function name and parentheses like a normal function.
It should be noted that the infix function can only have one parameter, and it must be the last parameter of the function. Additionally, the infix function cannot change the values ​​of function parameters because they are considered read-only.

Nested functions

In Kotlin, you can continue to declare functions within functions

fun main() {
    
    
    // 外部函数
    fun outerFunction() {
    
    
        // 内部函数
        fun innerFunction() {
    
    
            println("我是内部函数")
        }
        
        innerFunction() // 调用内部函数
    }
    
    outerFunction() // 调用外部函数
}

In this example, innerFunction is a nested function of outerFunction. We first called outerFunction, and then called innerFunction inside outerFunction. Nested functions are very useful for encapsulation and code organization. Some auxiliary functions or private methods can be defined inside a function.

  • Internal functions can access parameters of external functions
  • Each time it is called, a function object is generated

Annotation usage target

Annotations are used as a metadata mechanism to pass additional information to the compiler, or for reflection at runtime.
When an element may contain multiple contents (such as construction attributes, member attributes), When using annotations, you can use "annotation usage target" to make the annotations have an effect on the target, such as @file:, @get:, @set:, etc.

  • @file:jvmName specifies the JVM name of the function in the generated Java bytecode file
    Insert image description here
  • The @get:jvmName annotation is used to specify the JVM name of the attribute in the generated Java bytecode file.
    Insert image description here
  • The @set:jvmName annotation is used to specify the JVM name of the attribute in the generated Java bytecode file.
    Insert image description here

function simplification

Functions that originally return directly can be simplified by using the symbol =
Insert image description here
Insert image description here

Function parameter default value

Example of using function parameter default values ​​in Kotlin:

fun printMessage(message: String = "Hello") {
    
    
    println(message)
}
printMessage() // 输出 "Hello"
printMessage("World") // 输出 "World"

Java function overloading can be replaced by default function parameter values

// 使用 @JvmOverloads 对 Java 暴露重载函数
@JvmOverloads
fun toast(text: CharSequence, duration: Int =
Toast.LENGTH_SHORT) {
    
    
Toast.makeText(this, text, duration).show()
}

In Java, overloaded functions (also known as methods) refer to defining multiple methods in a class with the same name but different parameter lists. These methods often have different behaviors to handle different input parameters. By using the same method name, Java allows you to call the appropriate method based on the type and number of parameters passed to the method.

public class Example {
    
    
    public void print(String message) {
    
    
        System.out.println(message);
    }
    
    public void print(int number) {
    
    
        System.out.println(number);
    }
}

In the above example, the print method is overloaded twice, once to accept a string parameter and another time to accept an integer parameter. Depending on the parameter types passed when calling, the appropriate method will be called

Expand

  • Extension functions can add a function to any class, thereby replacing the utility class
    Insert image description here

  • When the extension function and member function are the same, the member function is called first

  • Extension functions are statically resolved, and the calling function is determined at compile time (no polymorphism)
    Insert image description here
    Insert image description here

function type

The function type consists of "incoming parameter type" and "return value type". Use "->" to connect. Incoming parameters need to use "()". If the return value is Unit, the function type cannot be omitted. It is actually an interface. We pass When using a function, you can pass "::function name", or "anonymous function" or use "lambda"
Insert image description here
anonymous function
Insert image description here
lambda to call the transfer function
Insert image description here
in Java.
Insert image description here

Insert image description here

inline function

The semantics of inline functions are simple: copy and paste the function body where the function is called. There is no difficulty in using it, just use the inline keyword to modify the function.
A function modified with inline is an inline function. The inline modifier affects the function itself and the lambda expression passed to it. All of these will be inlined to the call site, that is, the compiler will call the function where it is called. method body instead of creating a function object and generating a reference

Inline functions combined with "function types" can reduce the objects generated by "function types". Functions declared
using inlinekeywords are "inline functions". At "compile time", the function body in "inline functions" will be directly inserted into call place.
So when writing inline functions, you need to pay attention and try to reduce the number of lines of code in inline functions!

The use of Kotlin inline functions is declared through the keyword inline. Inline functions are inserted into the code where they are called during compilation to reduce the overhead of function calls.
Here is an example of a simple Kotlin inline function:

inline fun max(a: Int, b: Int): Int {
    
    
    return a > b ? a : b
}

In this example, the max function is declared as an inline function. When you call the max function in your code, the Kotlin compiler inserts its code directly into the call site instead of making a regular function call.

It should be noted that the parameters of inline functions must be of specific types and cannot be nullable types. In addition, the code for inline functions must be very simple, otherwise it will increase the code size and compilation time.
In addition to using the inline keyword when defining a function, you can also use the inline keyword when calling the function to force the function to be inlined. For example:

fun main(args: Array<String>) {
    
    
    inline fun printMax(a: Int, b: Int) {
    
    
        println(max(a, b))
    }
    printMax(10, 20) // 内联函数调用
    printMax(100, 200) // 内联函数调用
}

In this example, the printMax function is declared as an inline function and is called using the inline keyword. This way, the Kotlin compiler injects its code directly into the call site, instead of making a regular function call.

Partially disabled using inline

noinlineSome parameters can be prohibited from participating in inline compilation

inline fun foo(inlined: () -> Unit, noinline notInlined:
() -> Unit) {
    
    
//......
}

In Kotlin, inline functions are functions that are inlined by the compiler by default. The advantage of inline functions is that they can reduce the cost of function calls and improve code execution efficiency. However, if the code of the inline function is large, it will increase the size of the code, thus affecting the overall performance of the program.

The noinline keyword can be used to prohibit certain parameters from participating in inline compilation. When you define a function, you can use the noinline keyword to mark certain parameters so that the compiler will not inline these parameters into the call to the function.

Here is an example using the noinline keyword:

fun foo(inlineParam: Int, noinlineParam: String) {
    
    
    // 函数内容
}

In this example, the inlineParam parameter will be inlined by the compiler, but the noinlineParam parameter will not be inlined. In this way, when you call the foo function, the compiler will insert the inlineParam parameter directly into the call, while the noinlineParam parameter will remain intact and will not participate in inline compilation.

It should be noted that using the noinline keyword will increase the size of the code because the compiler needs to keep a separate code block for each function. Therefore, there is a trade-off between code size and execution efficiency when using the noinline keyword.

reified type parameters

Because of the existence of inline functions, we can inline + reifiedachieve the effect of "true generics" by combining

val RETROFIT = Retrofit.Builder()
.baseUrl("https://api.hencoder.com/")
.build()

inline fun <reified T> create(): T {
    
    
return RETROFIT.create(T::class.java)
}
val api = create<API>()

In Kotlin, the combination of inline functions and reified type parameters can achieve an effect similar to "true generics".

Inline functions refer to inserting the function body directly into the calling point in the function definition, thereby reducing the overhead of function calls. The advantage of inline functions is that they can improve the execution efficiency of the code, but it should be noted that the number of lines of code for inline functions should not be too many, otherwise it may increase the code length and compilation time.

Reified type parameters refer to reifying type parameters into actual types when doing generic programming. In Kotlin, the reified modifier can be used to reify a generic type parameter, so that the actual type information of the generic type can be obtained at runtime.

By combining inline functions and specific type parameters, an effect similar to "true generics" can be achieved. In Kotlin, you can use inline functions and reified type parameters to achieve static type checking and runtime type information acquisition similar to Java.

Here is a simple example code:

inline fun <reified T> printInstance(instance: T) {
    
    
    val className = T::class.java.simpleName
    println("The instance of $className is: $instance")
}

fun main() {
    
    
    val stringInstance = "Hello, world!"
    val intInstance = 42

    printInstance(stringInstance) // 输出:The instance of String is: Hello, world!
    printInstance(intInstance)   // 输出:The instance of Int is: 42
}

In this example, we define an inline function printInstance that accepts a parameter instance of type T. By using the reified modifier, we can get the actual type information of the generic type at runtime. Inside the function, we use T::class.java.simpleName to get the name of the generic type, and println to print out the value and type information of the instance.

In the main function, we pass a string instance and an integer instance to the printInstance function respectively. Due to the use of inline functions and reified type parameters, we can obtain the actual type information of the generic type directly inside the function and print out the value and type information of the instance.

abstract properties

In Kotlin, we can declare abstract attributes. When subclasses override abstract attributes, they need to rewrite the correspondingsetter/getter
Insert image description here

entrust

Property delegation

For some common attribute operations, we can implement them only once through delegation, for example:
lazydelayed attributes: the value is only calculated on the first access
observable; observable attributes: notification collection when the attribute changes
map: save the attribute into existence In a map,
for a read-only property (that is, declared by val), the delegate object must provide a getValue()function named

For a mutable property (that is , declared with var), the delegate object also provides
setValue()functionsgetValue()

class delegate

Inheritance can be reduced through the class delegation model
. For class delegation, the compiler will give priority to using its own rewritten functions instead of the functions of the delegate object.

interface Base {
    
    
fun print()
}
class BaseImpl(val x: Int) : Base {
    
    
override fun print() {
    
    
print(x)
}
}
// Derived 的 print 实现会通过构造参数中的 b 对象来完成。
class Derived(b: Base) : Base by b

Kotlin standard functions

When using it, you can make some judgments through simple rules:

  • Return to self -> choose from apply and also

    • Use this as a parameter in the scope ----> select apply
      Insert image description here

    • Use it as parameter in scope ----> select also
      Insert image description here

  • No need to return self -> choose from run and let

    • Use this as a parameter in the scope ----> select run
      -

    • Use it as a parameter in the scope ----> select let
      Insert image description here

apply is suitable when performing additional operations on an object;
let is suitable when used with null judgment (preferably member variables, not local variables, local variables are more suitable for if);
with is suitable when performing multiple operations on the same object

After-school questions

What will the following code output?

data class User

fun main(){
    
    
val user = User()
val copy = user.copy()
println(user == copy)
println(user === copy)
}

This code defines a data class named User in Kotlin, then creates a User instance user and copies it, creating another instance copy. Then, it prints the results of the two comparisons:

  1. user == copy: This line will print true because user and copy are instances with the same attribute values ​​and status.
  2. user === copy: This line will output false because although user and copy have the same property values ​​and status, they are two different objects occupying different locations in memory. The === operator
    is used in Kotlin to compare whether the references of two objects are equal, not just their property values.
  1. [Essay question] Declare a variable call and write a "function type declaration" in which the "incoming parameter type" is the Request type and the "return value type" is the Response type.

val call: /* function type declaration*/

You can use Kotlin to declare a function type that accepts a parameter of type Request and returns a result of type Response. Here's how to declare such a function type:

val call: (Request) -> Response

Here, we create a type alias named CallType, which represents a
function that accepts Request type parameters and returns a Response type result. You can change the Request and Response types according to actual needs.

Guess you like

Origin blog.csdn.net/weixin_74239923/article/details/134134216