Android Kotlin实战之高阶使用泛型扩展协程懒加载详解

前言:

通过前面几篇文章,我们已基本掌握kotlin的基本写法与使用,但是在开发过程中,以及一些开源的API还是会出现大家模式的高阶玩法以及问题,如何避免,接下来讲解针对原来的文章进行一些扩展,解决大家在工作中遇到的问题,如何去解决

如果还有人不了解kotlin,可以查看我的基础篇kotlin。

Android kotlin在实战过程问题总结与开发技巧详解_kotlin 同步锁_蜗牛、Z的博客-CSDN博客

Kotlin语法详解与实践教程,区分JAVA以及如何闭坑_kotlin mapof可以嵌套 to_蜗牛、Z的博客-CSDN博客

1. lazy

lazy是kotlin中的懒加载,这种写法在很多场景中都有,懒加载并不是立刻在内存中申请,而是通过lazy(),调用才会

lazy的只能是常量,用val修饰,变量是全局也可以是局部变量

通过这个lazy{}可以看出,无论你在最上面调用什么,默认值都是最后一个。通过IDE,可以提示看到ccc ^lazy,这就是懒加载的值。这个值也可以通过方法获取

fun a() {
    val aa: String by lazy {
        MyLog.log("val aa:String by lazy start")
        "aaaa"
        "bbbb"
        b()

    }

    MyLog.log(aa)
    MyLog.log(aa)
}


fun b(): String {
    return "aaaaa"
}

一旦懒加载对象被调用过,后期在调用将只能取到值,不会再处理其他的lazy{} 方法体。

注意:lazy和lateinit无法一起使用。lazy修饰val变量,lateinit修饰的是var变量。

2.lateinit

也是懒加载机制,对象是var的格式。但是,这个变量需要是全局变量,不能是局部变量。

lateinit var string: String
lateinit var people: People
fun c() {

    people = People()
    people.name = "zhangshan"

    string = "aaaa"
    MyLog.log(people.name!!)

    val child: People by lazy{
        People()
    }
    

}

3.如何避免参数加"!!"可为空符号

kotlin的变量如果不是懒加载修饰,那么你在申请的时候,就要给出变量值,即默认值

常见写法:

var name:String?=null,表示当前对象是null

var title:String="title",表示已默认值、

但是如果你申请为null,那么你在赋值的时候,这个参数就要在多个地方加上"!!",否则会报错。这种写法很烦人。

如何避免?

使用懒加载机制,通过上面两个懒加载,可以避免这种问题的参数。否则你在任何地方进程参数传递都会被判为null。

4.类的扩展函数

kotlin支持在现有的类中,动态扩展函数,格式就是类对象+"."+函数名,可以支持参数和返回值,扩展的函数和正常函数一样使用

以下是对String扩展一个两个方法,分别是log()和size()


fun String.log(log:String) {

    MyLog.log(log)
}

fun String.size(): Int {

    return toString().length
}

5.扩展属性、扩展变量

kotlin支持动态新增变量,格式就类名+"."+变量名+":"+类型

这种写法可以无限的扩展你需要的变量,扩展变量一样,只能在最外围,不能在方法体中定义,

扩展属性需要额外重写get()方法,且不能申请直接赋值

val String.defaulename: String
    get() {
        return "defaulename"
    }


fun e() {
    val name=""
    MyLog.log(name.defaulename)
}

这里需要重写get(),给出默认值。

6.apply与let与also

apply与alse是链式设置,返回的是当前对象,let返回值是unit,

  • apply、also,闭包的返回值都是this,前者apply接受的闭包类型调用者的扩展函数,后者接受的闭包类型为 入参为调用者类型的函数;
  • also、apply,非常适合对同一个对象连续操作的链式调用;
  • run、let,闭包的返回值为最后一行非赋值代码,前者run接受的闭包类型调用者的扩展函数,后者接受的闭包类型为 入参为调用者类型的函数;
  • run、let,非常适合上一个操作返回值作用于下一个操作的调用;

我们经常会使用如下写法:

if(people!=null){

peope.name="zhangshan"

}

apply写法:

people?.apply{it->

it.name="zhangsnan"

}

7.类的初始化模块init

kotlin中没有static{}模块,是通过init{}替代了static。如果需要提前初始化的可以放在init中,常见的so库加载可以放到init中

class Example {
    init {

        //init module
    }
}

8 .接口多继承,方法名冲突

 如果接口中有两个相同的方法名,在java中是需要修改一个的。在kotlin中就可以避免这种。只要在继承方法中,指向各个类的即可。


class TestInterface : IntFacA, IntFacB {

    override fun log() {
        super<IntFacA>.log()
        super<IntFacB>.log()
    }

    override fun info() {

    }
}

9.internal 介绍

internal 属于修饰,和provite、public、protect一样

  • private 意味着只在这个类内部(包含其所有成员)可见;
  • protected—— 和 private一样 + 在子类中可见。
  • internal —— 能见到类声明的 本模块内 的任何客户端都可见其 internal 成员;
  • public —— 能见到类声明的任何客户端都可见其 public 成员。

10.数据类(data)

将类对象修饰成data,需要的变量直接在构造器中申明,不需要方法体

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

11.密封类(sealed)

密封类用来表示受限的类继承结构:当一个值为有限几种的类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。

要声明一个密封类,需要在类名前面添加 sealed 修饰符。虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明

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

一个密封类是自身抽象的,它不能直接实例化并可以有抽象(abstract)成员。

密封类不允许有非-private 构造函数(其构造函数默认为 private)。

fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}

类似接口继承,通过泛型来判断

12.泛型,通配符类型参数:in 和 out

在开发过程中,泛型的利用,可以很好的解决代码臃肿问题,也能很好的进行解耦。在kotlin中,泛型的使用有普通的,也有通配符限定

1.正常泛型

interface Factory<T> {


    fun getEntity(): T
}

class Room : Factory<String> {

    override fun getEntity(): String {
        return "String info"
    }
}

这种是我们最常见的泛型,通过泛型,可以很好的获取不同的对象类型

2通配符 out和in

java中也有通配符,? extend E,? extends E 表示此方法接受 E 或者 E 的 一些子类型对象的集合,而不仅仅是 E 自身。简而言之,带 extends 限定(上界)的通配符类型使得类型是协变的。

还有一种,? super String,表示只接收String类型。

在 kotlin 语言中,out 表示协变,in 表示逆变;

kotlin 中的 “out T” 等同于 Java 的 “?extends T”

kotlin 中的 “in T” 等同于 Java 的 “?super T”

也就是上线和下线。

13.嵌套类

内部类可以直接使用,就是通过最外层类引用即可,也可以单独使用

class Student {

    lateinit var name: String

    class Classmate {

        lateinit var name: String

    }
}


fun main() {

    var mate=Student.Classmate()
    mate.name="zhangsan"
    MyLog.log(mate.name)
}

14.内部类:inner 

标记为 inner 的嵌套类能够访问其外部类的成员。内部类会带有一个对外部类的对象的引用。

class Student {

    lateinit var name: String

   inner class Classmate {

        lateinit var name: String

    }
}


fun main() {

    var mate=Student().Classmate()
    mate.name="zhangsan"
    MyLog.log(mate.name)
}

15.匿名内部类:

使用对象表达式创建匿名内部类实例:

window.addMouseListener(object : MouseAdapter() {

    override fun mouseClicked(e: MouseEvent) { …… }

    override fun mouseEntered(e: MouseEvent) { …… }
})

接口的监听:

1.lambda表达式

2.object匿名内部类

   view.setOnClickListener(object :View.OnClickListener{
        override fun onClick(v: View?) {

        }
    })

16.枚举类

枚举类的最基本的用法是实现类型安全的枚举,kotlin枚举有一下几种

1.支持匿名类

2.支持参数扩展

enum class MyEmu {
    ONE{
         var size="123"
       },TWO,THREE
}

enum class MyEmu1(tag:Int) {
    ONE(1),TWO(2),THREE(3)
}

如果你想弄那么复杂,就当普通枚举使用

3、枚举类型新增接口

enum class MyEmu :MyInfo{
    ONE{
        override fun log() {
            TODO("Not yet implemented")
        }
    },TWO{
        override fun log() {
            TODO("Not yet implemented")
        }
    }
}
interface MyInfo {

    fun log();
}

如果枚举类型实现了接口,那么枚举中的任何元素的都变成了匿名内部类,都要实现接口的方法

17.open超类

如果一个类无法被继承,就可以将该类通过open修饰,如果类中的变量无法使用,也可以通过open修饰。

open class MyNAME {
    open var name = ""
}

18.静态类:object 

静态类,是通过object修饰,该类里面的所有变量和方法都是静态。且不在需要class修饰

object staticName {
    var name = ""

    fun log() {
        
    }
}

19.伴生对象

伴随对象是在普通类中进行扩展一个对象出来的,该伴生对象的成员可通过只使用类名作为限定符来调用

class BanshengObject {

    companion object {
        var name = ""

        fun log() {

        }
    }
}


fun main() {

    BanshengObject.log()
    BanshengObject.name
}

请注意,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员

如果使用 @JvmStatic 注解,你可以将伴生对象的成员生成为真正的静态方法和字段

20.类型别名:typealias

        类型别名为现有类型提供替代名称。 如果类型名称太长,你可以另外引入较短的名称,并使用新的名称替代原类型名。

class Alias {

    inner class MyStudent(name:String)
}

typealias Alias_MyStudent = Alias.MyStudent

typealias SetString=Set<String>
typealias MyHandler = (Int, String, Any)->Void

fun main() {

    var m:MyHandler

   

}

这个别名使用在kotlin有很多,kotlin对java的api进行封装,就是通过别名完成。也可以支持方法的别名

21.内联类

内联类必须含有唯一的一个属性在主构造函数中初始化。在运行时,将使用这个唯一属性来表示内联类的实例

inline class InlinerBean(val title:String) {

    val length: Int
        get() = title.length
}

fun main() {
    val  bean=InlinerBean("this is inline class")
    
}

// 不存在 'InlinerBean' 类的真实实例对象
// 在运行时,'InlinerBean' 仅仅包含 'String' 

 val  bean=InlinerBean("this is inline class")

同时,内联是为了解决额外的堆内存分配问题。

内联类支持普通类中的一些功能。特别是,内联类可以声明属性与函数

内联函数,有且仅有一个构造参数

内联函数也可以当普通函数使用,可以实现接口

22.委托:by

委托模式已经证明是实现继承的一个很好的替代方式, 而 Kotlin 可以零样板代码地原生支持它

interface Base {
    val message: String
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override val message = "BaseImpl: x = $x"
    override fun print() { println(message) }
}

class Derived(b: Base) : Base by b {
    // 在 b 的 `print` 实现中不会访问到这个属性
    override val message = "Message of Derived"
}

fun main() {
    val b = BaseImpl(10)
    val derived = Derived(b)
    derived.print()
    println(derived.message)
}

注意,以这种方式重写的成员不会在委托对象的成员中调用 ,委托对象的成员只能访问其自身对接口成员实现。

23.委托属性

有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够为大家把他们只实现一次并放入一个库会更好。例如包括:

  • 延迟属性(lazy properties): 其值只在首次访问时计算;
  • 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
  • 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。

为了涵盖这些(以及其他)情况,Kotlin 支持 委托属性:

class Example {
    var p: String by Delegate()
}

语法是: val/var <属性名>: <类型> by <表达式>。在 by 后面的表达式是该 委托, 因为属性对应的 get()(与 set())会被委托给它的 getValue() 与 setValue() 方法。 属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(与 setValue()——对于 var 属性)

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }
 
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}

分析:

by是ReadOnlyProperty的连接,

public fun interface ReadOnlyProperty<in T, out V> {
    /**
     * Returns the value of the property for the given object.
     * @param thisRef the object for which the value is requested.
     * @param property the metadata for the property.
     * @return the property value.
     */
    public operator fun getValue(thisRef: T, property: KProperty<*>): V
}

所有我们在属性委托时,需要实现 public operator fun getValue(thisRef: T, property: KProperty<*>): V

所以定义属性委托必须要实现接口ReadOnlyProperty

实战:

在jetpack的datastore中,也提供了属性委托。

    val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
        "my_datastore",
        produceMigrations = { it ->
            listOf(SharedPreferencesMigration(it, "sp_test"))
        })

24.Lambda 表达式

lambda 表达式与匿名函数是“函数字面值”,即未声明的函数, 但立即做为表达式传递

max(strings, { a, b -> a.length < b.length })

函数 max 是一个高阶函数,它接受一个函数作为第二个参数。 其第二个参数是一个表达式,它本身是一个函数,如下:

fun compare(a: String, b: String): Boolean = a.length < b.length

Lambda 表达式语法

Lambda 表达式的完整语法形式如下:

val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }

lambda 表达式总是括在花括号中, 完整语法形式的参数声明放在花括号内,并有可选的类型标注, 函数体跟在一个 -> 符号之后

val sum = { x: Int, y: Int -> x + y }
    val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
    sum(2,3)

    val add = { x: Int, y: Int -> x + y }
    add(2,3)

从 lambda 表达式中返回一个值

ints.filter {
    val shouldFilter = it > 0 
    shouldFilter
}

ints.filter {
    val shouldFilter = it > 0 
    return@filter shouldFilter
}

25.构造集合

在开发过程中,发现使用集合都需要指定泛型,还有一种可以指定一个默认值,后面所有的集合都是这种

val numbersSet = setOf("one", "two", "three", "four")
val emptySet = mutableSetOf<String>()

26.空集合

还有用于创建没有任何元素的集合的函数:emptyList()、emptySet() 与 emptyMap()。 创建空集合时,应指定集合将包含的元素类型。

27.具体类型构造函数

要创建具体类型的集合,例如 ArrayList 或 LinkedList,可以使用这些类型的构造函数。 类似的构造函数对于 Set 与 Map 的各实现中均有提供

val linkedList = LinkedList<String>(listOf("one", "two", "three"))
val presizedSet = HashSet<Int>(32)

28.集合加减法

我们正常使用逻辑都是对于变量运算,但是在kotlin中的集合也可以使用

 val plusList = numbers + "five"
    val minusList = numbers - listOf("three", "four")
    val minusList2 = numbers - "three"
    println(plusList)
    println(minusList)
    println(minusList2)

29.集合数据分组

Kotlin 标准库提供用于对集合元素进行分组的扩展函数。 基本函数 groupBy() 使用一个 lambda 函数并返回一个 Map。 在此 Map 中,每个键都是 lambda 结果,而对应的值是返回此结果的元素 List。 例如,可以使用此函数将 String 列表按首字母分组。 


    val numbers = listOf("one", "two", "three", "four", "five")

    println(numbers.groupBy { it.first().toUpperCase() })

30.协程

Kotlin 是一门仅在标准库中提供最基本底层 API 以便各种其他库能够利用协程的语言。与许多其他具有类似功能的语言不同,async 与 await 在 Kotlin 中并不是关键字,甚至都不是标准库的一部分。此外,Kotlin 的 挂起函数 概念为异步操作提供了比 future 与 promise 更安全、更不易出错的抽象。

kotlinx.coroutines 是由 JetBrains 开发的功能丰富的协程库。它包含本指南中涵盖的很多启用高级协程的原语,包括 launch、 async 等等。


 

在kotlin中,suspend修饰的方法叫着协程。目前针对协程有一下几种

1.同步:runBlocking:

2.异步:kotlinx-coroutines-core库

 代码库:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"

class MySuspendMain {
    suspend fun info() {

    }
}


fun main() {
    GlobalScope.launch { // 在后台启动一个新的协程并继续
        delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
        println("World!") // 在延迟后打印输出
    }
    println("Hello,") // 协程已在等待时主线程还在继续
    Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活

    runBlocking {
        val mains=MySuspendMain()
        mains.info()

    }
    

}

由于kotlinx-coroutines-core库很大,我会出一篇详细的文章讲解。

Android kotlin实战之协程suspend详解与使用_蜗牛、Z的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/qq36246172/article/details/129241429
今日推荐