Kotlin学习笔记
基本标示
- 定义和包含包和java一样,使用package和import;import进来的类可以用as关键字重命名
- 函数和方法的定义:fun name( [parameters] )[: returnType] { body },参数可以携带默认值(跟C/C++一样)
- 如果fun的返回值很简短,可以写成:fun name( [parameters] )[: returnType] = returnValue
- 如果函数没有返回值,用Unit作为返回类型(不再是void,而且一般省略)
- 变量定义:val/var name[:type][= initValue]
- val定义常量,var定义变量;如果给初值,那么可以省略类型说明
- 注释和java一样
- 在字符串常量(双引号)中可以通过添加前缀$来引用变量和输出代码:$name/{ code },如果要输出$符号,写成:${'$'}
- if-else可以被当做?-:使(即代表会产生左值),而?-:将被弃用,当then或者else逻辑段中有多句语句,则最后一句语句的返回值(或者变量)将被作为if/else的返回值返回
- null不再允许赋值给普通变量,如果一个变量需要存储null,它需要定义为Type?类型
- 类型监测由(var.getClass() == Type.class) 变为(var is Type),用于Any定义的对象,普通有明确类型说明的对象不需要类型判断(注:不等于的时候,感叹号写在is边上,应该是为了更符合人类的语法)
- for-in,不再用传统的for(c1;c2;c3)的写法,如果需要做累加器,可以写成for(i in min..max [step n])的形式;如果是累减,写成:for(i in max downTo min [step n]);当遍历map时,使用:for((key, value) in map);作为可以被for-in循环的集合,需要具备一个iterator方法,返回的对象所属的类具有next和hasNext方法,这三个方法都需要定义为operator;遍历数组的索引可以写成:for(index in array.indices) { ... },如果需要同时访问索引和值,可以写成:for((index, value) in array.withIndex()) { ... }
? for(i in min..max) { }
? for(i in min until max) { }//不包含max
? for(i in min..max step n) { }
? for(i in max downTo min) { } - while一样
- switch-case的多选形式变成了when[(var)] { condition -> code };判断对象可以被省略;code包含多句语句,使用大括号包围;default改为else条件:else -> code;code也可作为返回值返回给when的调用块:var a = when { b is String -> 1 };当有多个条件执行相同的处理逻辑,可以把条件用都好分隔:when(x) { 1,2 -> code }
- min<var<max在一般语言中写成:var>min && var < max,Kotlin做了优化,可以使用关键字in来处理这种范围判断:var [!]in min..max(注:这里变量可以等于min或max)
- 支持lambda语法:setOnClickListener(new OnClickListener() { void onClick() { .. } }) => setOnClickListener({ .. })
- 数据类(其实就是C的structure),写成:data class Type(val prop1: Type, val prop2: Type),提供一组通用的方法
? get/set:set只提供给var属性,val属性只有get
? equals:一样
? hashCode:一样
? copy:fun copy(pam1: T = this.pam1, pam2: T = this.pam2) = DataClass(pam1, pam2)
? component1()..componentN(): - 数据类有一些相关的特性:
? 主构造函数至少包含一个参数
? 数据类不能用abstract、open、sealed或者inner修饰
? 数据类只能选择实现接口,不能实现继承父类(1.1以前的版本) - 数据类使用的特性:
? 属性可以在主构造函数中给出默认值(初始化)
? 数据类实例可以赋值给组合对象来选取需要的属性:val (prop1, prop2) = instance - listOf:组建列表对象,写成:listOf(A, B, C, ...)
- mapOf:组建键值对表,写成:mapOf(k1 to v1, k2 to v2, ...)
? 注:访问和修改元素都使用[index]来表示 - 惰属性:延时初始化,写成:var/val name: Type by lazy { code }(注:惰属性默认是同步的,也就是说在多个线程中,值是一致的)
- 拓展方法:在已有类的技术上添加成员函数,写成:fun Type.name() { ... },需要注意的是,拓展的方法是和类绑定的,不具有多态性(子类实例放进父类引用,父类引用调用拓展函数依旧是父类的拓展函数);成员函数的优先级永远高于拓展方法;除了拓展方法,还可以拓展属性,不过需要注意的是拓展的属性不能占用内存,因为编译器无法得知何时以及怎么初始化这些属性;拓展方法甚至可以作用于内部对象
- 单件:object Name { properties/functions }
- 不为null的速写方式:a?.property/function();和?:搭配,可以用来表示为null时的处理方式:a?.property/function() ?: code(注:code会在变量a为null时执行);也可以使用?:单独判断为null时的处理方式:a ?: code,相当于没有为真执行体的分支判断;当某段逻辑需要在变量a为真时执行,写成:a?.let { ... };也可以和?:配合使用,做分支返回:var result = a?.let { .. } ?: ..
? a?.property/function()
? a?.property/function() ?: elseCode
? a ?: elseCode//参数判断
? a?.let { thenCode }
? a?.let { thenCode } ?: elseCode - try-catch:跟java一样
- 当在同一个对象上多次或者带分支/循环的执行方法,可以使用with关键字,写成:with(a) { fun1() if(c1) { fun2() } fun3() }相当于:a.fun1();if(c1) { a.fun2(); } a.fun3();
- 新java读取资源的方式:val stream = Files.newInputStream(Paths.get("/file.txt")) stream.buffered().reader().use { reader -> println(reader.readText()) }
- 数字类型不再提供宽松的类型转变;并且Char不再作为数字类型
- 数字可以使用_来分隔,提高可读性:1010_10010100_1010
- Type?在类型上,不等于任何类型(连自己都不等)
- 不再有强制类型转化(注:自动类型转化依旧存在:小到大,但在做比较的时候,会比较小的类型拉伸出来的部分,这部分和大类型是不同的,所以会反馈false),使用toType转化数字类型
- 一些操作符的变量(这个没啥用)
? << => shl
? << => shr
? >>> => ushr
? & => and
? | => or
? ^ => xor
? ! => inv - 数组:var/val ary[: Array<Type>][= Array(N, { i -> code })],可以使用NumberType + Array组成特定的数字数组,并可以用numberType + ArrayOf函数初始化,写成:var/val ary: IntArray = intArrayOf(1, 2, 3)
- 字符串支持for-in遍历;可以使用三个引号的区域块,填写带折行的字符串:var/val str = """ Hello World """,使用trimMargin方法可以清除一些标识性的字符
- 循环跳转可以使用break/continue @XXXX => XXXX@(一般不需要)
- 构造函数分为主构造和辅构造,主构造函数跟在类名之后,写为:class Type constructor {body},主构造函数不能包含代码块,如果需要做初始化逻辑,可以写在init块中:init {code}(注:init块中可以引用到主构造函数的参数)
- 辅构造函数定义在类体中,用关键字constructor开头;需要注意的是,辅构造函数必须直接或者间接的调用主构造函数,在函数头之后跟冒号加this调用:constructor(parameters): this(parameters) {...}
? 如果有一个带参数,但所有的参数都有默认参数的构造函数,则编译器会生成一个无参的构造函数调用它 - 创建对象不用new关键字
- 继承:父类用关键字open修饰,因为Kotlin的类默认是final修饰的;如果子类有主构造函数,则必须在这里调用父类的构造函数;如果子类没有主构造函数,则所有的子辅构造函数必须调用父类的构造函数(用super关键字,回头试试用类名)
- 属性和方法如果需要继承和派生,在父类中需要用open修饰,在子类中需要用override修饰;在final类中,禁止用open修饰属性和方法;用override修饰的成员默认是open的,如果需要,要用final禁止其被派生;在子类中不能直接访问父类中的属性,必须在父类中用open修饰,再在子类中用override修饰;因为接口可以定义方法的实体,所以会在子类中出现冲突,通过super<Type>.funcation()来做类限定
- abstract意味着没有实体,因为没有实体,所以肯定会被派生(所以没必要再用open修饰)
- 属性会默认带着get/set方法(val没有set),这也是为什么Type?类型必须给出初值;get/set可以用访问域做限制;可以在get/set块中使用field关键字访问和修改属性实体:var prop = 10 set(value) {field = value}
- 如果属性不要在构造函数中初始化,需要把这个属性修饰为lateinit表示其稍后初始化,只能作用在var属性上(废话)
- 接口和抽象类的区别:接口不保留状态,所有的属性都是抽象的,有实体的方法起到了行为指导的作用(说白了,抽象类是个体抽象,接口是身份抽象)
- get属性的访问域跟随属性本身的访问域
- 访问域
? 在*.kt中 - public:所有地方都可以访问
- private:只在这个kt文件中可以被访问
- internal:在kt文件所在包中可以被访问
- protected:不准修饰在kt文件中
? 在类体中 - public:那都能访问
- private:这个类体中可以被访问
- internal:这个包中可以被访问
- protected:这个类中可以被访问加上子类中可以被访问
- 主构造函数被访问域或者其他说明性关键字修饰时,关键字constructor不能省
- 封装类:将自己的子类和子对象封装在自己体内,所生成的实例可以在某个时间内用指定的子类实例或者子对象所代表:
sealed class A
{
class B: A()
object C: A()
}
val c = A.C
when(c)
{
is B -> ...
C -> ...
}
- 模板:跟java一样,但有些和java不同的特性:
? 在给出常量做初始化的时候,可以省略<Type>说明
? 如果模板类只是产出T,则可以用<out T>进行明确说明;相应的,如果只是消耗T(把T对象作为参数),则可以用<in T>进行说明。这样操作的好处是编译器可以允许模板类的多态性:C<Number> a = C<Double> - 嵌套类和内部类:Nested不需要加说明符,跟外层类是一个级别的;inner需要用关键字inner修饰,是属于外层类的实例,可以访问实例的属性和方法
- 枚举类,用法:
? enum class A { STATUS_1 }
? enum class A { STATUS_1(0x0002) }
? enum class A { STATUS_1{ fun a() = 0x0002 }; fun b() = true }//可以包含方法,内部状态也可以(注:状态和方法之间用分号分隔) - 枚举类的伴随方法:
? valueOf(key: String): EnumClass:返回指定键的值(1.1之后可以用enumValueOf<T>()模板函数)
? values(): Array<EnumClass>:返回所有键(1.1之后可以用enumValues<T>()函数) - 实例对象:object,可以用来实现单件
- 伴随对象:companion object,如果是定义在类中作为静态属性和方法,需要添加companion关键字标示,这样其中定义的属性和方法才可以被类直接调用;如果是没有给出object名的companion,则可以用Companion引用
- 类代理:class Derived(b: Base): Base by b,用Derived创建的实例可以引用所有Base的公开方法(注:在代理类中定义的重载函数优先级要高于引用的级别,也就是说用override在Derived中重载方法a()会覆盖b.a())
- 属性代理:var/val prop: Type by Delegate(),可以在Delegate类中定义getValue/setValue方法,相当于属性的get/set
- 属性变化的代理观察接口:var/val prop: Type by Delegate. observable (initValue) { prop, old, new -> { code } }