一、Kotlin概述
- 一种在java虚拟机上运行的静态类型编程语言;
- 可以和java代码相互运作;
- 容易在Android项目中替代java或者同java一起使用;
二、Kotlin的特点
- 简洁使用;
- 安全;
- 互操作性;
- 工具友好;
三、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泛型
- Kotlin泛型的好处
- 架构开发的一把利器;
- 使我们的代码或开发出来的框架更加的通用;
- 增加程序的健壮性,避开运行时可能引起的ClassCastException;
- 能够帮助你研究和理解别的框架;
- 自己造轮子需要,能用泛型解决问题;
- 泛型接口
/**
* 泛型接口
*/
//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}")
}
}
- 泛型方法
/**
* 泛型方法
*/
fun <T> fromJson(json: String, tClass: Class<T>): T? {
// 获取t的实例
val t: T? = tClass.newInstance()
return t
}
- 泛型约束
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 }
}
- 泛型中的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注解
- Kotlin注解的好处
- 架构开发的一把利器;
- 使逻辑实现更加简洁,让代码更加清晰易懂;
- 能够帮助你研究和理解别的框架;
- 自己造轮子需要,能用注解解决问题;
- 注解的声明
//和一般的声明很类似,只是在class前面加上了annotation修饰符
annotation class ApiDoc(val value: String)
@ApiDoc("修饰类")
class Box {
@ApiDoc("修饰字段")
val size = 8
@ApiDoc("修饰方法")
fun test() {
}
}
- 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字节码中,也会对反射可见
}
- 注解的使用场景
- 提供信息给编译器:编译器可以利用注解来处理一些,比如一些警告信息、错误等。
- 编译阶段时处理:利用注解信息来生成一些代码,在kotlin生成代码非常常见,一些内置的注解为了与java API的互操作性,往往借助注解在编译阶段生成一些额外的代码。
- 运行时处理:某些注解可以在程序运行时,通过反射机制获取注解信息来处理一些程序逻辑。
- 举例
自定义注解实现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扩展技术探秘与应用
- kotlin扩展的好处
- 提供架构的易用性;
- 减少代码量,让代码更加整洁、纯粹;
- 提高编码的效率,生产力提高;
-
扩展方法的原型
扩展方法的使用
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
}
- 泛型扩展方法
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
}
- 扩展属性
// 为String添加一个lastChar属性,用于获取字符串的最后一个字符
val String.lastChar: Char get() = this.get(this.length - 1)
- 为伴生对象添加扩展
class Jump {
companion object {}
}
fun Jump.Companion.print(str: String) {
println(str)
}
- 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实用技巧
- 使用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来访问这些控件的实例了。
- 字符串的空判断,向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可用。
- 使用@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) {
}