前言
以前android的开发大部分使用java开发,而由于kotlin的推出,公司也开始转向了使用kotlin,所以在这里记录下学习kotlin的笔记.
基本语法
基本类型
在kotlin中没有基本类型,其中一切都是对象,这也导致了其没有自动转换类型的功能,如folat不会自动上转为double之类的,而在java的基础类型之间转换需要调用其中的方法,虽然在本文中,多出地方使用了关键字,但其也是一个对象,只是作者沿用了java的说法.
val i :Int = 7
val d :Double = i.toDouble()//Int对象中的转换方法
val s = "eeeee"//String类型的s
val c = s[0]//kotlin的字符串类型也可以像数组那样字节访问,且可以当做数组来迭代
定义包和导入包
这个与java的使用时一样的
package com.lewis.test
import com.lewis.test.util
定义函数
kotlin中的函数使用fun作为关键字来定义,返回值在函数参数声明后使用":"冒号来定义返回值类型,当然也可以让其自动判断返回类型,返回参数也使用return返回
fun sum(a :Int,b:Int) : Int{
return a+b
}
fun sum(a:Int,b:Int) = a + b//自动推断返回的(a+b)的类型
fun sum(a:Int,b:Int) = { a + b}//这里返回的是a+b的类型,如Int
kt中无返回值(返回无意义的值),也就是java中的void,其使用关键字Unit,当然这个关键字一般是可以省略的,如下两个函数,其返回值是Unit(无返回值)
fun test(){
println("555")
}
fun test() :Unit{
println("555")
}
kotlin的函数可以使用参数默认值,这是java所不支持的,其使用与C++的默认参数类似,但也有点不同
fun defultTest(name :String ,age:Int,sex : String = "Man"){//此处的第三个参数有默认值Man
print(name+sex+age)
}
defultTest("lewis",23)//第三个参数可以省略,其默认为Man,相当于defultTest("lewis",23,"Man")
defultTest("lewis",23,"women")//这里就不使用默认参数,使用women值
这里有个小扩展,在C++中的默认参数一定要放在函数参数列表的后面,否则会报错,但kotlin这样用却不会报错,个人认为将默认参数放在中间这是一个意义不大的事情
fun defultTest(name :String ,sex : String = "Man",age:Int){}
//像这种情况,在调用时因为第三个参数没有默认值,所以一定要传第三个值,所以这里也导致了第二个值一定要传,所以这里,个人认为在实际开发中尽量避免这样的设计
可变长度的参数
在java中可以使用...来表示函数传入的参数长度不固定,而kotlin中也有这功能,但不是...,而是vararg关键字
fun argTest(vararg data:String){
println(data.size)
println(data.toList())//转换为list,并调用toString()输出,直接输出data是一个数组对象
}
局部函数
kotlin中有局部函数,从名字看,就是函数中的函数
fun save(str:String){
fun isEmpty(str:String){//定义一个内部函数
if (str == null){ throw NullpointException("null")}
}
isEmpty(str)//在函数内部直接调用,在外部不可调用
}
局部参数定义
val定义的只能赋值一次,类似于java的final;var定义可变类型,类似java定义的普通变量;两种定义的使用方式基本类型,其不同之处只是在定以后val的参数不可以再次赋值,而var可以
fun main(args : Array<String>){
val a:Int = 1
val b = 2//自动识别b为int类型
val c: Int//定义c,其类型为int,此时的类型不可以省略
c = 3
}
fun main(args:Array<String>){
var d = 5//自动识别d为int
d += 1//运算符和java一样用
}
其中可以使用as关键字类转换类型,父子类的转换或强制转换
var a:Int = 3 as Any as Int//这里是将3转换成Any类型,在转换为Int类型(这里只是作为一个例子,没有任何实际意义)
字符串模板
kt可使用"",在字符串中引用参数或将"{xxx}"xxx的结果转换为字符串
fun stringTest(){
var a = 5
val str = "a is $a"//此时str的字符串赋值为"a is 5"
a = 1
println(str.replace("is","was")+",but now is $a")
println("${str.replace("is","was")},but now is $a")
}
if条件判断
kt中的if与java的用法类似,可以说java怎么用,kt就可以怎么用,但kt有更多的用法,如kt的if是可以用返回值的,在if的代码块的最后一句,如果为一个值,那么他就是if的返回值,这里需要注意的是,如果需要其有返回值,那每一个if条件的代码块的最后一行都需要是有一个同类型的值,否则无返回值
fun ifTest(): Int {
var result = if (3 == 4) 3 else 4
return result
}
fun ifTest2():Int{
return if(3==4)println(3)else 4//这样子if是没有返回值的,且编译器会报错
}
null可空值
在kt中,要是某个值可为null时,需要再声明的类型后面添加?,来表示该应用可以为空;
且在这个参数使用时加上?使用,可在其不为空时才调用,在为空时不调用;
使用!!来保证不为空时才调用,为空时抛出异常;
使用?:来给其在空时的默认值
var a : Int? = null
var a: Int = null//此处会报错
fun getInt():Int?{//这里的返回可以为null
...
var a:String? = null
a?.length()//这里只有在a不为空时才会调用
a!!.length()//这里a为空时会直接抛出异常
a?.length() ?:0//这里在a为空的时候,使用默认值0
a.length()//这里是不能通过编译的,因为a在这里有可能为空
a as? String//这里是a不为空时将a转换为String类型,否者返回null
}
类型检测及自动类型转换
kt中使用is关键字来检测类型,且检测后的分支中可以直接当该类型使用
fun getStringLength(obj:Any):Int?{//Any为任何类型,类似于java的object
if (obj is String){
return obj.length//此分支中,obj的类型为String
}
//出了分支obj还是String
return null
}
for循环
kt中使用in关键字来循环遍历
fun forTest(args:Array<String>){
for(item in args){//遍历其数组中的元素
println(item)//此处不可做删除操作,否则会抛异常
}
for(index in args.indices){//遍历其数组中的下标
println(args[index])//此处也不可做删除操作,删除了数组的大小会变小,在最后就会出现数组越界,所以如果删除了元素就要做相应的保护措施
}
}
区间
kotlin中有使用..来表示区间
if(x in 1..3){//x是否在1到3之间,包括1和3,如果为!in为如果不在1到3的区间
...
}
迭代区间
for(x in 1..5){//包括1和5
println(x)//1 2 3 4 5
}
for(x in 1..5 step 2){//包括1和5,step 2表示迭代时每次迭代的步长为2
println(x)//1 3 5
}
for(x in 1 util 5){//1到5,此处不包括5,一般用于遍历列表
println(x)//1 2 3 4
}
for(x in 100 downTo 1 step 3){//downTo是递减,这里是从100递减到1,步长为3,就是每次-3,包括1和100
println(x)//100 97 94 91...
}
while循环
这个与java 的一样使用
when表达式
类似于java 的swich,且when和if一样是有返回值的,返回值用法也和if的一样
fun whenTest(obj : Any) :String =
when(obj){
1 ->"111"
2,3->"2,3"//多个情况可使用逗号隔开
4..7->"4-7"//使用区间也可以
"asd" ->"aaaa"
is Long -> "long"
!is String -> "Not string"
else -> "unKnown"
}
当然when中可以不传参数,再不传的情况下,其分支条件需要为布尔值
when{
true->"true"
a ==0 || b == 0->"false"
else "null"
}
异常
kotlin的异常与java 的类似,也是使用try-catch-finally,但其和if一样是一个表达式
var result = try{//这里无异常时会返回22,有异常时返回null,且一定会打印change
Integer.parseInt("22")
} catch(e : NumberFormatException){
null
}finally{
println("change")
}
==相等比较
在kotlin中==是使用equals()比较的,是数值相等,而在java中,若对对象使用==,则是比较引用相等
在kotlin中,如果需要比较其引用相等,需要使用===,三等号
类
定义类
class MainActivity{
}
class MainActivity2(name:String,surname:String)//此处的类内无任何操作,所以可以忽略大括号,且(name:String,surnameString)这个就是声明其的构造函数
class MainActivity3(var name:String,var surname:String)//这个和上一个很像,但上一个的类中并没有声明有属性,但这个声明了name和surname两个参数
class MainActivity4(var name:String,var surname:String){
init{
...//这个方法块是在主构造函数的函数体
}
}
属性
构造函数中声明了属性或类中声明的属性,会默认的有setter和getter方法,虽在在使用的时候像是直接在操作属性其实其是调用了对应的get和set方法,当然也可以修改默认的set和get方法
class Test(){
var change0 = 0
var change1 = 1
get() {
return field+1//field是保留值,指向获取的这个参数,这里就是指向change1
}
set(value) {this.change = 2}
//这里修改了change1的get和set的默认方法,对change0和change2没有影响
var change = 2
var change3 = 4
private set//可以将访问其的权限设置为本类使用或其他的
}
构造方法
class Test1(var name:String){//此处括号声名的就是此类的主构造函数,当存在主构造函数时,就不能有从构造函数
}
class Test2{//无主构造函数
constructor(name:String){//从构造函数
this(name,0)//和java一样,可以使用this来调用其他构造函数
}
constructor(name:String,age:Int){}
}
一般来说,要使用主构造函数来实现,然后其他参数提供相应的默认值,来达到类似重载的功能
类继承
kotlin中的类只能继承显示声明为open或abstract的类,其中open是可被重写的意思,abstract是抽象的意思
open class Test1(name: String)
abstract class Test2(surname: String)
向上面两个类的声明才可以被继承,继承时使用":"来继承,而不是java中的extends关键字
class Test3(name: String) : Test1(name){//这里在构建是会默认调用父类Test1(name:String)的构造方法
}
kotlin的方法默认是final,不可重写的,所以,如果需要可以重写就需要显示声名open,或设为abstract抽象方法,而继承后重写一定要使用override关键字,且override关键字默认为open,所以在重写后,若想此方法不可被重写就要显示的使用final,kotlin的final与java的类似
abstract class abTest{
abstract fun one()//子类必须重写
open fun two(){}//子类可以重写,也可以不重写
fun three(){}//子类不可重写
}
接口
接口的实现与继承一样,都是使用":",接口内的方法默认都为open,且无实现体就默认为abstract
kotlin的接口定义也是使用interface关键字,且支持在接口中提供默认实现
无默认方法的,其子类一定要实现这个方法,如有默认实现的,子类可以不是实现,默认使用接口中的是实现
interface Clickable{
fun click()
fun showOff() = println("default")//此方法有默认的实现
}
interface Clickable2{
fun showOff() = println("default2")//此方法有默认的实现
}
class Button : Clickable,Clickable2{
override fun click() = println("click")//override 是重写父类的编辑,在java中是一个注释且可有可无,但在kotlin中,如果是重写就一定要有override
override fun showOff() {//当实现的两个接口都有同一个方法,那子类一定要重写他,否则会报错
super<Clickable>.showOff()//调用指定父类的实现
super<Clickable2>.showOff()
println("Button")
}
}
这里可以看出继承和实现的不同,继承需要加上其构造方法,而实现不用
除了方法之外,kotlin的接口还支持默认属性,且kotlin的接口不存储属性值,只有其子类才会存储
interface ITest{
val name :String//此处的name不可以赋值,且子类实现后必须重写此属性
val age:Int//此处提供了get,其子类可以不重写age
get() = 1
}
class Test4(override val name :String) :ITest
可见性修饰符
在kotlin中有public/internal/protected/private,四种,默认为public,而java中默认为包私有,这和kotlin的不太一样,且kotlin没有包私有修饰符.protected与java的不同,只能是在本类和子类中可见,而kotlin转java是,internal会被转为public
修饰符 || 类成员 || 顶层声名
public(默认) || 所有地方可见 || 所有地方可见
internal || 模块中可见 || 模块中可见
protected || 子类可见 || 无此用法
private || 类中可见 || 文件中可见
data数据类
kotlin中提供了一个数据类,数据类中提供了equals/hashcode/toString方法的重写,还提供了copy方法来复制数据类,声名的关键字为data
data class TestData(val name:String,var age:Int)
enum枚举类
enum class EnumTest(val a:Int,val b:String ){
ONE(1,"1"),TWO(2,"2");//此处定义枚举类的值,若要定义方法,则要使用";"隔开,这里是kotlin中唯一要使用";"的地方
fun printAll() = println("$a,$b")
}
扩展函数
这个就厉害了,可以类以外的一个地方对类方法进行扩展,也可以说是给类添加一个函数,然后在其他地方使用到这个类时,可以直接调用这个方法使用,如对Context添加一个Toast的方法
需要注意的是,扩展函数无法使用被扩展类的protected和private修饰的属性或方法
//Test.kt
fun Context.toast(message :CharSequence){
Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
}
//MainACtivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
toast("nfh")//因为Activity是继承了Context,所以可以直接调用Context中扩展的函数toast
}
}
当然除了可以添加函数,也可以添加属性
public var Context.age: Int//对Context添加属性age,这样所有Context的子类及本身都有这个属性了
get() = age
set(value) { age = value}
需要注意的是,扩展函数不能被子类重写,因为扩展函数会被当做静态类型来处理,而且当扩展函数名和成员函数名一样是会优先使用成员函数名
内部类(嵌套类)
在java中的内部类,如不声名为静态的,其会持有外部类的引用,而声名了静态类型则不持有
而在kotlin中默认是不持有的(相当于静态),如果需要持有外部类引用则需要使用inner关键字
class Outer{
inner class Inner{
fun getOut() : Outer = this@Outer//要使用外部类时,要使用this@去引用
}
}
密封类
关键字sealed,使用sealed声名的类,默认为open,其子类只能在本类中继承在类外不可继承(在kotlin1.1版本改为在文件中)
sealed class Expr{
calss Num():Expr()
class Sum():Expr()
}
kotlin基础之后的
协程
协程有个特点就是轻量级:
协程与线程类似,但是他比线程更加的轻量级,如开启1000个线程和1000个协程去执行一个工作,其中的开启线程就是开了1000个线程,这里是非常消耗资源的,而1000个协程,其中只是开了几个或几十个线程去工作.
协程的使用
使用协程需要导入一些包
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion"//版本用了0.20版本再高点低点问题不大
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesVersion"//这个是android使用的包,这里会多一个使用handle调度协程所工作的线程的方法
kotlin的协程可使用async或launch来开启,两者功能类似,只是async默认是异步的,即不会立即运行,且两者的返回值不同,launch返回一个Job对象,async返回一个Deferred( -一个轻量级、非阻塞的 future),而Deferred内部是继承了Job所以两者之间的差异并不是很大
使用协程时,需要给其指定协程所执行的上下文或环境(也可以说是在哪个线程),而0.20版本中提供了以下的默认环境
//它里面定一个线程池,去处理你的代码块
CommonPool
//无限制,就是当前什么线程就是什么线程。
Unconfined
//安卓开发用的,用android的handler处理你的代码块,构造方法需要提供Handler
HandlerContext
正式使用协程
val fu = async(CommonPool ){
}
println(fu.isCancelled)//fu协程是否取消
val obj = launch(Unconfined){
}
obj.cancel()//取消obj协程
val UI = HandlerContext(Handler(Looper.getMainLooper()), "UI")
launch(UI){//这里就是运行在UI线程中了
}
集合类
kotlin中使用的集合类是java的集合类,虽然如此,kotlin还为其添加了很多便于开发者操作的方法/函数
val list = arrayListOf(1,4,6,46)//这里的list其实就是java的ArrayList
val map = hashMapOf(1 to "one",4 to "four")//这里的map是java的HashMap
中缀调用
这个有一点点像C++中对运算符的重载的感觉,虽然表象上差距挺大的
像集合类中map中的to,这个就是个中缀调用,定义中缀调用函数的关键字是infix,他可以作用在普通函数(需要再一个类中的,顶层函数不可以)和扩展函数中且规定了其函数参数只能有一个,返回值可有可无
infix fun Int.infixTest(other:Int):Int {//这里的定义了一个扩展函数,且支持中缀调用
println(this+other)
return this+other
}
c infixTest 3//中缀调用方式,其会返回c+3的和
c.infixTest(3)//当然这样子也是可以的,效果是一样的
而kotlin中对Any提供了一个扩展函数to,其实现为
infix fun Any.to(other:Any) = Pair(this,other)//Pair是kotlin标准库中的类,其存储了一个键值对,其实也就是两个元素,所以map初始化时可以使用to来定义每一个键值对
字符串处理
kotlin的字符串处理也是使用了java的字符串处理,只不过kotlin还为其添加了很多易用的扩展函数,如获取字符串第一个或最后一个字符的函数
"12.654-A".split(".-".toRegex)//字符串分隔,这里使用Regex类型来解决一些字符问题
"12.654-A".split(".","-")//kotlin支持多个字符的切割
"""/\.asd\'"""//三重冒号也就是左右两边都是""",被三重冒号包围的字符串为纯字符串,无任何的转义序列
类委托
kotlin中提供了类委托的操作,关键字为by,这委托是在实现一个接口时,将实现部分委托给一个属性,使得本类成为一个中转的类
class ListTest<T> (val list : List<T> = ArrayList()) : List<T> by list{这里的ListTest类实现了List,其实现部分委托给了list属性
override fun add(value : T){//重写add方法
println(value)
list.add(value)
}
}
单例
在kotlin中实现单例非常的方便,使用object关键字就可以实现
object Test5{//定义
val data = "data"
fun printAll(){
println(data)
}
}
Test5.printAll()//调用单例
伴生对象
伴生对象有点像java的静态方法,但kotlin中没有static这东西,而代替他的是使用顶层函数,但是顶层函数无法访问类的private成员,所以这是就需要伴生对象了,其关键字为companion,一般会配合object使用
并且伴生对象也支持扩展函数,一般可以声名一个空的伴生对象,留给之后扩展
class A{
companion object{//定义伴生对象
fun bar(){
println("伴生")
}
}
}
A.bar()//调用时,可以直接通过类型名调用
interface ITest{
}
class B{
companion object My : ITest{//定义伴生对象,并给个名字My,且可以实现接口
fun bar(){
println("伴生")
}
}
}
B.My.bar()//调用时通过类名加伴生名字调用
fun ITestTest(test:ITest){//此函数需要传入一个ITest
}
ITestTest(B)//或者B.My
let函数
let函数有点像groovy的闭包,使用let后,可在{}中通过it或自定义为其他参数来使用调用者
var str :String? = "666"
str?.let{//此处会在str不为空的情况下,才调用let中的内容,否者不调用
println(it.length)//此时使用的it其实就是str对象
it.indexOf(1)
}
lateinit 属性延时初始化
在使用lateinit可以声名一个不为空的属性,且声名时不初始化,在之后再初始化,这里的属性需要为var,如果为val的话就必须在构造函数中初始化
private lateinit var data:String
fun setData(s:String){
data = s
}
如果在初始化之前就调用了data,那么就会报错..
by lazy惰性初始化
这个与上面的latinit不太一样,by lazy()是在使用的时候在进行相关的初始化,且默认线程安全
class User(val name:String){
var age by lazy{
loadAge()//此处为初始化的操作
}
}
var user =User("lewis_v")
user.age//在此处调用age时才会进行age的初始化
Nothing类型
Nothing类型作为函数返回值,表示这个函数不会正常结束,也表明这个函数不会返回任何东西
fun fail(msg:String) : Nothing{
throw IllegalStateException(msg)
}
fun fail(msg:String) : Nothing{
return 0//这里报错,不给编译,一定要出错才可以
}
运算符重载
kotlin的运算符重载与C++的类似,其关键字为operator,其可重载二元、一元、复合、比较运算符
二元
二元的有*/%+-运算
其对应的重载方法为
表达式 || 函数名
- || times
/ || div
% || mod
- || plus
- || minus
data class User(var age:Int){
operator fun plus(other:User):User{//重载+号,+号左边的为自身,右边的为other,其他符号与这个一样操作
return Point(age + other.age)
}
}
User(5) + User(5) //此处的结果为User(10)
复合
复合的有+= -= *= /= %=
其重载的方法与其二元运算符差不多,在其基础上加上Assign即可,如+号为plusAssign,-号minusAssign
data class User(var age:Int){
operator fun plusAssign(other:User):User{//重载+=号,+=号左边的为自身,右边的为other,其他符号与这个一样操作
return Point(age + other.age)
}
}
var user = User(5)
user += User(5) //此处的结果user变为User(10)
一元
一元的有++ -- + - !
其对应的重载方法为
表达式 || 函数名
+a || unaryPlus
-a || unaryMinus
!a || not
++a,a++ || inc
--a,a-- || dec
data class User(var age:Int){
operator fun inc():User{//重载++号,一元没有参数传入,只有对自己进行操作,其他符号与这个一样操作
return Point(age + 1)
}
}
var user = User(5)
user ++ //此处的结果user变为User(6)
比较运算符
==为重写equals
其余的> < >= <=之类的比较需要实现Comparable,实现其中的方法compareTo,在比较时,会比较此方法返回数值的大小
小结
以上是前段时间学习kotlin的学习笔记,总的来说,kotlin使用上更加方便简洁,且内部已经提供了很多常用的操作如集合类的操作等等...