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.数组

数组的创建

        // arrayOf
        val array: Array = arrayOf(1, 2, 3)

        // arrayOfNulls
        val array1: Array = arrayOfNulls(3)
        array1[0] = 4
        array1[1] = 5
        array1[2] = 6

        // Array(5)的构造函数
        val array2: Array = 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,值为【1,1,1,1,1】的整型数组
        val array4 = IntArray(5) { 1 }

        // 大小为5、值为【0,1,2,3,4】的整形数组(值初始化为其索引值)
        val array5 = IntArray(5) { it * 1 }
        println(array5[4])

数组的遍历

        // 数组遍历
        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.集合

  • List 
  • Set
  • Map

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

//不可变集合
val stringList: List = listOf("one", "two", "one")

//可变集合
val numbers: MutableList = mutableListOf(1, 2, 3, 4)

集合排序:

        val number3 = mutableListOf(1, 2, 3, 4)

        // 随机排序
        number3.shuffle()

        // 从小到大
        number3.sort() 

        // 从大到小
        number3.sortDescending() 

        // 使用sortBy进行排序,适合单条件排序
        lauguageList.sortBy { it.score }

        // 使用sortWith进行排序,适合多条件排序
        lauguageList.sortWith(compareBy({
            it.score
        }, { it.name }))

4.方法

方法声明

//方法申明   
fun learn(days: Int): Boolean {
    return days > 100
}
fun  方法(参数):返回值{
    方法体
}

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

package com.lf.testkotlin

fun functionLearn(days: Int): Boolean {
    return days > 100
}

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

fun main() {
    Person().test1()
}

class Person {

    fun test1() {
        println("成员方法")
    }
}

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

fun main() {
    Person.test2()
}

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

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

package com.lf.testkotlin

object NumUtil {
    fun double(num: Int): Int {
        return num * 2
    }
}

fun main() {
    NumUtil.double(2)
}

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

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

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

fun read(b: Array, off: Int = 0, len: Int = b.size) {

}

可变数量的参数:vararg

fun append(vararg str: Char): String {
    val result = StringBuffer()
    for (char in str) {
        result.append(char)
    }
    return result.toString()
}

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

fun magic(): Int {
    fun foo(v: Int): Int {
        return v * v
    }

    val v1 = (0..100).random()
    return foo(v1)
}

5.Lambda表达式

无参数的情况

    val/var 变量名 = { 操作的代码 }

eg:
    //源代码
    fun test() {
        println("无参数")
    }

    //lambda代码
    val test1 = { println("无参数") }

有参数的情况

val/var 变量名 : (参数的类型, 参数类型, ...) -> 返回值类型 
    = {参数1, 参数2, ... -> 操作参数的代码}
// 等价于,即表达式的返回值类型会根据操作的代码自推导出来。
val/var 变量名 = {参数1: 类型, 参数2: 类型, ... -> 
    操作参数的代码}

eg:
    // 源代码
    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 }

6.it

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

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

testClosure(1)(2) {
    println(it)
}

7.下划线

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

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) }

三、方法进阶

高阶方法(函数)

函数作为参数

    /**
     * eg: 实现一个能够对集合元素进行求和的高阶函数,并且每遍历一个集合元素要有回调
     */
    fun List.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}")

函数作为返回值

    /**
     * eg: 实现一个能够对集合元素进行求和的高阶函数,并且返回一个声明为(scale: Int)-> Float的函数
     */
    fun List.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}")

闭包(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)
}

方法的解构声明

把里面的字段给解构出来

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}")
}

匿名方法

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

方法字面值

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

四、构造方法

/**
 * 主构造方法(主构造方法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")
    }
}

五、继承与覆盖

父类必须用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()
    }
}

六、属性

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

eg:

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

//定义了一个自定义的getter,每次访问该属性时都会调用它。
val isClose: Boolean
        get() = Calendar.getInstance().get(Calendar.HOUR_OF_DAY) > 11

//定义了一个自定义的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)
    }

七、抽象类与接口

/**
 * 抽象方法
 */
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() {

    }
}

数据类

必须要有至少一个参数,并且不能被定义成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  isEmpty(list: ArrayList): 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()
}

八、泛型

泛型接口

interface Drinks {
    fun taste(): T
    fun price(t: T)
}

class Sweet {
    val price = 5
}

class Coke : Drinks {
    override fun taste(): Sweet {
        println("Sweet")
        return Sweet()
    }

    override fun price(t: Sweet) {
        println("Coke price:${t.price}")
    }
}

泛型方法

/**
 * 泛型方法
 */
fun  fromJson(json: String, tClass: Class): T? {
    // 获取t的实例
    val t: T? = tClass.newInstance()
    return t
}

泛型约束

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

九、注解

注解的申明

//和一般的声明很类似,只是在class前面加上了annotation修饰符
annotation class ApiDoc(val value: String)

@ApiDoc("修饰类")
class Box {
    @ApiDoc("修饰字段")
    val size = 8

    @ApiDoc("修饰方法")
    fun test() {
        
    }
}

元注解:

给注解类加注解

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

@Target

接受一个vararg可变数量的参数,可以同时指定多个作用的目标对象,参数类型限定为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//表示作用对象是一个类型别名
}

@Retention 

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

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

使用场景:

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

十、扩展

1.好处

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

2.扩展方法


//交换列表中的两个元素的位置
fun MutableList.swap(index1: Int, index2: Int) {
    val temp = this[index1]
    this[index1] = this[index2]
    this[index2] = temp
}

//泛型扩展方法
fun  MutableList.swap2(index1: Int, index2: Int) {
    val temp = this[index1]
    this[index1] = this[index2]
    this[index2] = temp
}

3.扩展属性

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

4.为伴生对象添加扩展

class Jump {
    companion object {}
}

fun Jump.Companion.print(str: String) {
    println(str)
}

5.kotlin中的扩展

let

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

//原型
fun  T.let(f: (T) -> R): R = f(this)

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

run

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

//原型
fun  T.run(f: T.() -> R): R = f()

//举例
data class Room(val address: String, val price: String, val size: Float)

fun testRun(room: Room) {
    room.run {
        println("Room:$address,$price,$size")
    }
}

apply

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

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

//举例
fun testApply() {
    ArrayList().apply {
        add("1")
        add("2")
        add("3")
    }.let {
        println(it)
    }
}

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

十一、扩展案例

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

//为Activity添加find扩展方法,用于通过资源id获取控件
fun  Activity.find(@IdRes id: Int): T {
    return findViewById(id)
}

//为Int添加onClick扩展方法,用于为资源id对应的控件添加onclick监听
fun Int.onClick(activity: Activity, click: () -> Unit) {
    activity.find(this).apply {
        setOnClickListener {
            click
        }
    }
}

//使用
val textView = find(R.id.text)
R.id.text.onClick(this) {
    textView.text = "kotlin扩展"
}

十二、实用技巧

  • 使用Kotlin安卓扩展,减少findViewById的模板代码
//使用方法
//1.gradle中引入插件:
apply plugin: 'kotlin-android-extensions'

//2.在代码中导入:
import kotlinx.android.synthetic.main.<布局>.*

//3.若需要调用View的合成属性,同时还应该导入
import kotlinx.android.synthetic.main.view.*

//4.最后可以通过控件id来访问这些控件了

  • 字符串的空判断,使用 isNullOrEmpty、isNullOrBlank
  • 使用@JvmOverloads告别繁琐的构造函数重载

​​​​​​​

//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) {
    
}

你可能感兴趣的:(Android:基础篇,kotlin,开发语言,android)