kotlin 从入门到精通-1

一、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) {
    
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,119评论 9 118
  • 前言 Google 在2017年 I/O 大会上宣布,Kotlin 正式成为 Android 的一级开发语言,和 ...
    sweetying阅读 1,377评论 0 5
  • 本篇参考资料《第一行代码 第三版》 2020.4月出版本篇文章只是本人看书的理解和整理的笔记,更完整的内容还在书上...
    番茄tomato阅读 562评论 2 3
  • Kotlin的优势 代码简洁高效、强大的when语法,不用写分号结尾,findViewById光荣退休,空指针安全...
    Windy_816阅读 1,274评论 1 6
  • 这篇文章会列出我认为入门需要掌握的特性,如果要想应用到项目中去的话可以先去GitHub上找一些优秀的Kotlin项...
    BigDevil_S阅读 742评论 0 1