美文网首页
kotlin 从入门到精通-1

kotlin 从入门到精通-1

作者: 南小夕 | 来源:发表于2022-02-17 22:11 被阅读0次

    一、Kotlin概述

    1. 一种在java虚拟机上运行的静态类型编程语言;
    2. 可以和java代码相互运作;
    3. 容易在Android项目中替代java或者同java一起使用;

    二、Kotlin的特点

    1. 简洁使用;
    2. 安全;
    3. 互操作性;
    4. 工具友好;

    三、Kotlin构建流程

    上面图上时Kotlin和java的构建流程,Java的构建流程这里就不说了,看一下Kotlin的构建流程,首先Kotlin文件会被kotlin编译器编译成Java的字节码文件,字节码文件会被jar工具打包成jar包,最终会被各平台的打包工具输出成我们的应用程序,在最后一步还需要Kotlin的运行时来辅助Kotlin的运行。

    四、Kotlin必备基础

    1. 基本类型

    Kotlin的基本数值类型包括Byte、Short、Int、Long、Float、Double等。不同于Java的是,字符不属于数值类型,是一个独立的数据类型

    对于整数,存在四种具有不同大小和值范围的类型
    类型:Byte;位宽:8;最小值:-128;最大值:127.
    类型:Short;位宽:16;最小值:-32768;最大值:32767.
    类型:Int;位宽:32;最小值:-2,147,483,648(-231),最大值:2,147,483,647(231-1).
    类型:Long;位宽:64;最小值:-9,223,372,036,854,775,808(-263);最大值:9,223,372,036,854,775,807(263-1).

    对于浮点数,Kotlin提供了Float和Double类型
    类型:Float;位宽:32.
    类型:Double;位宽:64.

    2. 数组

    数组在Kotlin中使用Array类来表示,它定义了get和set方法(按照运算符重载约定这会转变为[])以及size属性,以及一些其他有用的成员方法:

    public class Array<T> {
    
        public inline constructor(size: Int, init: (Int) -> T)
    
        public operator fun get(index: Int): T
    
        public operator fun set(index: Int, value: T): Unit
    
        public val size: Int
    
        public operator fun iterator(): Iterator<T>
    }
    

    数组的创建方法:

        /**
         * 数组
         */
        fun arrayType() {
            // arrayOf
            val array: Array<Int> = arrayOf(1, 2, 3)
    
            // arrayOfNulls
            val array1: Array<Int?> = arrayOfNulls<Int>(3)
            array1[0] = 4
            array1[1] = 5
            array1[2] = 6
    
            // Array(5)的构造函数
            val array2: Array<String> = Array(5) { I ->
                (i * i).toString()
            }
    
    
            // intArrayOf(), doubleArrayOf()
            val x: IntArray = intArrayOf(1, 2, 3)
            println("x[0] + x[1] = ${x[0] + x[1]}")
    
    
            // 大小为5,值为【0,0,0,0,0】的整型数组
            val array3 = IntArray(5)
    
            // 例如:用常量初始化数组中的值
            // 大小为5,值为【42,42,42,42,42】的整型数组
            val array4 = IntArray(5) { 42 }
    
            // 例如:使用lambda表达式初始化数组中的值
            // 大小为5、值为【0,1,2,3,4】的整形数组(值初始化为其索引值)
            val array5 = IntArray(5) { it * 1 }
            println(array5[4])
        }
    

    数组的遍历方式:

     /****遍历数组的常用5中方式****/
            // 数组遍历
            for (item in array) {
                println(item)
            }
    
            // 带索引遍历数组
            for (i in array.indices) {
                println("$i -> ${array[i]}")
            }
    
            // 遍历元素(带索引)
            for ((index, item) in array.withIndex()) {
                println("$index -> $item")
            }
            
            // forEach遍历数组
            array.forEach {
                println(it)
            }
    
            // forEach增强版
            array.forEachIndexed { index, item ->
                println("$index -> $item")
            }
    

    3. 集合

    kotlin标准库提供了一整套用于管理集合的工具,集合是可变数量(可能为零)的一组条目,各种集合对于解决问题都具有重要意义,并且经常用到。

    • List是一个有序集合,可通过索引(反映元素位置的整数)访问元素。元素可以在list中出现多次。列表的一个示例是一句话:有一组字、这些字的顺序很重要并且字可以重复。
    • Set是唯一元素的集合。它反映了集合(set)的数学抽象:一组无重复的对象。一般来说set中元素的顺序并不重要。例如,字母表是字母的集合(set)。
    • Map(或者字典)是一组键值对。键是唯一的,每个键都刚好映射到一个值,值可以重复。

    集合的可变形与不可变性
    在Kotlin中存在两种意义上的集合,一种是可以修改的,一种是不可修改的。

    • 不可变集合
    val stringList: List<String> = listOf("one", "two", "one")
    println(stringList)
    
    val stringSet: Set<String> = setOf("one", "two", "one")
    println(stringSet)
    
    • 可变集合
    val numbers: MutableList<Int> = mutableListOf(1, 2, 3, 4)
    numbers.add(5)
    numbers.removeAt(1)
    numbers[0] = 0
    println(numbers)
    

    不难发现,每个不可变集合都有对应的可变集合,也就是以mutable为前缀的集合。

        /**
         * 集合
         */
        fun collectionType() {
            // 不可变集合
            val stringList: List<String> = listOf("one", "two", "one")
            println(stringList)
    
            val stringSet: Set<String> = setOf("one", "two", "one")
            println(stringSet)
    
            // 可变集合
            val numbers: MutableList<Int> = mutableListOf(1, 2, 3, 4)
            numbers.add(5)
            numbers.removeAt(1)
            numbers[0] = 0
            println(numbers)
    
            val hello: MutableSet<String> = mutableSetOf("H", "e", "l", "l", "o") // 自动过滤重复元素
            hello.remove("o")
            println(hello)
    
            // 集合的加减操作
            hello += setOf("w", "o", "r", "l", "d")
            println(hello)
    
            /**Map<K,V>不是Collection接口的继承者,但是它也是Kotlin的一种集合类型**/
            val numberMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 4, "key5" to 5)
            println("All keys:${numberMap.keys}")
            println("All values:${numberMap.values}")
    
            if ("key2" in numberMap) println("value by key key2:${numberMap["key2"]}")
            if (1 in numberMap.values) println("1 is in the map")
            if (numberMap.containsValue(1)) println("1 is in the map")
        }
    

    集合排序

        /**
         * 集合排序
         */
        fun collectionSort() {
            val number3 = mutableListOf(1, 2, 3, 4)
            // 随机排序
            number3.shuffle()
            println(number3)
    
            number3.sort() // 从小到大
            number3.sortDescending() // 从大到小
            println(number3)
    
            // 条件排序
            data class Lauguage(var name: String, var score: Int)
    
            val lauguageList: MutableList<Lauguage> = mutableListOf()
            lauguageList.add(Lauguage("Java", 80))
            lauguageList.add(Lauguage("Kotlin", 90))
            lauguageList.add(Lauguage("Dart", 99))
            lauguageList.add(Lauguage("C", 90))
            // 使用sortBy进行排序,适合单条件排序
            lauguageList.sortBy { it.score }
            println(lauguageList)
    
            // 使用sortWith进行排序,适合多条件排序
            lauguageList.sortWith(compareBy({
                it.score
            }, { it.name }))
        }
    

    原理探索

    • 两个具有相同键值对,但顺序不同的map相等吗?
      无论键值对的顺序如何,包含相同键值对的两个map是相等的。因为源码里只比较了key-value,跟顺序无关。
    val anotherMap = mapOf("key2" to 2, "key1" to 1, "key3" to 3, "key4" to 4, "key5" to 5)
    println("anotherMap == numberMap:${anotherMap == numberMap}")
    anotherMap.equals(numberMap)
    
    • 两个具有相同元素,但单顺序不同的list相等吗?
      不相等。源码中会依次比较相同index的value是否相等。
    val stringList: List<String> = listOf("one", "two", "three")
    val anotherList: List<String> = listOf("three", "two", "one")
    println("stringList == anotherList:${stringList == anotherList}")
    stringList.equals(anotherList)
    

    4. 方法

    (1)方法声明
    fun functionLearn(days: Int): Boolean {
        return days > 100
     }
    

    方法可以直接定义在文件中

    package com.example.myapplication
    
    fun functionLearn(days: Int): Boolean {
        return days > 100
    }
    

    成员方法定义及调用

    fun main() {
        Person().test1()
    }
    
    class Person {
        /**
         * 成员方法
         */
        fun test1() {
            println("成员方法")
        }
    }
    

    静态方法(也叫类方法)的定义:在kotlin中没有static关键字,可以使用伴生对象来实现类方法。

    fun main() {
        Person.test2()
    }
    
    class Person {
        companion object {
            fun test2() {
                println("companion object 实现类方法")
            }
        }
    }
    

    (2)工具类的实现:
    Object类名,就可以定义一个静态类,它里面所有的方法都是静态方法。

    package com.example.myapplication
    
    object NumUtil {
        fun double(num: Int): Int {
            return num * 2
        }
    }
    
    fun main() {
        NumUtil.double(2)
    }
    

    (3)单表达式方法

    /**
     * 单表达式方法,当方法返回单个表达式时,可以省略花括号并且在 = 符号之后指定代码体即可
     */
    fun double(x: Int): Int = x * 2
    

    (4)参数默认值
    第一个可以代表后两个,减少方法的重载。

    /**
     * 默认值,方法参数可以有默认值,当省略相应的参数时使用默认值,与其java相比,这可以减少重载数量
     */
    fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) {
    
    }
    
    fun read(b: Array<Byte>) {
    
    }
    
    fun read(b: Array<Byte>, off: Int = 0) {
    
    }
    

    (5)可变数量的参数

    /**
     * 可变数量的参数
     */
    fun append(vararg str: Char): String {
        val result = StringBuffer()
        for (char in str) {
            result.append(char)
        }
        return result.toString()
    }
    

    (6)局部方法

    /**
     * 局部方法
     */
    fun magic(): Int {
        fun foo(v: Int): Int {
            return v * v
        }
    
        val v1 = (0..100).random()
        return foo(v1)
    }
    

    五、Lambda表达式

           view.setOnClickListener(new View . OnClickListener (){
                @Override
                public void onClick(View v) {
                    Toast.makeText(v.getContext(); "Lambda简洁之道", Toast.LENGTH_LONG);
                }
            });
    
            //VS
    
            view.setOnClickListener { v -> Toast.makeText(v.getContext(); "Lambda简洁之道", Toast.LENGTH_LONG) }
    

    Lambda表达式特点:

    • 是匿名方法
    • 二是可传递

    Lambda语法:

    • 无参数的情况
    val/var 变量名 = { 操作的代码 }
    
    • 有参数的情况
     val/var 变量名 : (参数的类型, 参数类型, ...) -> 返回值类型 = {参数1, 参数2, ... -> 操作参数的代码}
    // 等价于
    // 此种写法:即表达式的返回值类型会根据操作的代码自推导出来。
    val/var 变量名 = {参数1: 类型, 参数2: 类型, ... -> 操作参数的代码}
    
        /**
         * 无参数情况
         */
        fun test() {
            println("无参数")
        }
    
        // lambda代码
        val test1 = { println("无参数") }
    
        /**
         * 有参数情况
         */
        // 源代码
        fun test2(a: Int, b: Int): Int {
            return a + b
        }
    
        // lambda代码
        val test3: (Int, Int) -> Int = { a, b -> a + b }
        //或者
        val test4 = { a: Int, b: Int -> a + b }
    

    it

    (1)认识it

    • it并不是kotlin中的一个关键字(保留字)
    • it是在当一个高阶方法中Lambda表达式的参数只有一个的时候可以使用it来使用此参数
    • it可表示为单个参数的隐式名称,是kotlin语言约定的
      (2)举例:单个参数的隐式名称
    // 这里举例一个语言自带的一个高阶方法filter,此方法的作用是过滤掉不满足条件的值
    val arr = arrayOf(1, 3, 5, 7, 9)
    // 过滤掉数组中元素小于5的元素,取其第一个打印。这里的it就表示每一个元素。
    println(arr.filter { it < 5 }.component1())
    
    testClosure(1)(2) {
        println(it)
    }
    

    如何使用下划线

    在使用Lambda表达式的时候,可以用下划线(_)表示未使用的参数,表示不处理这个参数。
    在遍历一个Map集合的时候,这非常有用

    val map = mapOf("key1" to "value1", "key2" to "value2", "key3" to "value3")
    map.forEach { (key, value) ->
        println("$key \t $value")
    }
    
    // 不需要key的时候
    map.forEach { (_, value) -> println(value) }
    

    六、Kotlin方法进阶

    1. 高阶方法(函数)

    函数作为参数
    举例:实现一个能够对集合元素进行求和的高阶函数,并且每遍历一个集合元素要有回调

        /**
         * 高阶函数--函数作为参数
         */
        fun List<Int>.sum(callback: (Int) -> Unit): Int {
            var result = 0
            for (v in this) {
                result += v
                callback(v)
            }
            return result
        }
    // 调用
    val list = listOf(1, 2, 3)
    val result = list.sum { println("it:${it}") }
    println("${result}")
    

    函数作为返回值
    举例:实现一个能够对集合元素进行求和的高阶函数,并且返回一个声明为(scale: Int)-> Float的函数

        /**
         * 函数作为返回值
         */
        fun List<String>.toIntSum(): (scale: Int) -> Float {
            println("第一层函数")
            return fun(scale): Float {
                var result = 0f
                for (v in this) {
                    result += v.toInt() * scale
                }
                return result
            }
        }
    // 调用
    val listString = listOf("1", "2", "3", "4")
    val result2 = listString.toIntSum()(2)
    println("计算结果:${result2}")
    

    2. 闭包(Closure)

    概念

    • 闭包可以理解为能够读取其他方法内部变量的方法;
    • 闭包是将方法内部和方法外部连接起来的桥梁;

    特性

    • 方法可以作为另一个方法的返回值或参数,还可以作为一个变量的值;
    • 方法可以嵌套定义,即在一个方法内部可以定义另一个方法;

    好处

    • 加强模块化
    • 抽象
    • 灵活
    • 简化代码

    举例
    实现一个接受一个testClosure方法,该方法要接受一个Int类型的v1参数,同时能够返回一个声明为(v2: Int, (Int) -> Unit)的函数,并且这个函数能够计算v1与v2的和。

    fun testClosure(v1: Int): (v2: Int, (Int) -> Unit) -> Unit {
        return fun(v2: Int, printer: (Int) -> Unit) {
            printer(v1 + v2)
        }
    }
    // 调用
    testClosure(1)(2) {
        println(it)
    }
    

    3. 方法的解构声明

    在Kotlin中支持对一个对象,将它里面的字段给解构出来。

    data class Result(val message: String, val code: Int)
    
    fun test11() {
        var result = Result("message", 0)
        // 解构
        val (message, code) = result
        println("message:${message} code:${code}")
    }
    

    4. 匿名方法

    val fun1 = fun(x: Int, y: Int): Int = x + y
    

    5. kotlin方法字面值

    fun literal() {
            // 定义了一个变量tmp,而该变量的类型就是(Int)-> Boolean
            var temp: ((Int) -> Boolean)? = null
            // { num -> (num > 10) } 就是方法字面值
            temp = { num -> (num > 10) }
            println("temp(11):${temp(11)}")
        }
    

    七、构造方法与继承

    1. 构造方法

    主构造方法

    /**
     * 主构造方法
     */
    class KotlinClass constructor(name: String) {
    
    }
    

    主构造方法constructer()可以省略;

    次构造方法

    /**
     * 主构造方法
     */
    class KotlinClass constructor(name: String) {
        // 次构造方法
        constructor(view: View, name: String) : this(name) {
            println("name:$name")
        }
    
        constructor(view: View, name: String, index: Int) : this(name) {
            println("name:$name,index:$index")
        }
    }
    

    次构造方法可以有多个,但必须调用主构造方法;

    2. 继承与覆盖

    父类必须用open修饰,需要被覆盖的方法也需要open修饰,需要被覆盖的属性也需要open修饰

    open class Animal(age: Int) {
        init {
            println(age)
        }
    
        open val foot: Int = 0
        open fun eat() {
    
        }
    }
    
    class Dog : Animal {
        constructor(age: Int) : super(age)
    
        override val foot = 4
        override fun eat() {
            super.eat()
        }
    }
    

    3. 属性

    Getters与Setters
    声明一个属性的完整语法是

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

    其初始器(initializer)、getter和setter都是可选的。如果属性类型可以从初始器(或者从其getter返回值)中推断出来,也可以省略
    例1:

    val simple: Int? // 类型Int、默认getter、必须在构造方法中初始化
    

    例2:
    我们可以为属性定义自定义的访问器。如果我们定义了一个自定义的getter,那么每次访问该属性时都会调用它。

    val isClose: Boolean
            get() = Calendar.getInstance().get(Calendar.HOUR_OF_DAY) > 11
    

    如果我们定义了一个自定义的setter,那么每次给属性赋值时都会调用它。一个自定义的setter,如下所示:

     var score: Float = 0.0f
            get() = if (field < 0.2f) 0.2f else field * 1.5f
            set(value) {
                println(value)
            } 
    

    属性延迟初始化

    lateinit var shop: Shop2
        fun setup() {
            shop = Shop2()
        }
    
        fun test() {
            // ::表示创建成员引用或类引用
            if (::shop.isInitiaized) println(shop.address)
        }
    

    八、Kotlin抽象类与接口

    抽象类

    /**
     * 抽象方法
     */
    abstract class Printer {
        abstract fun print()
    }
    
    class FilePrinter : Printer() {
        override fun print() {
    
        }
    }
    

    接口

    /**
     * 接口
     */
    interface Study {
        val time: Int//抽象的
        fun discuss()
        fun learnCourses() {
            println("Android 架构师")
        }
    }
    
    class StudyAS(override val time: Int) : Study {
        override fun discuss() {
    
        }
    }
    

    如果被继承的两个接口中有相同名字的方法,子类在调用父类方法时需要指定要调用哪个接口的方法。

    interface A {
        fun foo() {
            println("A")
        }
    }
    
    interface B {
        fun foo() {
            println("B")
        }
    }
    
    class D : A, B {
        override fun foo() {
            super<A>.foo() // 需要指定接口,解决冲突
        }
    }
    

    数据类
    必须要有至少一个参数,并且不能被定义成open或者抽象的,不能被继承。

    /**
     * 数据类,可以有自己的类体,包括属性和方法
     */
    data class Address(val name: String, val number: Int) {
        var city: String = ""
        fun print() {
            println(city)
        }
    }
    

    对象表达式与对象声明

    open class Address2(name: String) {
        open fun print() {
    
        }
    }
    
    class Shop2 {
        var address: Address2? = null
        fun addAddress(address2: Address2) {
            this.address = address2
        }
    }
    
    fun test3() {
        // 如果超类型有一个构造方法,则必须传递适当的构造方法参数给它
        Shop2().addAddress(object : Address2("Android") {
            override fun print() {
                super.print()
            }
        })
    }
    
    fun foo() {
        val adHoc = object {
            var x: Int = 0
            var y: Int = 0
        }
        println(adHoc.x + adHoc.y)
    }
    
    /**
     * 对象的声明
     */
    object DataUtil {
        fun <T> isEmpty(list: ArrayList<T>): Boolean {
            return list?.isEmpty()
        }
    }
    

    伴生对象

    class Student(val name: String) {
        companion object {
            val student = Student("Android")
            fun study() {
                println("Android 架构师")
            }
        }
    }
    
    fun testStudent() {
        println(Student.student)
        Student.study()
    }
    

    九、深入理解Kotlin泛型

    1. Kotlin泛型的好处
    • 架构开发的一把利器;
    • 使我们的代码或开发出来的框架更加的通用;
    • 增加程序的健壮性,避开运行时可能引起的ClassCastException;
    • 能够帮助你研究和理解别的框架;
    • 自己造轮子需要,能用泛型解决问题;
    1. 泛型接口
    /**
     * 泛型接口
     */
    //java
    interface Drinks<T> {
        T taste();
        void price(T t);
    }
    
    fun main() {
        println(Coke().taste().price)
    }
    
    //kotlin
    interface Drinks<T> {
        fun taste(): T
        fun price(t: T)
    }
    
    class Sweet {
        val price = 5
    }
    
    class Coke : Drinks<Sweet> {
        override fun taste(): Sweet {
            println("Sweet")
            return Sweet()
        }
    
        override fun price(t: Sweet) {
            println("Coke price:${t.price}")
        }
    }
    
    1. 泛型方法
    /**
     * 泛型方法
     */
    fun <T> fromJson(json: String, tClass: Class<T>): T? {
        // 获取t的实例
        val t: T? = tClass.newInstance()
        return t
    }
    
    1. 泛型约束
      java中可以通过有界类型参数来限制参数类型的边界,Kotlin中泛型约束也可以限制参数类型的上界:
    //java
    public static <T extends Comparable<? super T>> void sort(List<T> list){}
    
    //kotlin
    fun <T : Comparable<T>?> sort(list: List<T>) {}
    
    fun test() {
        sort(listOf(1, 2, 3)) //OK,Int是Comparable<Int>的子类型
    //    sort(listOf(Blue())) //错误:Blue不是Comparable<Blue>的子类型
    }
    

    对于多个上界的情况

    fun test1() {
        val listString = listOf("A", "B", "C")
        val list = test(listString, "B")
        println(list)
    }
    
    // 多个上界的情况
    fun <T> test(list: List<T>, threshold: T): List<T>
            where T : CharSequence,
                  T : Comparable<T> {
        return list.filter { it > threshold }.map { it }
    }
    
    1. 泛型中的out与in
      在kotlin中out代表协变,in代表逆变,为了加深理解我们可以将kotlin的协变看成java的上界通配符,将逆变看成java的下界通配符:
    //kotlin使用处协变
    fun sumOfList(list: List<out Number>)
    
    //java上界通配符
    fun sumOfList(List<? extends Number> list)
    
    //kotlin使用处逆变
    fun addNumbers(list: List<in Int>)
    
    //java下界通配符
    fun addNumbers(List<? super Integer> list)
    

    总的来说,Kotlin泛型更加简洁安全,但是和java一样都是有类型擦除的,都是属于编译时泛型。

    十、深入理解Kotlin注解

    1. Kotlin注解的好处
    • 架构开发的一把利器;
    • 使逻辑实现更加简洁,让代码更加清晰易懂;
    • 能够帮助你研究和理解别的框架;
    • 自己造轮子需要,能用注解解决问题;
    1. 注解的声明
    //和一般的声明很类似,只是在class前面加上了annotation修饰符
    annotation class ApiDoc(val value: String)
    
    @ApiDoc("修饰类")
    class Box {
        @ApiDoc("修饰字段")
        val size = 8
    
        @ApiDoc("修饰方法")
        fun test() {
            
        }
    }
    
    1. Kotlin中的元注解
      和java一样在kotlin中一个kotlin注解类自己本身也可以被注解,可以给注解类加注解,我们把这种注解称为元注解。
      Kotlin中的元注解类定义于Kotlin.annotaion包中,主要有:
    • @Target:定义注解能够应用于哪些目标对象;
    • @Retention:注解的保留期;
    • @Repeatable:标记的注解可以多次应用于相同的声明或类型;
    • @MustBeDocumented:修饰的注解将被文档工具提取到API文档中;

    4种元注解,相比Java中5种元注解少了@Inherited,在这里四种元注解种最常用的是前两种:
    @Target
    @Target顾名思义就是目标对象,也就是我们定义的注解能够应用于那些目标对象,可以同时指定多个作用的目标对象。
    @Target的原型

    @Target(AnnotationTarget.ANNOTATION_CLASS)// 可以给标签自己贴标签
    @MustBeDocumented
    public annotation class Target(vararg val allowedTargets: AnnotationTarget)
    

    从@Target的原型中我们可以看出,它接受一个vararg可变数量的参数,所以可以同时指定多个作用的目标对象,并且参数类型限定为AnnotationTarget。
    在@Target注解中可以同时指定一个或多个目标对象,看一下AnnotationTarget枚举类的源码:

    public enum class AnnotationTarget {
        CLASS,// 表示作用对象有类、接口、object对象表达式、注解类
        ANNOTATION_CLASS,//表示作用对象只有注解类
        TYPE_PARAMETER,//表示作用对象是泛型类型参数(暂时还不支持)
        PROPERTY,//表示作用对象是属性
        FIELD,//表示作用对象是字段,包括属性的幕后字段
        LOCAL_VARIABLE,//表示作用对象是局部变量
        VALUE_PARAMETER,//表示作用对象是函数或构造函数的参数
        CONSTRUCTOR,//表示作用对象是构造函数,主构造函数或次构造函数
        FUNCTION,//表示作用对象是函数,不包括构造函数
        PROPERTY_GETTER,//表示作用对象是属性的getter函数
        PROPERTY_SETTER,//表示作用对象是属性的setter函数
        TYPE,//表示作用对象是一个类型,比如类、接口、枚举
        EXPRESSION, //表示作用对象是一个表达式
        FILE,//表示作用对象是一个File
        @SinceKotlin("1.1")
        TYPEALIAS//表示作用对象是一个类型别名
    }
    

    一旦注解被限定了@Target那么它只能被应用于限定的目标对象上,为了验证这一说法,我们为ApiDoc限定下目标对象:

    @Target(AnnotationTarget.CLASS)
    annotation class ApiDoc(val value: String)
    
    @ApiDoc("修饰类")
    class Box {
        @ApiDoc("修饰字段")
        val size = 8
    
        @ApiDoc("修饰方法")
        fun test() {
    
        }
    }
    

    这样一来ApiDoc注解只能被应用于类上,如果将它应用在方法或字段上则会抛出异常。

    @Retention
    @Retention我们可以理解为保留期、和java一样Kotlin有三种时期:源代码时期(SOURCE)、编译时期(BINARY)、运行时期(RUNTIME)
    @Retention原型

    @Target(AnnotationTarget.ANNOTATION_CLASS)//目标对象是注解类
    public annotation class Retention(val value: AnnotationRetention = AnnotationRetention.RUNTIME)
    

    Retention接收一个AnnotationRetention类型的参数,该参数有个默认值,默认是保留在啊运行时期。
    AnnotationRetention

    @Retention元注解取值主要来源于AnnotationRetention枚举类
    public enum class AnnotationRetention {
        SOURCE,//源代码时期(SOURCE):注解不会存储在输出class字节码中
        BINARY,//编译时期(BINARY):注解会存储在class字节码中,但是对反射不可见
        RUNTIME//运行时期(RUNTIME):注解会存储在class字节码中,也会对反射可见
    }
    
    1. 注解的使用场景
    • 提供信息给编译器:编译器可以利用注解来处理一些,比如一些警告信息、错误等。
    • 编译阶段时处理:利用注解信息来生成一些代码,在kotlin生成代码非常常见,一些内置的注解为了与java API的互操作性,往往借助注解在编译阶段生成一些额外的代码。
    • 运行时处理:某些注解可以在程序运行时,通过反射机制获取注解信息来处理一些程序逻辑。
    1. 举例
      自定义注解实现API调用时的请求方法检查
    public enum class Method {
        GET,
        POST
    }
    
    @Target(AnnotationTarget.CLASS)
    @Retention(AnnotationRetention.RUNTIME)
    annotation class HttpMethod(val method: Method)
    
    interface Api {
        val name: String
        val version: String
            get() = "1.0"
    }
    
    class ApiGetArticles() : Api {
        override val name: String
            get() = "/api.articles"
    }
    
    fun fire(api: Api) {
        val annotations = api.javaClass.annotations
        val method = annotations.find { it is HttpMethod } as? HttpMethod
        println("通过注解得知该接口需要通过:${method?.method} 方法请求")
    }
    
    fun main() {
        fire(ApiGetArticles())
    }
    

    十一、Kotlin扩展技术探秘与应用

    1. kotlin扩展的好处
    • 提供架构的易用性;
    • 减少代码量,让代码更加整洁、纯粹;
    • 提高编码的效率,生产力提高;
    1. 扩展方法的原型
    2. 扩展方法的使用

    fun main() {
        val list = mutableListOf(1, 2, 3)
        list.swap(0, 2)
        println("list.swap(0,2):$list")
    }
    
    fun MutableList<Int>.swap(index1: Int, index2: Int) {
        val temp = this[index1]
        this[index1] = this[index2]
        this[index2] = temp
    }
    
    1. 泛型扩展方法
    fun main() {
        val listString = mutableListOf("A", "B", "C")
        listString.swap2(0, 2)
        println("list.swap2(0,2):$listString")
    }
    
    fun <T> MutableList<T>.swap2(index1: Int, index2: Int) {
        val temp = this[index1]
        this[index1] = this[index2]
        this[index2] = temp
    }
    
    1. 扩展属性
    // 为String添加一个lastChar属性,用于获取字符串的最后一个字符
    val String.lastChar: Char get() = this.get(this.length - 1)
    
    1. 为伴生对象添加扩展
    class Jump {
        companion object {}
    }
    
    fun Jump.Companion.print(str: String) {
        println(str)
    }
    
    1. Kotlin中常用的扩展
      在Kotlin的源码中定义了大量的扩展,比如:let、run、apply,了解并运用这些函数能帮我们提高编码效率。
      let扩展
      函数原型
    fun <T, R> T.let(f: (T) -> R): R = f(this)
    

    let 扩展函数的实际上是一个作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,那么let函数是一个不错的选择;let函数另一个作用就是可以避免写一些判断null的操作。

    /**
     * let
     */
    fun testLet(str: String?) {
        // 避免为null的操作
        str?.let {
            println(it.length)
        }
        //限制作用域
        str.let {
            val str2 = "let作用域"
            println(it + str2)
        }
    }
    

    run扩展
    函数原型

    fun <T, R> T.run(f: T.() -> R): R = f()
    

    run函数只接收一个lambda函数为参数,以闭包形式返回,返回值为最后一行的值或者指定的return的表达式,在run函数中可以直接访问实例的公有属性和方法。

    data class Room(val address: String, val price: String, val size: Float)
    
    /**
     * run
     */
    fun testRun(room: Room) {
        room.run {
            println("Room:$address,$price,$size")
        }
    }
    

    apply扩展
    函数原型:

    fun <T> T.apply(f: T.() -> Unit): T { f(); return this }
    

    apply函数的作用是:调用某对象的apply函数,在函数范围内,可以任意调用该对象的任意方法,并返回该对象。
    从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply函数的返回的是传入对象的本身。
    apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。或者动态inflate出一个XML的View的时候需要给View绑定数据也会用到,这种情景非常常见。

    /**
     * apply
     */
    fun testApply() {
        ArrayList<String>().apply {
            add("1")
            add("2")
            add("3")
        }.let {
            println(it)
        }
    }
    

    十二、Kotlin扩展案例

    使用Kotlin扩展为控件绑定监听器减少模版代码

    //为Activity添加find扩展方法,用于通过资源id获取控件
    fun <T : View> Activity.find(@IdRes id: Int): T {
        return findViewById(id)
    }
    
    //为Int添加onClick扩展方法,用于为资源id对应的控件添加onclick监听
    fun Int.onClick(activity: Activity, click: () -> Unit) {
        activity.find<View>(this).apply {
            setOnClickListener {
                click
            }
        }
    }
    
    //使用
    val textView = find<TextView>(R.id.text)
    R.id.text.onClick(this) {
        textView.text = "kotlin扩展"
    }
    

    十三、Kotlin实用技巧

    1. 使用Kotlin安卓扩展,向findViewById说拜拜
      在进行Android编码时我们避免不了的需要使用findViewById()来获取指定控件的对象,Kotlin中启用Gradle安卓扩展插件即可省去这些模版代码,
      首先在gradle中引入插件:
    apply plugin: 'kotlin-android-extensions'
    

    然后在代码中导入:

    import kotlinx.android.synthetic.main.<布局>.*
    

    若需要调用View的合成属性,同时还应该导入

    import kotlinx.android.synthetic.main.view.*
    

    最后就可以通过控件id来访问这些控件的实例了。

    1. 字符串的空判断,向TextUtils.isEmpty说拜拜
      在我们日常开发中,经常会对字符串进行空判断,相信大家对TextUtils.isEmpty一定都不陌生,它可以帮我们判断字符串是否为空而且不用担心npe问题。
      那么在Kotlin中,有一个叫:
    public inline fun CharSequence?.isNullOrEmpty(): Boolean = this == null || this.length == 0
    

    的扩展函数能帮我们省去对TextUtils.isEmpty的使用;

    //java
    if (!TextUtils.isEmpty(name)) {
        textview.setText(name);
    }
    //kotlin
    if (!name.isNullOrEmpty()) {
        textView.text = name
    }
    

    除了isNullOrEmpty扩展之外,CharSequence还有个名叫

    public inline fun CharSequence?.isNullOrBlank(): Boolean = this == null || this.isBlank()
    

    如果name都是空格,则TextUtils.isEmpty不满足使用。那isNullOrBlank可用。

    1. 使用@JvmOverloads告别繁琐的构造函数重载
      在Kotlin中@JvmOverloads注解的作用就是:在有默认参数值的方法中使用@JvmOverloads注解,则Kotlin就会暴露多个重载方法。这对我们自定义控件也很有用,
    //java
    public class CustomView extends FrameLayout {
        public CustomView(@NonNull Context context) {
            super(context);
        }
    
        public CustomView(@NonNull Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public CustomView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    }
    
    //kotlin
    class CustomKotlinView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) : FrameLayout(context, attrs, defStyleAttr) {
        
    }
    

    相关文章

      网友评论

          本文标题:kotlin 从入门到精通-1

          本文链接:https://www.haomeiwen.com/subject/qwjtlrtx.html