• Kotlin


    一、了解Kotlin

    1.概述

    • 一种在Java虚拟机上运行的静态类型编程语言
    • 虽然与Java语法并不兼容,但可以和Java代码相互运作,可以使用Java现有框架
    • 容易在Android项目中替代Java或者同Java一起使用

    在2019年的Google 1/0大会上Kotlin被选为Android开发首选语言

    2.特点

    • 简洁易用,提供大量扩展使代码更加的简洁,开发的框架更加易用
    • 安全,避免了空指针异常等一些常有类的错误,让代码更加的健壮
    • 互操作性,充分利用Android JVM 浏览器和现有的库,和已有的技术,完成一个互通
    • 工具友好,可以使用任意Java的ide 或者浏览器来构建kotlin 应用

    3.构建流程

    Kotlin文件会被Kotlin 编译器编译成Java字节码文件,字节码文件会被jar工具打包成jar包,最终会被各平台的打包工具输出成我们的应用程序

    二、基础

    1.数据类型

    Byte、Short、Int、Long、Float、Double

    • 整数
      • Byte   8位
      • Short   16位
      • Int    32位
      • Long  64位
    • 浮点数
      • Float;32位
      • Double;64位

    2.数组

    数组的创建

    1. // arrayOf
    2. val array: Array<Int> = arrayOf(1, 2, 3)
    3. // arrayOfNulls
    4. val array1: Array<Int?> = arrayOfNulls<Int>(3)
    5. array1[0] = 4
    6. array1[1] = 5
    7. array1[2] = 6
    8. // Array(5)的构造函数
    9. val array2: Array = Array(5) { i ->
    10. (i * i).toString()
    11. }
    12. // intArrayOf(), doubleArrayOf()
    13. val x: IntArray = intArrayOf(1, 2, 3)
    14. println("x[0] + x[1] = ${x[0] + x[1]}")
    15. // 大小为5,值为【0,0,0,0,0】的整型数组
    16. val array3 = IntArray(5)
    17. // 大小为5,值为【1,1,1,1,1】的整型数组
    18. val array4 = IntArray(5) { 1 }
    19. // 大小为5、值为【0,1,2,3,4】的整形数组(值初始化为其索引值)
    20. val array5 = IntArray(5) { it * 1 }
    21. println(array5[4])

    数组的遍历

    1. // 数组遍历
    2. for (item in array) {
    3. println(item)
    4. }
    5. // 带索引遍历数组
    6. for (i in array.indices) {
    7. println("$i -> ${array[i]}")
    8. }
    9. // 遍历元素(带索引)
    10. for ((index, item) in array.withIndex()) {
    11. println("$index -> $item")
    12. }
    13. // forEach遍历数组
    14. array.forEach {
    15. println(it)
    16. }
    17. // forEach增强版
    18. array.forEachIndexed { index, item ->
    19. println("$index -> $item")
    20. }

    3.集合

    • List 
    • Set
    • Map

    集合的可变形与不可变性--以mutable为前缀的为可变集合

    1. //不可变集合
    2. val stringList: List = listOf("one", "two", "one")
    3. //可变集合
    4. val numbers: MutableList<Int> = mutableListOf(1, 2, 3, 4)

    集合排序:

    1. val number3 = mutableListOf(1, 2, 3, 4)
    2. // 随机排序
    3. number3.shuffle()
    4. // 从小到大
    5. number3.sort()
    6. // 从大到小
    7. number3.sortDescending()
    8. // 使用sortBy进行排序,适合单条件排序
    9. lauguageList.sortBy { it.score }
    10. // 使用sortWith进行排序,适合多条件排序
    11. lauguageList.sortWith(compareBy({
    12. it.score
    13. }, { it.name }))

    4.方法

    方法声明

    1. //方法申明
    2. fun learn(days: Int): Boolean {
    3. return days > 100
    4. }
    5. fun 方法(参数):返回值{
    6. 方法体
    7. }

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

    1. package com.lf.testkotlin
    2. fun functionLearn(days: Int): Boolean {
    3. return days > 100
    4. }

    成员方法类().成员方法

    1. fun main() {
    2. Person().test1()
    3. }
    4. class Person {
    5. fun test1() {
    6. println("成员方法")
    7. }
    8. }

    静态方法(类方法):使用伴生对象来实现类方法  类.类方法

    1. fun main() {
    2. Person.test2()
    3. }
    4. class Person {
    5. companion object {
    6. fun test2() {
    7. println("companion object 实现类方法")
    8. }
    9. }
    10. }

    工具类Object关键字来修饰,内部所有的方法都是静态的

    1. package com.lf.testkotlin
    2. object NumUtil {
    3. fun double(num: Int): Int {
    4. return num * 2
    5. }
    6. }
    7. fun main() {
    8. NumUtil.double(2)
    9. }

    单表达式方法当方法返回单个表达式时,可以省略花括号并且在 = 之后指定代码体即可

    fun double(x: Int): Int = x * 2
    

    参数默认值方法参数可以有默认值,当省略相应的参数时使用默认值,与其java相比,减少重载数量

    1. fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) {
    2. }

    可变数量的参数:vararg

    1. fun append(vararg str: Char): String {
    2. val result = StringBuffer()
    3. for (char in str) {
    4. result.append(char)
    5. }
    6. return result.toString()
    7. }

    局部方法:在方法的内部创建方法

    1. fun magic(): Int {
    2. fun foo(v: Int): Int {
    3. return v * v
    4. }
    5. val v1 = (0..100).random()
    6. return foo(v1)
    7. }

    5.Lambda表达式

    无参数的情况

    1. val/var 变量名 = { 操作的代码 }
    2. eg:
    3. //源代码
    4. fun test() {
    5. println("无参数")
    6. }
    7. //lambda代码
    8. val test1 = { println("无参数") }

    有参数的情况

    1. val/var 变量名 : (参数的类型, 参数类型, ...) -> 返回值类型
    2. = {参数1, 参数2, ... -> 操作参数的代码}
    3. // 等价于,即表达式的返回值类型会根据操作的代码自推导出来。
    4. val/var 变量名 = {参数1: 类型, 参数2: 类型, ... ->
    5. 操作参数的代码}
    6. eg:
    7. // 源代码
    8. fun test2(a: Int, b: Int): Int {
    9. return a + b
    10. }
    11. // lambda代码
    12. val test3: (Int, Int) -> Int = { a, b -> a + b }
    13. //或者
    14. val test4 = { a: Int, b: Int -> a + b }

    6.it

    it是在当一个高阶方法中Lambda表达式的参数只有一个的时候可以使用it来使用此参数

    1. // 这里举例一个语言自带的一个高阶方法filter,此方法的作用是过滤掉不满足条件的值
    2. val arr = arrayOf(1, 3, 5, 7, 9)
    3. // 过滤掉数组中元素小于5的元素,取其第一个打印。这里的it就表示每一个元素。
    4. println(arr.filter { it < 5 }.component1())
    5. testClosure(1)(2) {
    6. println(it)
    7. }

    7.下划线

    在使用Lambda表达式的时候,可以用下划线(_)表示未使用的参数,表示不处理这个参数

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

    三、方法进阶

    高阶方法(函数)

    函数作为参数

    1. /**
    2. * eg: 实现一个能够对集合元素进行求和的高阶函数,并且每遍历一个集合元素要有回调
    3. */
    4. fun List.sum(callback: (Int) -> Unit): Int {
    5. var result = 0
    6. for (v in this) {
    7. result += v
    8. callback(v)
    9. }
    10. return result
    11. }
    12. // 调用
    13. val list = listOf(1, 2, 3)
    14. val result = list.sum { println("it:${it}") }
    15. println("${result}")

    函数作为返回值

    1. /**
    2. * eg: 实现一个能够对集合元素进行求和的高阶函数,并且返回一个声明为(scale: Int)-> Float的函数
    3. */
    4. fun List.toIntSum(): (scale: Int) -> Float {
    5. println("第一层函数")
    6. return fun(scale): Float {
    7. var result = 0f
    8. for (v in this) {
    9. result += v.toInt() * scale
    10. }
    11. return result
    12. }
    13. }
    14. // 调用
    15. val listString = listOf("1", "2", "3", "4")
    16. val result2 = listString.toIntSum()(2)
    17. println("计算结果:${result2}")

    闭包(Closure)

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

    特性:

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

    好处:

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

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

    1. fun testClosure(v1: Int): (v2: Int, (Int) -> Unit) -> Unit {
    2. return fun(v2: Int, printer: (Int) -> Unit) {
    3. printer(v1 + v2)
    4. }
    5. }
    6. // 调用
    7. testClosure(1)(2) {
    8. println(it)
    9. }

    方法的解构声明

    把里面的字段给解构出来

    1. data class Result(val message: String, val code: Int)
    2. fun test11() {
    3. var result = Result("message", 0)
    4. // 解构
    5. val (message, code) = result
    6. println("message:${message} code:${code}")
    7. }

    匿名方法

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

    方法字面值

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

    四、构造方法

    1. /**
    2. * 主构造方法(主构造方法constructer()可以省略)
    3. */
    4. class KotlinClass constructor(name: String) {
    5. // 次构造方法
    6. constructor(view: View, name: String) : this(name) {
    7. println("name:$name")
    8. }
    9. constructor(view: View, name: String, index: Int) : this(name) {
    10. println("name:$name,index:$index")
    11. }
    12. }

    五、继承与覆盖

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

    1. open class Animal(age: Int) {
    2. init {
    3. println(age)
    4. }
    5. open val foot: Int = 0
    6. open fun eat() {
    7. }
    8. }
    9. class Dog : Animal {
    10. constructor(age: Int) : super(age)
    11. override val foot = 4
    12. override fun eat() {
    13. super.eat()
    14. }
    15. }

    六、属性

    1. //其初始器(initializer)、getter和setter都是可选的。如果属性类型可以从初始器(或者从其getter返回值)中推断出来,也可以省略
    2. var [: ] [ = ]
    3. []
    4. []
    5. eg:
    6. val simple: Int? // 类型Int、默认getter、必须在构造方法中初始化
    7. //定义了一个自定义的getter,每次访问该属性时都会调用它。
    8. val isClose: Boolean
    9. get() = Calendar.getInstance().get(Calendar.HOUR_OF_DAY) > 11
    10. //定义了一个自定义的setter,每次给属性赋值时都会调用它
    11. var score: Float = 0.0f
    12. get() = if (field < 0.2f) 0.2f else field * 1.5f
    13. set(value) {
    14. println(value)
    15. }
    16. 属性延迟初始化
    17. lateinit var shop: Shop2
    18. fun setup() {
    19. shop = Shop2()
    20. }
    21. fun test() {
    22. // ::表示创建成员引用或类引用
    23. if (::shop.isInitiaized) println(shop.address)
    24. }

    七、抽象类与接口

    1. /**
    2. * 抽象方法
    3. */
    4. abstract class Printer {
    5. abstract fun print()
    6. }
    7. class FilePrinter : Printer() {
    8. override fun print() {
    9. }
    10. }
    11. /**
    12. * 接口
    13. */
    14. interface Study {
    15. val time: Int//抽象的
    16. fun discuss()
    17. fun learnCourses() {
    18. println("Android 架构师")
    19. }
    20. }
    21. class StudyAS(override val time: Int) : Study {
    22. override fun discuss() {
    23. }
    24. }

    数据类

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

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

    对象表达式与对象声明

    1. open class Address2(name: String) {
    2. open fun print() {
    3. }
    4. }
    5. class Shop2 {
    6. var address: Address2? = null
    7. fun addAddress(address2: Address2) {
    8. this.address = address2
    9. }
    10. }
    11. fun test3() {
    12. // 如果超类型有一个构造方法,则必须传递适当的构造方法参数给它
    13. Shop2().addAddress(object : Address2("Android") {
    14. override fun print() {
    15. super.print()
    16. }
    17. })
    18. }
    19. fun foo() {
    20. val adHoc = object {
    21. var x: Int = 0
    22. var y: Int = 0
    23. }
    24. println(adHoc.x + adHoc.y)
    25. }
    26. /**
    27. * 对象的声明
    28. */
    29. object DataUtil {
    30. fun isEmpty(list: ArrayList<T>): Boolean {
    31. return list?.isEmpty()
    32. }
    33. }

    伴生对象

    1. class Student(val name: String) {
    2. companion object {
    3. val student = Student("Android")
    4. fun study() {
    5. println("Android 架构师")
    6. }
    7. }
    8. }
    9. fun testStudent() {
    10. println(Student.student)
    11. Student.study()
    12. }

    八、泛型

    泛型接口

    1. interface Drinks<T> {
    2. fun taste(): T
    3. fun price(t: T)
    4. }
    5. class Sweet {
    6. val price = 5
    7. }
    8. class Coke : Drinks<Sweet> {
    9. override fun taste(): Sweet {
    10. println("Sweet")
    11. return Sweet()
    12. }
    13. override fun price(t: Sweet) {
    14. println("Coke price:${t.price}")
    15. }
    16. }

    泛型方法

    1. /**
    2. * 泛型方法
    3. */
    4. fun fromJson(json: String, tClass: Class<T>): T? {
    5. // 获取t的实例
    6. val t: T? = tClass.newInstance()
    7. return t
    8. }

    泛型约束

    在kotlin中out代表协变,in代表逆变,为了加深理解我们可以将kotlin的协变看成java的上界通配符,将逆变看成java的下界通配符

    九、注解

    注解的申明

    1. //和一般的声明很类似,只是在class前面加上了annotation修饰符
    2. annotation class ApiDoc(val value: String)
    3. @ApiDoc("修饰类")
    4. class Box {
    5. @ApiDoc("修饰字段")
    6. val size = 8
    7. @ApiDoc("修饰方法")
    8. fun test() {
    9. }
    10. }

    元注解:

    给注解类加注解

    • @Target:定义注解能够应用于哪些目标对象;
    • @Retention:注解的保留期;
    • @Repeatable:标记的注解可以多次应用于相同的声明或类型;
    • @MustBeDocumented:修饰的注解将被文档工具提取到API文档中;

    @Target

    接受一个vararg可变数量的参数,可以同时指定多个作用的目标对象,参数类型限定为AnnotationTarget

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

    @Retention 

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

    1. @Retention元注解取值主要来源于AnnotationRetention枚举类
    2. public enum class AnnotationRetention {
    3. SOURCE,//源代码时期(SOURCE):注解不会存储在输出class字节码中
    4. BINARY,//编译时期(BINARY):注解会存储在class字节码中,但是对反射不可见
    5. RUNTIME//运行时期(RUNTIME):注解会存储在class字节码中,也会对反射可见
    6. }

    使用场景:

    • 提供信息给编译器:编译器可以利用注解来处理一些,比如一些警告信息、错误等。
    • 编译阶段时处理:利用注解信息来生成一些代码,在kotlin生成代码非常常见,一些内置的注解为了与java API的互操作性,往往借助注解在编译阶段生成一些额外的代码。
    • 运行时处理:某些注解可以在程序运行时,通过反射机制获取注解信息来处理一些程序逻辑。

    十、扩展

    1.好处

    • 提供架构的易用性;
    • 减少代码量,让代码更加整洁、纯粹;
    • 提高编码的效率,生产力提高;

    2.扩展方法

    1. //交换列表中的两个元素的位置
    2. fun MutableList.swap(index1: Int, index2: Int) {
    3. val temp = this[index1]
    4. this[index1] = this[index2]
    5. this[index2] = temp
    6. }
    7. //泛型扩展方法
    8. fun MutableList.swap2(index1: Int, index2: Int) {
    9. val temp = this[index1]
    10. this[index1] = this[index2]
    11. this[index2] = temp
    12. }

    3.扩展属性

    1. //获取字符串中的最后一个元素
    2. fun String.lastChar():Char = this.get(this.length - 1)

    4.为伴生对象添加扩展

    1. class Jump {
    2. companion object {}
    3. }
    4. fun Jump.Companion.print(str: String) {
    5. println(str)
    6. }

    5.kotlin中的扩展

    let

    • 一个作用域函数
    • 可以避免写一些判断null的操作

    1. //原型
    2. fun T.let(f: (T) -> R): R = f(this)
    3. //举例
    4. fun testLet(str: String?) {
    5. // 避免为null的操作
    6. str?.let {
    7. println(it.length)
    8. }
    9. //限制作用域
    10. str.let {
    11. val str2 = "let作用域"
    12. println(it + str2)
    13. }
    14. }

    run

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

    1. //原型
    2. fun T.run(f: T.() -> R): R = f()
    3. //举例
    4. data class Room(val address: String, val price: String, val size: Float)
    5. fun testRun(room: Room) {
    6. room.run {
    7. println("Room:$address,$price,$size")
    8. }
    9. }

    apply

    调用某对象的apply函数,在函数范围内,可以任意调用该对象的任意方法,并返回该对象。

    1. //原型
    2. fun T.apply(f: T.() -> Unit): T { f(); return this }
    3. //举例
    4. fun testApply() {
    5. ArrayList().apply {
    6. add("1")
    7. add("2")
    8. add("3")
    9. }.let {
    10. println(it)
    11. }
    12. }

    从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply函数的返回的是传入对象的本身。

    十一、扩展案例

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

    1. //为Activity添加find扩展方法,用于通过资源id获取控件
    2. fun Activity.find(@IdRes id: Int): T {
    3. return findViewById(id)
    4. }
    5. //为Int添加onClick扩展方法,用于为资源id对应的控件添加onclick监听
    6. fun Int.onClick(activity: Activity, click: () -> Unit) {
    7. activity.find(this).apply {
    8. setOnClickListener {
    9. click
    10. }
    11. }
    12. }
    13. //使用
    14. val textView = find(R.id.text)
    15. R.id.text.onClick(this) {
    16. textView.text = "kotlin扩展"
    17. }

    十二、实用技巧

    • 使用Kotlin安卓扩展,减少findViewById的模板代码
    1. //使用方法
    2. //1.gradle中引入插件:
    3. apply plugin: 'kotlin-android-extensions'
    4. //2.在代码中导入:
    5. import kotlinx.android.synthetic.main.<布局>.*
    6. //3.若需要调用View的合成属性,同时还应该导入
    7. import kotlinx.android.synthetic.main.view.*
    8. //4.最后可以通过控件id来访问这些控件了
    • 字符串的空判断,使用 isNullOrEmpty、isNullOrBlank
    • 使用@JvmOverloads告别繁琐的构造函数重载

    ​​​​​​​

    1. //java
    2. public class CustomView extends FrameLayout {
    3. public CustomView(@NonNull Context context) {
    4. super(context);
    5. }
    6. public CustomView(@NonNull Context context, @Nullable AttributeSet attrs) {
    7. super(context, attrs);
    8. }
    9. public CustomView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    10. super(context, attrs, defStyleAttr);
    11. }
    12. }
    13. //kotlin
    14. class CustomKotlinView @JvmOverloads constructor(
    15. context: Context,
    16. attrs: AttributeSet? = null,
    17. defStyleAttr: Int = 0
    18. ) : FrameLayout(context, attrs, defStyleAttr) {
    19. }

  • 相关阅读:
    机器人使用记录
    【Java】和【C语言】实现一个有序数组的二分查找
    nisshinbo日清纺NJW4750-T1四通道组合稳压器应用方案
    linux守护进程
    面试官:为什么 Redis 要有哨兵?我该怎么回答?
    安卓请求权限
    【技术分享】堆叠交换机替换指导
    python--数据容器--set(集合)
    区间查找题解(优先队列+二分)
    接口测试场景:怎么实现登录之后,需要进行昵称修改?
  • 原文地址:https://blog.csdn.net/weixin_42277946/article/details/132419882