Kotlin30分钟快速上手

探讨、补充,纠正。参考来源李刚的《疯狂Kotlin讲义》

一.基本类型
二.运算符
三.流程控制
四.函数和lambda表达式
五.高级函数
六.数组和集合
七.面向对象
八.泛型
九.比较偏的语法

一.基本类型

(1)每种类型都有一种扩展,比如Int还有另外一种Int?,区别在于**定义时后者能为null,**但是调用相关方法时需要?.fun()这样调用,或者强制调用!!.fun()。这一切都是为了最大化避免NPE,?.fun()是变量不为空才会调用,所以一般只有强制调用才会触发NPE。
(2)数值之间的转换,像Int,String这些类型都提供了转换成其他类型比如Long,Float的方法,就是toXXX(),比如Int转Float->toFloat();
(3)定义可变变量:var v : Int = 1,或者var v = 1,编译器会自动进行变量推导,推荐前者;不可变变量是val.
(4)没有Object类型,取而代之的是Any
(5)String类型:①支持索引字符,str[1];②支持嵌入变量或表达式:

val price = 10
var str = "涨了${price}个点"
var str1 = "随机数:${rand.nextInt(10)}, str长度是${str.length}"

二.运算符

(1)区间运算符:闭区间(for(i in 0…5),i从0到5(包含)),半闭区间(for(i in 0 until 5(不包含)) ),反向闭区间(for(i in 5 downTo 0)),区间步长(5 down 0 step 2,步长必须为正数),以上区间范围也可赋值给变量,比如:var v = 0…5,v可以简单看成是LinkedList;
(2)运算符重载:和C++差不多,数值之间的计算比如加减乘除,大小比较等都对应有个方法,比如a+b等价于a.plus(b),a>b等价于a.compareTo(b)>0,a++等价于a.inc(),包括区间运算符a…b其实等价于a.rangeTo(b)。简单示例,重载++ :

// data class代表数据类,像C的struct
data class Data(val x : Int, val y : Int) {
    // 关键字是operator,还可重载双目运算符等。
    operator fun inc() : Data {
        return Data(x + 1, y + 1)
    }
}

三.流程控制

(1)多了一个比较特殊的语法糖,可以直接通过流程控制来赋值一个变量,比如:

// v是一个String或者是个Int
var v = if (b) {
    print("true")
    "这变量单独一行,直接会赋值给v"
} else {
    // 这个100也可以没有,这样这个分支返回的就是一个Any对象
    100
}

注意,函数返回值不能通过这样的方式,还是要用return;
(2)循环语句的while和java一样,for不一样,**没有for(int i = 0; i < 10; i++)**这样了,取而代之的是使用区间遍历:for(i in 0 until 10)
(3)没有了switch语法,取而代之的是when … ->语句:

// param是个Any类型,语句只有一行用->就行了,多行用{},is可要可不要
when(param) {
    is Float -> {
        print("是Float")
    }
    10 - 8 -> print("值是2") 
    in 0..10 -> print("在0到10内")
    'd','e' -> print("字符d或e")
    is Data -> print("是Data对象")
    else -> print("不知道是什么")
}

when语句特性:①分支后的值不要求是常数,可以是任意表达式;②先匹配先执行,执行完就是break了,不会再判断下面的分支,跟switch不一样;③上面举例param是个Any类型,所以可以判断很多类型,如果编译器推导出了类型,比如param = 1,已经推导它是个Int,那分支只有Int相关才行;④什么都没有匹配,走else分支.

四.函数和lambda表达式

(1)语法格式:
fun 函数名() : [返回值类型] { // 代码body}
(2)返回空类型void用Unit代替;
(3)单表达式函数:函数只是返回单个表达式,此时可以省略花括号,在等号(=)后指定函数体:
fun test(x : Int, y : Float) : Float = x * y
(4)命名参数:像python,如test(10, y = 1f),前面x使用位置参数,后面y用命名参数,如果希望调用函数时混合使用命名参数和位置参数,那么命名参数必须位于位置参数之后;
(5)形参默认值:fun test(x : Int = 1) {}
(6)可变参数,使用vararg修饰: fun test(vararg x : Int) {},一个函数最多只能带一个可变参数,如果可变形参在前面,后面的参数要使用命名参数.
(7)内联函数:使用inline修饰,跟C++的一样;
(8)lambda表达式,标准语法:
{(形参列表) ->
// 零到多条可执行语句
}
示例:

var square = { n : Int ->
    n * n    
}
print(square(5)) //输出25

(9)匿名函数的return本质依然是函数,return用于返回函数本身,而lambda表达式的return 用于返回它所在的函数。

五.高阶函数

(1)函数类型就像数据类型一样,既可用于定义变量,也可用作函数形参类型,还可作为函数的返回值类型,像C的函数指针。每个函数的函数类型由函数的形参列表、->和返回值类型组成,比如:
fun foo(a : Int, name : String) : String 这个函数的函数类型为:
(Int, String) -> String,如果是空参数或空返回值:
fun test(),则为:()->Unit或者(),举例:

fun pow(base : Int, expo : Int) : String {}
// 使用::将函数名称赋值给变量
var myfun : (Int, Int)->String = ::pow 

(2)作为函数的形参:
fun test(fn : (Int)->Int) {}
(3)作为函数返回值:
fun test() : (Int)->String {}

六.数组和集合

(1)数组差别比较大,创建基本类型的数组对应都改成用对象BooleanArray,IntArray等;
其他类型的话使用arrayOf(),arrayOfNulls(),emptyArray()方法以及Array(size : Int, init : (Int)->T):

// 创建包含指定元素的数组,相当于java数组的静态初始化,知道元素类型用intArrayOf也是一样
var arr1 = arrayOf(2, 4, 10)
// 相当于new int[10]
var arr2 = IntArray(5)
// 或者使用arrayOfNulls:
var arr3 = arrayOfNulls<Double>
// 使用lambda表达式初始化,相当于new int[]{1, 4, 9}
var intArr4 = IntArray(3, {it * it})

(2)集合,Kotlin 并未真正实现任何集合类,它只是借用了 Java 集合框架原有的集合类,Kotlin 的集合被分成两大类 : 可变集合和不可变集合.
XXXOf在"前面"是不可变集合,"后面"是可变集合,比如listOf(),listOfNotNull(),mapOf(),setOf()都是不可变集合,arrayListOf,hashMapOf(),linkedSetOf()都是可变集合,直接使用java的集合也是可以的,比如直接在kotlin文件ArrayList()创建一个数组也行
map集合好用很多,示例:

var map = mapOf("java" to 89, "kotlin" to 32)
map["spring"] = 90
for (en in map.entries)
for (key in map.keys)
for ((key, value) in map)
print("java" in map) // 输出true

(3)数组和集合常用方法:
=》distinct():去掉数组中的重复元素
=》indices():返回IntRange[0, arr.length);
=》withIndex():返回一个迭代器对象,该对象的所有元素都是IndexValue,即可同时访问索引和元素值:

for((index, value) in arr.withIndex()) {
    print("索引${index},值${value}")
}

=》all(predict:(T)->Boolean),使用lambda表达式判断所有元素是否都满足predict;
=》any(predict:(T)->Boolean),和all相似,只要一个元素满足就返回true;
=》count(predict:(T)->Boolean),和all相似,返回满足predict的个数
=》contentToString():相当于java中的Arrays.toString方法;
=》drop | dropLast(n : Int),去掉数组前面或者后面几个元素;
=》dropWhile | dropLastWhile(predicate:(T)->Boolean),根据predict条件去除元素,直到前面或后面第一个元素不再满足;
=》first | last(predicate:(T)->Boolean),获取前面或后面第一个满足predict的元素,有两个方法对应的是获取索引,indexOfFirst和indexOfLast;
=》filterXX(),findXX(),一系列过滤和查找元素的方法

七.面向对象

(1)类的标准格式:

[修饰符] class 类名 [constructor 主构造器] {
    零个到多个次构造器...
    属性,方法...
}

如果主构造器没有任何注解或修饰符,则可以省略 constructor 关键字。
(2)open 修饰符open是final的反义词,用于修饰一个类、方法或属性,表明类可派生子类,方法或属性可被重写。
(3)Kotlin会为读写属性提供默认的getter、setter方法,为只读属性提供默认的getter 方法;
(4)顶层函数(和类平级)的语法与类中方法的语法的主要区别就在于修饰符:顶层函数不能使用 protected、abstract和final修饰符,但类中的方法可使用 public | protected | internal | private | final | abstract | open 这些修饰符。
(5)kotlin 使用 val定义只读属性,使用var定义读写属性 ,系统会为类的只读属性(val)生成 getter 方法,会为读写属性(var)生成 getter和setter 方法。用“点语法”的时候实际上是调用了setter和getter方法,也可以自己在做一些额外的操作:

class Demo() {
    var full
        get() = full
        set(value) {
            print("invoke")
            full = value
        }
}

这些属性默认都是public和final(不可被继承)
(6)幕后属性:用private修饰属性,Kotlin 不会为幕后属性生成任何 getter、setter方法。因此程序不能直接访问幕后属性,必须由开发者为幕后属性提供 getter、setter方法.
(7)Kotlin 要求所有属性必须由程序员显式初始化一一要么在声明该属性时赋初始值;要么在构造器中对该属性赋初始值,Kotlin提供了 lateinit 修饰符来解决属性的延迟初始化。使用 lateinit 修饰的属性,可以在定义该属性时和在构造器中都不指定初始值.(使用这个容易会有NPE的风险):

lateinit var name : String

限制:①只能修饰可变属性;②所修饰的属性不能有自定义的getter,setter方法;③修饰属性不能是原生属性(java的8种基本类型对应的属性比如Int);④修饰的属性必须制定类型
(8)Kotlin的import语句支持 as 关键字,这样就可以为导入类指定别名(像python的导包),从而解决了导入两个不同包中的同名类的问题。Kotlin取消了Java的默认访问权限(包访 问权限),引入了internal访问控制符(模块访问权限)。Kotlin取消了protected 的包访 问 权限.
(9)Kotlin主构造器的参数能在对象初始化中使用,也能在声明属性的时候使用,主构造器更像Java的初始化块,或者说是对初始化块的增强,Java的初始化块不能传入参数,而Kotlin通过主构造器的设计,允许为初始化块传入参数。对象的初始化块:

class Person(name : String = "狗娃") {
    // 里面可以访问对象属性,初始化块可以有多个,构造器的属性可以有默认值
    init {
        var a = 6
        print("name : ${name}")
    }
}

(10)Kotlin 要求所有的次构造器必须委托调用主构造器,就是要先调用主构造器(执行初始化块中的代码),然后才执行次构造器代码。(如果只定义了一个constructor,那么它就是主构造器,主构造器不是特制跟在类名后面的那些):

class Person(name : String) {
    init {
        //调用了次级构造器之前都会先进来这里
    }
    // 带两个参数的次构造器
    constructor(name : String, age : Int) : this(name){}
}

(11)类的继承:

修饰符 class Sub : Fa{}

Any 类是所有类的父类,有点像Object,但并等价,Any类只有equals()、hashCode()和 toString()这3个方法 。Kotlin的类包括属性默认是不能派生子类的,都是用final修饰的,需要添加修饰符open才可以被继承。
(12)子类继承父类,同样要先调用父类的构造器,可以显示使用super关键字调用,或者先调用this关键字,调用其他构造器,反正最终要调用到父类的构造器。
(13)重写父类方法或属性,使用override修饰符
(14)如果需要在子类中使用super来引用超类型中的成员,则可使用尖括号加超类型
名限定的 super 进行引用。比如继承了一个类Bar,实现了一个接口,它们都有一个同名方法test(Kotlin的interface方法可以有实体),调用时可以强制调用super<Bar>.test(),如只有一个,就不需要尖括号了;
(15)Kotlin 提供了类型检查运算符: is 和!is,用法像java的instance。还有另外一个强制转型符as,不安全,推荐用as?,转型失败,不会发生异常,而是返回null.
(16)Kotlin 的 this 比 Java 的 this 更强大, Kotlin 的 this 支持用“@类名”形式,这种形式限制了该 this 代表哪个类的对象。
(17)Kotlin与Java的一个很大的区别是: Kotlin的final修饰符不能修饰局部变量,open也不能修饰局部变量,使用const修饰可执行“宏替换”的常量,像c++: const val i = 1
(18)使用sealed修饰的类称为密封类,是一种特殊的抽象类,专门用于派生子类。密封类与普通抽象类的区别在于 : 密封类的子类是固定的。密封类的直接子类必须与密封类本身在同 一个文件中,在其他文件中则不能为密封类派生子类,间接子类不受这一限制。
(20)与Java 8相似的是, Kotlin的接口既可包含抽象方法,也可包含非抽象方法,也可多继承,但不能实例。相应的的属性如果没有setter,getter方法,会自动为该属性添加 abstract 修饰符。接口同样支持多继承。
(21)嵌套类(相当于静态内部类):类中定义的类,也可放在类的函数里面;
内部类(非静态内部类):使用inner修饰的嵌套类.
(22) Kotlin取消了 static 修饰符,Kotlin类中的成员除嵌套类之外,全部都是非静态成员,Kotlin 还允许在接口中定义嵌套类,但不允许在接口中定义内部类.
(23)取代匿名内部类:对象表达式,匿名内部类只能指定一个父类型(接口或父类),但对象表达式可指定 0~N 个父类型(接口或父类):

object [: 0~N个父类型] {}
// java是直接new Thread(){},kotlin就变成:
object : Thread() {}

(24)对象声明和单例,对象声明和对象表达式很像,不要混淆了:

object ObjectName[: 0~N个父类型]{}
//这个MyObject直接是一个对象了,有点相当于 val MyObejct = object : Thread {}
//因为不能赋值,也不需要再var,所以这样生成的对象就是一个单例了
object MyObject : Thread{}

对象声明和对象表达式区别:①对象表达式是个表达式,可以被赋值给变量;②对象声明可包含嵌套类,不能包含内部类,而对象表达式反过来…;③对象声明不能定义在函数和方法内
(25)伴生对象和静态成员:companion修饰的对象声明,每个类只能定义一个伴生对象,可直接调用类的伴生对象的成员:

class Demo {
    companion object myObjct : Thread{
        var name = ""
        fun test(){}
    }
}
Demo.test()
print(Demo.name)

伴生对象也可以被扩展。
(26)kotlin的枚举类可以设置抽象方法;
(27)类和属性委托的本质就是将本类需要实现的部分方法委托给其他对象一一相当于借用其他对象的方法作为自己的实现,通过by关键字:

interface TestInterface{
    fun test()
    var name : String
}
class TestImpl : TestInterface {
    override fun test(){ //...}
    override var name = ""
}
// 定义一个类实现TestInterface,委托对象t
class Test2Impl(t : TestImpl) : TestInterface by t
// 另外一种写法,直接生成一个TestImpl作为委托对象
class Test3Impl : TestInterface by TestImpl() {
    // 属性委托
    var name : String by TestImpl()
}

八.泛型

(1)泛型的基本使用跟java差不多,有区别的是泛型参数必须明确指定类型,比如下面是错的:

var a = ArrayList()

还有就是继承泛型类时,子类不能再泛型化;
(2)java的泛型是不支持型变的,java采用通配符来解决这个问题,而kotlin采用安全的型变代替java的通配符,kotlin通配符上限(泛型协变out)意味着从中取出(out)对象是安全的,而传入(in)对象则变得不可靠,下限(泛型逆变in)则反过来,简单来说,Array<in Int>相当于java的泛型下限Array<? super Int>,extends则是out。
(3)举个例子,Collction<? extends Number>等价于Collection<out Number>,从中取出的元素至少是Number的子类,但不知道里面的实际引用是Collction<Int>还是Collection<Float>,往里面添加元素就变得不可靠;
(4)使用in,out来修饰泛型属于声明处型变,使用in修饰,那么泛型只能出现在方法的形参中,而out的话只能出现在方法的返回值中,如果要在形参和返回值中同时使用泛型,那么就不要使用in,out来修饰,这种不带in、out修饰的属于使用处型变,典型的就是Array<T>类;
(5)“*”星号投影是为了处理java的原始类型:

var list : ArrayList<*> = arrayListOf(1, "kotlin")

(6)泛型函数跟java一样,kotlin的泛型都有一个默认的上限: Any?在尖括号中只能指定一个上限,如果需要指定多个,需要使用where语句,泛型类也一样使用:

class A<T> where T : Comparable<T>, T : Cloneable {}

九.比较偏的语法

(1)类型别名,像C的typedef,typealias 类型别名 = 已有类型,如typealias myInt = Int
(2)传入可变参数,用*:

fun test(vararg x : Int, y : Float) {}

val arr = arrayOf(1, 2, 3)
test(*arr, y = 1f)

(3)尾递归函数,当函数将调用自身作为它执行的最后一行代码,且递归调用后没有更多代码时,可使用尾递归语法。尾递归不能在异常处理的try、 catch 、 finally 块中使用,尾递归函数需要使用 tailrec 修饰:

tailrec fun test(n : Int, total : Int = 1) : Int {
    if (n == 1) {
        return total
    } else {
        return test(n - 1, total * n)
    }
}

(4)局部函数:在函数里面定义的函数,对外隐藏,函数内部才能使用,适用于函数内部还有比较复杂的逻辑;
(5)lambda表达式如果只有一个形参,那这个形参可以省略,代码里面用it来代替形参名:
var square : (Int)->Int = {it * it}
(6)lambda表达式有像gradle一样的闭包语法,如果函数的最后一个参数时函数类型,如果传递lambda表达式作为其参数,那么允许在圆括号之外指定lambda表达式:

var list = listOf(1, 2, 3)
var rt = list.dropwhile(){it > 1} // rt 就是[2,3]

(7)lambda表达式有个严重缺陷:不能指定返回值类型,如果编译器推断不了返回值类型,这个时候可以用普通的函数或者匿名函数:

fun(el) : Boolean {
    return Math.abs(el) > 20
}

三个特点:①与普通函数相比,去掉了函数名;②如果系统推断出匿名函数的形参类型,那么匿名函数允许省略形参类型;③跟Lambda很像,能指定返回值是它弥补的一个缺陷;
(8)lambda表达式、匿名函数和局部函数都能捕获上下文中的变量和常量:

fun out() {
    var list = mutableListOf<String>()
    fun inner() {
        list.add("局部函数inner能访问到其上下文的变量list")
    }
}

(9)数组或集合的associateXX方法系列,有点像Rxjava的flatMap方法
图片
(10)类中的方法可独立出来。注意,抽出来的时候,第一个参数都是改函数所属的对象类型,比如下面这个run方法是没有形参的,但同样也要给它一个Dog的形参,表明调用时接受的对象.

var rn : (Cat)->Unit = Cat::run
val d = Cat()
rn(d)

跟C++的一个属性很像,忘了叫什么了…:
(11)componentN方法与解构,Kotlin允许将一个对象的N个属性“解构”给多个变量:

// 将user对象的两个属性分别赋值给n、p两个变量
var (n, p) = user
// 等价于
var n = user.component1()
var p = user.component2()

程序希望将对象解构给几个变量,就需要为该类定义几个componentN()方法,且该方法需要使用 operator修饰,会按照顺序一次赋给变量,如果不需要某个属性,用占位符"_”:

// 构造器使用var修饰的形参,直接就成为了该类的一个属性
class User(var name : String, var pass : String) {
    operator fun component1() { return name}
    operator fun component2() { return pass}
}
// 占位符使用
var (_, pass) = user

像map可以for((key, value) in map)这样遍历,就是用到了解构
(12)使用解构这种语法就可以让函数返回多个值,为了简化解构的实现,Kotlin提供了 一种特殊的类:数据类,用data修饰的class,会自动生成componentN()方法:

data class User(var name : String, var pass : String)

要求:①主构造器至少要一个参数;②主构造器所有参数需要用val或var声明为属性;③不能用abstract,open,sealed修饰。
(13)Kotlin 标准库提供了 Pair 和 Triple 两个数据类,前面的是存两个数据的,后面是存三个数据的。
(14)中缀表示法,用infix修饰的方法有点像双目运算符,只能有一个参数:

class Apples() {
    infix fun add(num : Int) {
    }
}
var a = Apples()
val otherApples = a add Apples()

(15)kotlin的Lambda表达式也能使用解构,前提它的参数是支持解构的类型;
(16)kotlin允许为类扩展方法:

var p = Person()
fun Person.extraFun() { print("Person类的扩展方法") }
p.extraFun()

扩展并没有真正地修改所扩展的类,被扩展的类还是原来的类,没有任何改变。本质就是定义了 一个函数,当程序用对象调用扩展方法, Kotlin在编译时会执行静态解析一一就是根据调用对象、方法名找到扩展函数,转换为函数调用。如果子类和父类都扩展了同一个函数名,子类扩展的那个方法不需要使用override关键字。
(17)如果一个类包含了具有相同签名的成员方法和扩展方法,当程序调用这个方法时,系统总是会执行成员方法,而不会执行扩展方法。查找顺序:①本身是否定义;②是否扩展了
(18)除了扩展函数,还能扩展匿名函数,还能扩展属性,扩展属性不能有初始值,必须提供setter,getter方法;
(19)Kotlin提供了一个lazy()函数,该函数接受一个Lambda表达式作为参数,并返回一个Lazy对象 。Lazy对象只能作为只读属性的委托对象.Lazy的getValue()方法的处理逻辑是:第一次调用该方法时,程序会计算 Lambda 表达式,并得到其返回值,以后程序再次调用该方法时,不再计算 Lambda 表达式,而是直接使用第 一 次计算得到的返回值 。Lazy对象只能作为只读属性的委托对象:

val lazyString : String by Lazy {
    print("第一次调用")
    "狗娃"
}
print(lazyString)
print(lazyString) // 这次不会再调用lazy里面的代码了

(20)还有属性监听的语法。

猜你喜欢

转载自blog.csdn.net/aa642531/article/details/88379968