目录
写在前面
盛年不重来,一日难再晨。及时当勉励,岁月不待人。时间过的是真快,0202年都快过完了,最后一个月了,是不是发现你离梦想越来越远了?😂
不扯淡了,回归正题。有关注过我的朋友应该知道我之前写了一个关于Kotlin技术的专栏,前段时间我自己翻过去看了看,我只想对自己说:卧槽,这写的什么j8玩意?于是乎我把它们全都删了,正因为如此,才有了今天的内容。重新整理下思路,把核心的知识点列出来,归纳到一篇里面来,争取做到用最短的时间让你快速高质量的掌握Kotlin开发的核心技术。OK,话不多说,开干!
一、Kotlin概述
1.1、Kotlin简介
- JetBrains开发,2019年的Google I/O大会上Kotlin被选为Android开发首选语言
- 一种在Java虚拟机上运行的静态类型编程语言
- 可以和Java代码相互运作
- 可以在Android项目中替代Java或者同Java一起使用
- 简洁易用:Kotlin中提供了大量的扩展,使得代码更加简洁
- 安全:避免了空指针异常等常见的一些错误,使得代码更加健壮
- 互操作性:充分利用JVM上Android和浏览器现有的库,与已有的技术完成互通
- 工具友好:使用任何Java IDE或者命令行来构建Kotlin应用
- 官方文档:https://www.kotlincn.net/docs/reference/
1.2、Kotlin构建流程
注:(图片来自网络)
1.3、Kotlin环境配置
工程的build.gradle文件中添加如下配置:
buildscript {
ext.kotlin_version = '1.3.50'
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
app module的build.gradle文件中添加如下配置:
//顶部添加以下两行
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
//依赖闭包中添加以下几行
dependencies {
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.core:core-ktx:1.0.2'
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}
二、Kotlin必备基础
在没说正文之前,先来说一下如何声明一个变量,因为下面的代码中经常会用到,其实很简单哈,所以不打算单独拎出来说了,就在这里先说一下:
val/var str:String = "Hello Kotlin"
val修饰的是只读变量,var修饰的是可读写变量
2.1、基本数据类型
Kotlin的基本数值类型包括Byte、Short、Int、Long、Float、Double等。跟Java有所不同的是,字符不属于数值类型,是一个独立的数据类型。
对于整数,存在四种具有不同大小和值范围的类型:
对于浮点数,Kotlin提供了Float和Double类型:
理论知识就说这么多,大家都有Java基础,这些东西大家借助官方文档稍微看一下就行了,不多说了。老规矩,咱们还是稍微写点代码感受下:
fun main() {
println("--------一起来学Kotlin---------")
baseType()
}
fun baseType() {
val num1 = 5 //Int 我们并未定义数据类型,编译器自动推断数据类型
val num2 = 8.88 //Double
val num3 = 3f //Float
//println()底层调用的是System.out.println()
//使用"$"来访问变量进行字符串拼接
println("num1=$num1 num2=$num2 num3=$num3")
println(num2.toInt()) //Double转换成Int,调用toXXX()方法进行数据类型转换
getDataType(num1)
getDataType(num2)
getDataType(num3)
}
//获取对应的数据类型
fun getDataType(params: Any) {
//使用"${}"可以访问表达式
println("$params 是 ${params::class.simpleName} 类型")
}
通过上面的代码,我们对Kotlin的使用能够有一个最简单的了解,并且知道了Kotlin可以自动进行类型推断,不用再像Java那样必须声明出对应的类型了:
2.2、数组
数组在Kotlin中使用Array类来表示,它定义了get和set方法(按照运算符重载约定会转变为[])和size属性,以及一些其它有用的成员方法。下面通过代码来进行说明:
创建数组的常用方式,这里介绍7种:
//arrayOf
val arr1 = arrayOf(1, 2, 3, 4)
//arrayOfNulls
val arr2 = arrayOfNulls<Int>(3)
arr2[0] = 1
arr2[1] = 2
arr2[2] = 3
//Array(3)的构造函数,使用lambda表达式的形式,内部i为数组的索引
val arr3 = Array(3) { i -> (i * 3).toString() }
//显示声明数组类型,调用intArrayOf()函数,类似的还有doubleArrayOf()。。。等等
val arr4: IntArray = intArrayOf(3, 4, 5)
//大小为5,值为[0,0,0]的整型数组
val arr5 = intArrayOf(3)
//用常量初始化数组中的值,长度为3,值为[33,33,33]的整型数组
val arr6 = IntArray(3) { 33 }
//使用lambda表达式初始化数组中的值,长度为3,值为[0,1,2]的整型数组,值初始化为其索引值
val arr7 = IntArray(3) { it * 1 }
println(arr7.asList())
遍历数组的常用方式,这里介绍5种:
//普通遍历
for (i in arr1){
println(i)
}
//带索引遍历数组
for (i in arr1.indices){
println("$i = ${arr1[i]}")
}
//带索引遍历元素
for ((index,element) in arr1.withIndex()){
println("$index = $element")
}
//forEach遍历数组,it代表数组元素的每一项
arr1.forEach { println(it) }
//forEach增强版
arr1.forEachIndexed{index, element ->
println("$index = $element")
}
2.3、集合
Kotlin标准库提供了一整套用于管理集合的工具,集合是可变数量的一组条目,并且对于解决实际问题具有重要意义,在项目开发中经常会被使用到:
- List是一个有序集合,可以通过索引(反映元素位置的整数)访问元素。元素可以在list中出现多次。比如:有一系列的文字,这些文字的顺序很重要并且文字可以重复
- Set是唯一元素的集合,它反映了集合(set)的数学抽象:一组无重复的对象。一般来说set中元素的顺序并不重要。比如:字母表是字母的集合(set)
- Map(或者字典)是一组键值对。键是唯一的,每一个键都刚好映射到一个值,值可以重复
集合的可变性与不可变性
在Kotlin中存在两种意义上的集合,一种是可以修改的,一种是不可修改的:
/**********不可变集合*************/
//List集合中的元素可以出现多次
val strList = listOf("a", "b", "a")
println(strList)
//Set集合中的元素必须是唯一的,
val strSet = setOf("a", "b", "a")
println(strSet)
/**********可变集合************/
val numList = mutableListOf(1, 2, 3, 4)
numList.add(5)
numList.removeAt(2)
numList[0] = 0
println(numList)
val hello: MutableSet<String> = mutableSetOf("H", "e", "l", "l", "o")
hello.remove("H")
println(hello)
//集合支持加减操作,前提是前一个集合必须是可变的
hello += setOf("K", "o", "t", "l", "i", "n")
println(hello)
/*****Map<K,V>不是Collection接口的继承类,但它也是Kotlin的一种集合类型*****/
//"key1"是key,to后面跟的是value
val numMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3)
println("Keys:${numMap.keys}")
println("Values:${numMap.values}")
//判断一个元素在不在map集合中
if ("key3" in numMap) println("Value is:${numMap["key3"]}")
if (numMap.containsKey("key3")) println("Value is:${numMap["key3"]}")
if (2 in numMap.values) println("2 is in this map")
if (numMap.containsValue(2)) println("2 is in this map")
集合的排序
val numList = mutableListOf(1, 2, 3, 4)
//随机排序
numList.shuffle()
println(numList)
numList.sort() //从小到大排序
println(numList)
numList.sortDescending() //从大到小排序
println(numList)
//条件排序
//自定义数据类:学科 类似于Java中的实体
data class Subject(var name: String, var score: Int)
//定义可变数组,添加四条数据
val subList: MutableList<Subject> = mutableListOf()
subList.add(Subject("语文", 90))
subList.add(Subject("数学", 99))
subList.add(Subject("英语", 89))
subList.add(Subject("物理", 88))
//使用sortBy进行排序,适合单条件排序
subList.sortBy { it.score }
println(subList)
//使用SortWith进行排序,适合多条件排序
subList.sortWith(compareBy(
//it前面也说过了,它实际上就是lambda表达式中的隐式参数
{ it.name }, { it.score })
)
println(subList)
2.4、空类型安全
在Java中有一个很恶心的异常:NPE空指针异常,到了Kotlin中在设计这门语言的时候就考虑到了这个问题,那么自然也就给出了对应的解决办法,下面就来详细说一说:
在Java中定义一个String类型变量的时候你是不确定这个变量是否为空的,并且是可以给它赋值为空的,所以Kotlin为了100%的兼容Java,那它就必须得能够接收空啊,那怎么办呢?嗯,我们在定义的时候给它加一个问号“?”,任何类型都是可以的哦,在后面加?表示该类型可以为空:
fun testNullAble(){
var nullable:String?="Hello Kotlin"
nullable = null
}
定义完了之后,在使用的时候问题又来了,比如下面这段代码,因为你这个nullable是可能为空的,所以你在获取它的长度的时候,编译器不确定你是否为空,就只能给你编译不通过了:
针对于这种问题解决办法一般有两种:
①、明确不为空
强制告诉编译器我确定是不为空的:!!,俩感叹号,这是个类型强转,表示强转为不可空类型:
fun testNullAble(){
var nullable:String?="Hello Kotlin"
//这里明确nullable不为空,使用!!
val length = nullable!!.length
}
②、不确定是否为空
使用elvis表达式,即“?:”问号冒号这种形式,它有点类似于C语言里面的三目运算符,如果为空会给它一个默认值,保证安全性:
fun testNullAble() {
var nullable: String? = "Hello Kotlin"
nullable = null
//不为空时值为nullable!!.length,为空时值为0
val length = nullable?.length ?: 0
println("length=${length}")
}
这样就不会报错啦:
2.5、分支表达式
在Kotlin中我们经常使用到的分支表达式主要有if...else表达式和when表达式,if...else...不多说了和Java中没什么区别,when表达式类似于Java中的switch表达式:
fun testExpre(a: Int): Int {
var b = 0
//if...else
if (a > 3) b = 3 else b = 0
//when表达式,类似于Java中的Switch
when (a) {
0 -> b = 0
1 -> b = 1
else -> b = 10
}
//when表达式变形:b提到外面,每个分支最后的部分作为表达式的值
b = when (a) {
0 -> 0
1 -> 1
else -> 10
}
//when表达式变形:判断条件转移到每个分支上
when {
a == 0 -> b = 0
a == 1 -> b = 1
else -> b == 10
}
//when表达式变形:同样可以把b提到外面
b = when {
a == 0 -> 0
a == 1 -> 1
else -> 10
}
return b
}
三、Kotlin方法与Lambda表达式
3.1、Kotlin方法基础
Kotlin方法的声明:
max:方法名
(a:Int):参数及参数类型,冒号后面的是参数类型
:Int 小括号后面的这个Int是返回值的类型
{}:花括号里面的内容是方法体
下面通过代码来学习方法的具体使用,最好是实际的敲一遍,不然看完一遍过一会儿你应该是没啥印象了😆:
fun main() {
println("funTest(20)=${funTest(20)}")
Person().name()
Person.age()
println("LogUtil.calculate(40)=${LogUtil.sqrt(40)}")
println("sqrt(40)=${sqrt(40)}")
read("I Love China")
println(append('I',' ','L','o','v','e',' ','C','h','i','n','a'))
println("sqrtRandom()=${sqrtRandom()}")
}
//方法的定义,在Kotlin中方法可以直接定义在文件里面,不用定义在类里面,在Kotlin中方法才是一等公民
fun funTest(a:Int):Boolean{
return a>10
}
class Person{
//Kotlin的成员方法,类似于Java中类的成员方法
fun name(){
println("成员方法")
}
//Kotlin的类方法,类似于Java中类的静态方法,companion object叫做Kotlin的伴生对象
companion object{
fun age(){
println("通过companion object实现类方法")
}
}
}
//Kotlin中实现类似于Java中的工具类,通过object修饰一个类名,就可以创建一个静态类
//整个静态类
object LogUtil{
fun sqrt(a:Int):Int{
return a*a
}
}
//单表达式方法:当方法返回单个表达式时,可以省略花括号并且在"="符号之后指定代码体即可
fun sqrt(a:Int):Int = a*a
//默认参数:方法参数可以有默认值,当省略相应的参数时使用默认值。跟Java相比,可以减少方法的重载数量
fun read(content:String,start:Int=0,len:Int=content.length){
println("文本:$content,开始位置:$start,文本长度:$len")
}
//可变数量的参数
fun append(vararg ch:Char):String{
val all = StringBuffer()
for (char in ch){
all.append(char)
}
return all.toString()
}
//方法的作用域
//在Kotlin中方法可以在文件中直接定义,不需要像Java那样创建一个类来保存这些方法,
// 除此之外,Kotlin方法还可以声明在一些局部的作用域里面,作为一些成员方法和扩展方法
//成员方法上面已经说过了,下面来看下局部方法,也就是在一个方法中再定义其它的方法
fun sqrtRandom():Int{
fun sq(a:Int):Int{
return a*a
}
val aa = (0..100).random()
return sq(aa)
}
对照着结果来理解一下:
3.2、Kotlin Lambda表达式基础
在JDK1.8的时候Java语言开始支持Lambda表达式,它可以理解为一种语法糖,Kotlin一开始就支持了这种语法,它本质上其实就是匿名方法,它的出现为开发者带来了许多便捷,使得代码编写变的更加简介。
特点:①、匿名方法②、可传递
语法如下:
无参数的情况:
val/var 变量名 = { 操作的代码 }
有参数的情况:
val/var 变量名 : (参数的类型,参数类型,... ) -> 返回值类型 = { 参数1,参数2,... -> 操作参数的代码 }
可等价于:
//此种写法:即表达式的返回值类型会根据操作的代码自动推导出来
val/var 变量名 = { 参数1:类型,参数2:类型,... -> 操作参数的代码 }
下面到代码中实际来看一下该如何使用:
fun main() {
test1()
println("test3(8,7)=${test3(8, 7)}")
println("test4(8,7)=${test4(8, 7)}")
}
//无参数情况
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
- it并不是Kotlin中的一个关键字(保留字)
- it是在当一个高阶方法中lambda表达式的参数只有一个的时候可以使用it来使用此参数
- it可表示为单个参数的隐式名称,是Kotlin语言约定的
举个栗子吧:比如Kotlin函数里的高阶函数filter()
fun test5() {
val arr = arrayOf(1, 2, 3, 4)
println("arrfilter:${arr.filter { it < 4 }.component1()}")
println("arrfilter:${arr.filter { it < 4 }}")
}
如何使用下划线_
在使用lambda表达式的时候,可以用下划线"_"表示未使用的参数,表示不处理这个参数
fun test6() {
val map = mapOf("key1" to "Value1", "key2" to "Value2", "key3" to "Value3")
map.forEach { (key, value) ->
println(value)
}
//这里key我们并不需要,所以可以通过下划线来替代第一个参数
map.forEach { (_, value) -> println(value) }
}
3.3、Kotlin高阶方法
高阶方法:将函数作为参数或者返回值的函数,Kotlin支持高阶方法,这也是Kotlin函数式编程的一大特性。下面咱们通过代码来学习Kotlin的高阶函数:
①、函数作为参数
fun main() {
val list = listOf(7, 8, 9)
val result = list.sum { println("element:${it}") }
println("result=${result}")
}
//高阶函数:函数作为参数
//实现一个能够对集合元素进行求和的高阶函数,并且每次遍历一个集合元素要有回调
fun List<Int>.sum(callback: (Int) -> Unit): Int {
//定义一个变量用以接收整个高阶函数的计算结果
var result = 0
//遍历集合,这里遍历this,this也就是扩展函数本身,这里就是List<Int>
for (element in this) {
result += element
callback(element) //回调callback函数
}
return result //将计算结果进行返回
}
结果如下:
②、函数作为返回值
fun main() {
val listString = listOf("7","8","9")
val value = listString.toIntSum()(200)
println("value=${value}")
}
//高阶函数:函数作为返回值
//实现一个能够对集合元素进行求和的高阶函数,并且返回一个声明为(translate:Int)->Int的函数
fun List<String>.toIntSum(): (translate: Int) -> Int { //外层函数
return fun(translate): Int { //内层函数,即返回值函数
//定义一个变量用来计算结果
var result = 0
//遍历集合,这里同样遍历this,即:List<String>
for (element in this) {
result += element.toInt() + translate //将元素转为Int类型,并且每一项的值都添加一个translate的值
}
return result //内层函数的返回值
} //整体作为外层函数的返回值
}
结果如下:
3.4、闭包(Closure)
- 能够读取其他方法内部变量的方法
- 定义在一个方法体内部的方法,它是将方法内部和方法外部连接起来的桥梁
闭包的特性:
- 方法可以作为另一个方法的返回值或参数,还可以作为一个变量的值
- 方法可以嵌套定义,即:在一个方法内部可以定义另一个方法
下面我们还是通过代码来实际学习一下闭包的使用:
fun main() {
addClosure(5)(6){
println("addClosure=${it}")
}
}
//闭包:实现一个addClosure方法,该方法接收一个Int类型的v1参数,同时能够返回一个
// 声明为(v2:Int,(Int)->Unit)的函数,并且这个函数能够计算v1与v2的和
fun addClosure(v1: Int): (v2: Int, (Int) -> Unit) -> Unit {
//第一个参数是Int类型,第二个参数是一个入参为Int类型返回值为Unit的函数,这里定义函数名为add
return fun(v2: Int, add: (Int) -> Unit) {
add(v1 + v2) //计算v1和v2的和
}
}
结果如下:这个例子还是比较复杂的,它自身首先将(v2: Int, (Int) -> Unit)这个函数作为返回值,所以本身是一个高阶函数,同时它的方法体内部又定义了一个方法,这个内部的方法本身又是一个返回值为函数的高阶函数,这个内部方法还可以读取外部方法的变量,所以它既是闭包又是高阶函数。
3.5、解构声明
Kotlin支持将一个对象中的字段给解构出来,举个栗子如下:
fun main() {
destructFun()
}
//定义数据类
data class ResponseData(val msg:String,val status:Int)
fun destructFun(){
val res = ResponseData("success",0)
//解构声明的语法如下,val(解构字段) = 对象,这里注意要和变量名称相对应
val (msg,status) = res
println("msg=${msg},code=${status}")
}
3.6、匿名方法
顾名思义就是没有方法名的方法,举个栗子吧:
//匿名函数,函数没有函数名,同时它也是一个表达式函数
val anony1 = fun(a: Int, b: Int): Int = a + b
val anony2 = fun(a: Int, b: Int): Int { //后面也可以直接跟上方法体
return a + b
}
3.7、方法的字面值(量)
方法的字面量可以理解为未声明的方法,说白了它就是一段代码,可以当做参数进行传递。方法的字面量是匿名的,但是可以为其绑定一个变量给它一个名字。
在 Kotlin中,Lambda表达式
、匿名函数
,都是一种函数字面值,下面我们来举个栗子看一下:
fun main() {
check()
}
fun check(){
//定义了一个变量temp,而该变量的类型就是 (Int)->Boolean
var temp:((Int)->Boolean)?=null
//{num->(num>0)} 它就是方法字面值
temp = {num->(num>0)}
println("temp(10)=${temp(10)}")
}
四、Kotlin类与接口
4.1、构造方法
Kotlin类允许其有一个主构造方法和多个次构造方法,但是每个次构造方法都必须先调用它的主构造方法
//构造方法分为主构造方法与次构造方法,直接声明在类名后面的是柱构造方法,并且constructor可以省略
class KotlinClass constructor(name: String) {
//内部使用constructor声明次构造方法,次构造方法必须调用主构造方法
constructor(age: Int, name: String) : this(name) {
//同时构造方法可以有自己的方法体
println("name=${name}")
}
//次构造方法可以有多个,但是每个次构造方法都必须先调用柱构造方法,完成主构造的初始化
constructor(age: Int, name: String, birthday: String) : this(name) {
//同时构造方法可以有自己的方法体
println("name=${name}")
}
}
4.2、类的继承
①、类的继承关系
Kotlin中的类默认都是final类型的,无法被继承:
想要让一个类可以被继承,需要使用"open"关键字修饰,下面通过代码来进行讲解:
//类默认都是final类型的,想要能够被继承需要加上open关键字
open class Animal(age: Int) {
init { //可以使用init代码块来完成相关的初始化
println("age=${age}")
}
//属性默认是隐藏的,想要被覆盖必须加上open
open val weight:Int = 0
//方法默认也是隐藏的,不允许子类直接覆盖,想要被覆盖也要加上open关键字
open fun eat(food:String){
println("吃${food}")
}
}
//Kotlin中类的继承用冒号":"
class Dog(age: Int) : Animal(age) {
//使用override关键字覆盖父类的属性
override val weight: Int = 100
//覆写父类的eat方法
override fun eat(food: String) {
super.eat(food)
}
}
②、属性声明及初始化
声明一个属性的完整语法是:
var <propertyName>[:<PropertyType>][=<property_initializer>]
[<getter>]
[<settter>]
其初始器(initializer)、getter和setter都是可选的。下面来看一个属性声明的栗子:
fun main() {
println("${Shop().name} 地址:${Shop().address} 评分:${Shop().score} 是否营业:${Shop().isClose} ")
}
//属性的声明及初始化,setter和getter
class Shop {
val name: String = "便利店"
// val address: String? = null
val address: String?
get() { //自定义getter方法初始化
return "南京"
}
val isClose: Boolean
get() = Calendar.getInstance().get(Calendar.HOUR_OF_DAY) < 24
var score: Int = 10
get() = if (field < 6) 6 else field
//注意val类型的只读变量不能有set方法
set(value) {
println(value)
}
}
③、延迟初始化
fun main() {
val test = Test()
test.inits()
test.test()
}
class Test {
//使用lateinit关键字进行初始化
lateinit var shop: Shop
fun inits() {
//在inits()方法里面对shop进行初始化
shop = Shop()
}
fun test() {
//使用isInitialized判断被标记为延迟初始化的属性是否被初始化
//::两个冒号表示创建成员引用或者类引用
if (::shop.isInitialized) println(shop.address)
}
}
4.3、抽象类
直接通过代码来学习它的说明及使用吧:
//抽象类
abstract class Car{
//在Kotlin中定义抽象类或者抽象方法要使用abstract关键字修饰
abstract fun drive()
}
//实现类
class Bus:Car(){
//实现父类的抽象方法
override fun drive() {
println("驾驶公交车")
}
}
4.4、接口
Kotlin接口和Java接口是有区别的:Kotlin接口既可以包括抽象方法的声明,也可以包括一些方法的实现
//Kotlin接口和Java接口是有区别的:Kotlin接口既可以包括抽象方法的声明,也可以包括一些方法的实现
interface Device{
val time:Int //接口的抽象属性,此处未定义它自己的实现
fun input() //抽象方法没有自身的实现
fun output(){ //此处抽象方法有自身的实现
println("输出")
}
}
//在主构造方法中重写接口的抽象属性time
class UsbDev(override val time: Int) :Device{
override fun input() {
//由于接口中input()方法没有自己的实现,所以这里要求我们需要实现该方法
println("重写输入设备")
}
}
接着来看接口中的一种特殊情况,具有覆盖冲突的情况如何解决?
interface A{
fun pr(){
println("A")
}
}
interface B{
fun pr(){
println("B")
}
}
//定义类C同时实现接口A和接口B
class C:A,B{
override fun pr() {
//此时调用父类的pr()方法会报错,因为接口A和B中定义了同名方法,无法区分
// super.pr()
//为了解决这种覆盖冲突,可以使用泛型指定调用的具体是哪一个接口
super<A>.pr()
super<B>.pr()
}
}
4.5、数据类
//数据类的主构造方法中必须至少有一个参数
//数据类不能被继承,即:不能使用open或者override关键字修饰
data class People(var name: String, val age: Int) { //数据类可以有自己的类体
var phone: String = ""
fun pr() { //数据类也可以定义自己的方法
println(phone)
}
}
4.6、对象表达式与对象声明
①、对象表达式
//Kotlin中需要对一个类做轻微改动或者创建对象时无需再去显示声明一个新的类,使用对象表达式即可满足
open class Address(name: String) {
open fun print() {}
}
class Shop2 {
var address: Address? = null
fun addAddress(address: Address) {
this.address = address
}
}
fun testAdd() {
/**
* 使用Shop2的对象调用addAddress()方法时需要实例化一个Address对象
* 同时对Address类中的print()方法做一定的修改,此时我们不想再创建一个新的类
* 来继承Address,此时该如何做呢?我们的对象表达式就能派上用场了,使用object来创建对象表达式
* 如果父类中有构造方法,则必须为其传递适当的构造方法参数
*/
Shop2().addAddress(object : Address("Kotlin") {
override fun print() { //重写print()方法
super.print()
println("对print()方法做修改")
}
})
}
②、对象声明
fun testObj() {
/**
* 如果只需要一个很简单的对象,对象里面只有简单的两个int类型的属性a和b
* 那么我们不需要新建一个类,只需要使用下面这种方式:object{}对象声明的形式
* 来创建一个对象即可,这里的对象实际上就是一个"匿名对象"
*/
var addObj = object {
var a: Int = 1
var b: Int = 2
}
println("addObj.a=${addObj.a}")
}
//这种方式声明出来的对象是有名称的,类似于静态方法的调用,前面我们也说过了
object EmptyUtil {
fun <T> isEmpty(list: ArrayList<T>?): Boolean {
return list?.isEmpty() ?: false
}
}
fun testEmpty(){
val list = arrayListOf(1)
println(EmptyUtil.isEmpty(list))
}
4.7、伴生对象
//伴生对象
class Student(val name: String){
//伴生对象在编译时会在外部类中将其创建为一个静态内部类
companion object{
//伴生对象中定义的属性会被直接编译为外部类的静态字段
val student = Student("张三")
//方法会被编译为伴生对象内部的方法
fun study(){
println("学习语文")
}
}
}
fun testStudent(){
println(Student.student) //类似Java中的静态字段
Student.study() //类似Java中的静态方法
}
通过AndroidStudio工具中的Tools--->Kotlin--->Show Kotlin Bytecode,然后点击DECOMPILE查看反编译生成的Java代码,这样我们就知道它的底层代码是什么啦:
五、Kotlin泛型
Kotlin泛型和Java一样就是一种语法糖,源代码中定义Class级别擦除,泛型实际上就是把类型参数化称之为类型参数。
5.1、泛型接口
fun main() {
println(Bike().drive().price)
}
//泛型接口:内部定义两个方法
interface Cars<T> {
fun drive(): T
fun price(t: T)
}
//为T创建一个具体的类
class Speed {
val price = 200
}
//定义接口的实现类,实现泛型接口中的两个方法
class Bike : Cars<Speed> {
override fun drive(): Speed {
println("Speed")
return Speed()
}
override fun price(t: Speed) {
println("Bike price=${t.price}")
}
}
5.2、泛型类
fun main() {
RedColor(Red()).printColor()
}
//泛型类
abstract class Color<T>(var t: T /*这里t也叫泛型字段*/) {
abstract fun printColor()
}
//为T创建一个具体的类
class Red {
val color = "Red"
}
class RedColor(t: Red) : Color<Red>(t) {
override fun printColor() {
//这里的t就是抽象类Color主构造中的泛型字段
println("color:${t.color}")
}
}
5.3、泛型方法
//泛型方法--->能够提高程序的通用性和扩展性
fun <T> fromJson(json:String,tClass:Class<T>):T?{
//获取T的实例,这里就随便实现一下
val t:T? = tClass.newInstance()
return t
}
5.4、泛型约束
Java中可以通过有界类型参数来限制参数类型的边界,Kotlin中泛型约束也可以限制参数类型的上界:
//泛型约束
//Kotlin中约束泛型上界是通过 :冒号后面跟上泛型上界
fun <T:Comparable<T>?> sort(list:List<T>?):Unit{}
fun testSort(){
sort(listOf(1,2,3)) //正确:Int是Comparable<Int>的子类型
// sort(listOf(Red())) //错误:Red不是Comparable<Red>的子类型
}
传入的类型不是Comparable<T>对应的子类型,会出现报错:如下图所示,这就是泛型约束起到的作用
当有多个上界时可以使用where关键字将多个上界列出来:
fun main() {
val listChar = listOf("A", "B", "C")
val list = testMulti(listChar, "A")
println(list)
}
//T有多个上界的情况
fun <T> testMulti(list: List<T>, t: T): List<T>
where T : CharSequence,
T : Comparable<T> {
//值大于t的
return list.filter { it > t }.map { it }
}
结果如下所示:
接着来看泛型中的out和in:
在Kotlin中out代表协变,in代表逆变,我们可以将Kotlin的协变看成Java的上界通配符,将逆变看成Java的下界通配符:
//Kotlin使用处协变
fun sumOfList(list:List<out Number>)
//Java上界通配符
void sumOfList(List<? extends Number> list)
//kotlin使用处逆变
fun addNumbers(list:List<in Int>)
//Java下界通配符
void addNumbers(List<? super Integer> list)
下面通过一张表格来对比Java泛型和Kotlin泛型:总的来说Kotlin泛型更加简洁安全,但是和Java一样都是有类型擦除的,都属于编译时泛型
六、Kotlin注解
首先我们通过代码来看下Kotlin注解的声明和简单使用:
/**
* 和一般的声明类似,只是在class前面加上了annotation修饰符
* 由于这个注解没有限制它的作用目标,所以它可以用来修饰类、方法、字段
*/
annotation class ApiDoc(val value: String)
@ApiDoc("修饰类")
class KTAnoTest {
@ApiDoc("修饰字段")
val size = 10
@ApiDoc("修饰方法")
fun test() {
println("修饰符方法")
}
}
Kotlin中的元注解
和Java一样在Kotlin中一个Kotlin注解类自己本身也可以被注解,可以给注解类加注解,我们把这种注解称为元注解。
Kotlin中的元注解类定义于kotlin.annotation包中,主要有:
- @Target:定义注解能够应用于哪些目标对象
- @Retention:注解的保留期
- @Repeatable:标记的注解可以多次应用于相同的声明或类型
- @MustBeDocumented:修饰的注解将被文档工具提取到API文档中
4种元注解,相比Java中5种元注解少了@Inherited,在这4种元注解中最常用的是前面两种。比如:
@Target(AnnotationTarget.CLASS) //作用在类上
@Retention(AnnotationRetention.SOURCE) //源码级别
annotation class ApiDoc(val value: String)
下面通过一段代码来具体看下注解的使用:
fun main() {
requestData(ApiImpl())
}
/**
* 自定义注解实现API调用时的请求方法检查
*/
//定义一个枚举类Method,其中两个方法get,post
public enum class Method {
GET,
POST
}
//定义注解接收Method类
@Target(AnnotationTarget.CLASS) //类级别
@Retention(AnnotationRetention.RUNTIME) //运行时可见
annotation class HttpMethod(val method: Method)
//API接口
interface API {
val name: String
val version: String
get() = "1.0.0"
}
//API实现类
@HttpMethod(Method.POST)
class ApiImpl : API {
override val name: String
get() = "/api/getDetail"
}
fun requestData(api: API) {
//通过反射拿到所有注解信息
val annotations = api.javaClass.annotations
//使用find工具方法查找对应的类型,通过as转成HttpMethod
val method = annotations.find { it is HttpMethod } as? HttpMethod
println("通过注解获取接口的请求方式类型为:${method?.method}方式")
}
最后的执行结果为:
七、Kotlin扩展(Extensions)
7.1、认识Kotlin扩展函数
Kotlin扩展函数就是使其可以作为一个类的成员进行调用的函数,它定义在类的外部,可以很方便的扩展一个已经存在的类。
fun main() {
val list = mutableListOf(1, 2, 3)
list.exchange(0, 1)
}
//对List进行扩展,实现两个元素的交换
fun MutableList<Int>.exchange(index1: Int, index2: Int) {
val temp = this[index1]
this[index1] = this[index2]
this[index2] = temp
println("集合元素为:${this}")
}
接着我们对上面的代码进行泛型扩展,让它的通用性更强:
fun main() {
val list1 = mutableListOf(1, 2, 3)
val list2 = mutableListOf("我", "爱", "中", "国")
list1.exchange(0, 1)
list2.exchange(0, 1)
}
//泛型扩展
fun <T> MutableList<T>.exchange(index1: Int, index2: Int) {
val temp = this[index1]
this[index1] = this[index2]
this[index2] = temp
println("集合元素为:${this}")
}
7.2、扩展属性
Kotlin扩展属性就是提供一种可以通过属性语法进行访问的API来进行扩展,但它们不能拥有任何状态,也不能添加额外的字段到现有的Java对象中:
fun main() {
println("我爱中国的最后一个字符是:${"我爱中国".lastChar}")
}
//为String添加一个lastChar属性,用于获取字符串的第一个字符
val String.lastChar: Char get() = this.get(this.length - 1)
7.3、伴生对象扩展
fun main() {
//伴生对象的扩展方法可以直接通过伴生对象所属的类名调用,类似于Java中的静态方法
Printer.print("Hello Kotlin")
}
//为伴生对象添加扩展
class Printer {
//创建伴生对象,暂时空实现
companion object {}
}
//为伴生对象创建print()方法
fun Printer.Companion.print(str: String) {
println(str)
}
7.4、Kotlin的常用内置扩展函数
①、let扩展
let它实际上是一个作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,那么就可以选择let函数;let函数的另一个作用就是可以避免写一些判断null的操作。
//let
fun testLet(str: String?) {
//避免判断null的操作
str?.let {
println(it.length)
}
//限制作用域
str.let {
val s = "s作用域只在let代码块内部"
println(it+s)
}
// s 外部无法访问
}
②、run扩展
run函数只接收一个lambda函数为参数,以闭包形式返回,返回值为最后一行的值或者指定的return的表达式,在run函数中可以直接访问实例的公有属性和方法。
//run
data class Apple(val origin:String,val price:Float,val weight:Float)
fun testRun(apple: Apple){
//使用run来简化调用
apple.run {
//可以直接访问属性,无需apple.origin
println("Apple:$origin,$price,$weight")
}
}
③、apply扩展
apply函数的作用是:调用某对象的apply函数,在函数范围内可以任意调用该对象的任意方法,并返回该对象。
从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply函数返回的是传入对象的本身。
apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。或者动态inflate出一个XML的View的时候需要给View绑定数据也会用到,这种场景非常常见。
fun main() {
testApply()
}
//apply
fun testApply() {
//返回的是ArrayList<String>的实例,很多时候用于初始化操作
ArrayList<String>().apply {
add("我")
add("爱")
add("中")
add("国")
}.let {
println("ArrayList<String>:${it}")
}
}
执行结果为:
OK,到这里关于在Android开发中经常用到的Kotlin相关的知识点我们就介绍的差不多了,更多更详细的Kotlin语言的使用大家可以继续参考官方文档的使用说明:
今天的内容就到这里了,文章有点长,感谢大家的阅读!
觉得还不错的可以加个关注,点点大拇指哦,也可以收藏起来以备不时之需!咱们下一篇再见啦!
祝:工作顺利!