基础语法
- 函数定义使用关键字 fun,参数格式为:参数 : 类型
- 表达式作为函数体,返回类型自动推断:
- 函数的变长参数可以用 vararg 关键字进行标识:
- 可变变量定义:var 关键字 . 不可变变量定义:val 关键字,只能赋值一次的变量(类似Java中final修饰的变量)
-
常量与变量都可以没有初始化值,但是在引用前必须初始化
编译器支持自动类型判断,即声明时可以不指定类型,由编译器判断。
-
$varName 表示变量值
${varName.fun()} 表示变量的方法返回值:
-
//类型后面加?表示可为空 var age: String? = "23" //抛出空指针异常 val ages = age!!.toInt() //不做处理返回 null val ages1 = age?.toInt() //age为空返回-1 val ages2 = age?.toInt() ?: -1
-
可以使用 is 运算符检测一个表达式是否某类型的一个实例(类似于Java中的instanceof关键字)。
基本数据类型
- Byte.Short.Int.Long.Float.Double.字符不属于数值类型,是一个独立的数据类型。
- 使用下划线使数字常量更易读:val oneMillion=1_000_000
- 三个等号 === 表示比较对象地址,两个 == 表示比较两个值大小
- 较小类型并不是较大类型的子类型,较小的类型不能隐式转换为较大的类型
- 数组用Array,还有ByteArray, ShortArray, IntArray,省去了装箱的操作,效率更高
- 三个引号 """ 扩起来的字符串,支持多行字符串
- String 可以通过 trimMargin() 方法来删除多余的空白。
条件控制
-
可以把 IF 表达式的结果赋值给一个变量
val max=if(a>b){ a }else{ b }
-
不需要三元操作符
val c=if(condition) a else b
-
用 in 运算符来检测某个数字是否在指定区间内,区间格式为 x..y :
-
when 类似其他语言的 switch 操作符
when(x){ 1->print("x==1") 2->print("x==2") in 3..10->print("x in 3..10") !11->print("x not 11") else->{ print("x else") } }
循环控制
-
for (item in collection) print(item)
for(item:Int in ints){ //... } for(i in array.indices){ //array[i] } for((index,value)in array.withIndex()){ prinln("the element at $index is $value") }
-
while与do...while
-
在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@
loop@ for(i in 1..100){ for(j in 1..100){ if(..){ break@loop } } }
类和对象
-
koltin 中的类可以有一个 主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称之后
-
类也可以有二级构造函数,需要加前缀 constructor:
class Person constructor(firstName: String) {} class Person constructor(firstName: String){ //二级构造函数 constructor(parent:Person,firstName: String):this(firstName){ parent.children.add(this) } }
-
如果一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的构造函数。构造函数是 public 。如果你不想你的类有公共的构造函数,你就得声明一个空的主构造函数:
class DontCreateMe private constructor(){}
-
如果主构造器没有任何注解,也没有任何可见度修饰符,那么constructor关键字可以省略
-
注意如果主构造函数的所有参数都有默认值,编译器会生成一个附加的无参的构造函数,这个构造函数会直接使用默认值
class Customer(val customerName: String = "")
-
Kotlin 中类不能有字段。提供了 Backing Fields(后端变量) 机制,备用字段使用field关键字声明,field 关键词只能用于属性的访问器
var no:Int=100 get()=field set(value){ if(value<10){ field=value }else{ field=-1 } }
-
构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀
class Person constructor(name:String){ init{ println("name is $name") } }
-
内部类使用 inner 关键字来表示,不用inner表示的类里面写的类无法访问外部类的成员
-
类的修饰符classModifier 和_accessModifier_:
- classModifier
abstract // 抽象类 final // 类不可继承,默认属性 enum // 枚举类 open // 类可继承,类默认是final的 annotation // 注解类
- accessModifier
private // 仅在同一个文件中可见 protected // 同一个文件中或子类可见 public // 所有调用的地方都可见 internal // 同一个模块中可见
继承
-
Kotlin 中所有类都继承该 Any 类,它是所有类的超类(Any 不是 java.lang.Object)
equals() hashCode() toString()
-
如果一个类要被继承,可以使用 open 关键字进行修饰
-
如果子类有主构造函数, 则基类必须在主构造函数中立即初始化.如果子类没有主构造函数,则必须在每一个二级构造函数中用 super 关键字初始化基类,或者在代理另一个构造函数。初始化基类时,可以调用基类的不同构造方法
//没有主构造函数 class Student:Persion{ constructor(ctx:Context):super(ctx){ } constructor(ctx:Context,attr:AttributeSet):super(ctx,attr){ } }
-
使用fun声明函数时,此函数默认为final修饰,不能被子类重写。如果允许子类重写该函数,那么就要手动添加 open 修饰
-
属性重写使用 override 关键字,属性必须具有兼容类型,每一个声明的属性都可以通过初始化程序或者getter方法被重写
-
可以用一个var属性重写一个val属性,但是反过来不行。因为val属性本身定义了getter方法,重写为var属性会在衍生类中额外声明一个setter方法
interface Foo { val count: Int } class Bar1(override val count: Int) : Foo class Bar2 : Foo { override var count: Int = 0 }
接口
-
Kotlin 接口与 Java 8 类似,使用 interface 关键字定义接口,允许方法有默认实现:
-
接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性值,实现接口时,必须重写属性
-
实现多个接口时,遇到同一方法继承多个实现的时候
interface A { fun foo() { print("A") } // 已实现 fun bar() // 未实现,没有方法体,是抽象的 } interface B { fun foo() { print("B") } // 已实现 fun bar() { print("bar") } // 已实现 } class C: A, B{ override fun foo(){ super<A>.foo() super<B>.foo() } override fun bar(){ super<B>.bar() } } fun main(args: Array<String>) { val c = C() c.foo(); c.bar(); //ABbar }
扩展
-
Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用 Decorator 模式
扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。
-
扩展函数可以在已有类中添加新的方法
fun receiverType.functionName(params){ body } //receiverType:表示函数的接收者,也就是函数扩展的对象 //functionName:扩展函数的名称 //params:扩展函数的参数,可以为NULL
-
扩展函数是静态解析的,并不是接收者类型的虚拟成员,在调用扩展函数时,具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的,而不是动态的类型决定的
open class C class D: C() fun C.foo() = "c" // 扩展函数 foo fun D.foo() = "d" // 扩展函数 foo fun printFoo(c: C) { println(c.foo()) // 类型是 C 类 } fun main(arg:Array<String>){ printFoo(D())//输出c }
-
若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数
class C { fun foo() { println("成员函数") } } fun C.foo() { println("扩展函数") } fun main(arg:Array<String>){ var c = C() c.foo()//成员函数 }
数据类与密封类
-
Kotlin 可以创建一个只包含数据的类,关键字为 data
数据类需要满足以下条件(保证生成代码的一致性以及有意义): 主构造函数至少包含一个参数。 所有的主构造函数的参数必须标识为val 或者 var ; 数据类不可以声明为 abstract, open, sealed 或者 inner; 数据类不能继承其他类 (但是可以实现接口)。
-
复制使用 copy() 函数,我们可以使用该函数复制对象并修改部分属性
data class User(val name: String, val age: Int) fun main(args: Array<String>) { val jack = User(name = "Jack", age = 1) val olderJack = jack.copy(age = 2) println(jack) println(olderJack) }
-
sealed密封类用来表示受限的类继承结构.当一个值为有限几种的类型, 而不能有任何其他类型时.在某种意义上,他们是枚举类的扩展.但每个枚举常量只存在一个实例,而密封类 的一个子类可以有可包含状态的多个实例.密封类可以有子类,但是所有的子类都必须要内嵌在密封类中。
-
sealed 不能修饰 interface ,abstract class(会报 warning,但是不会出现编译错误)
sealed class Expr data class Const(val number: Double) : Expr() data class Sum(val e1: Expr, val e2: Expr) : Expr() object NotANumber : Expr() fun eval(expr: Expr): Double = when(expr) { is Expr.Const -> expr.number is Expr.Sum -> eval(expr.e1) + eval(expr.e2) Expr.NotANumber -> Double.NaN // 使用 when 表达式时不再需要 `else` 子句,因为我们已经覆盖了所有的情况 }
泛型
class Box<T>(t:T){
var value=t
}
val box :Box<Int>=Box<Int>(1)
或者
val box =Box(1)
-
泛型约束:可以使用泛型约束来设定一个给定参数允许使用的类型
fun <T : Comparable<T>> sort(list: List<T>) { // …… } sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子类型 sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子类型
枚举类
enum class Color{
RED,BLACK,BLUE,GREEN,WHITE
}
//枚举初始化
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
-
使用枚举常量
enum class Color{ RED,BLACK,BLUE,GREEN,WHITE } fun main(args: Array<String>) { var color:Color=Color.BLUE println(Color.values())//以数组的形式,返回枚举值 println(Color.valueOf("RED"))//转换指定 name 为枚举值,若未匹配成功,会抛出IllegalArgumentException println(color.name) println(color.ordinal) }
对象表达式/声明
-
匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的 返回类型或者用作公有属性的类型,那么该函数或属性的实际类型 会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any。在匿名对象 中添加的成员将无法访问。
class C { // 私有函数,所以其返回类型是匿名对象类型 private fun foo() = object { val x: String = "x" } // 公有函数,所以其返回类型是 Any fun publicFoo() = object { val x: String = "x" } fun bar() { val x1 = foo().x // 没问题 val x2 = publicFoo().x // 错误:未能解析的引用“x” } }
-
Kotlin 使用 object 关键字来声明一个对象
-
当对象声明在另一个类的内部时,这个对象并不能通过外部类的实例访问到该对象,而只能通过类名来访问,同样该对象也不能直接访问到外部类的方法和变量
lass Site { var name = "github" object DeskTop{ var url = "www.github.com" fun showName(){ print{"desk legs $name"} // 错误,不能访问到外部类的方法和变量 } } } fun main(args: Array<String>) { var site = Site() site.DeskTop.url // 错误,不能通过外部类的实例访问到该对象 Site.DeskTop.url // 正确 }
-
类内部的对象声明可以用 companion 关键字标记,这样它就与外部类关联在一起,我们就可以直接通过外部类访问到对象的内部元素
class MyClass { companion object Factory { fun create(): MyClass = MyClass() } } val instance = MyClass.create() // 访问到对象的内部元素 ******** //可以省略掉该对象的对象名,然后使用 Companion 替代需要声明的对象名 class MyClass { companion object { } } val x = MyClass.Companion
-
个类里面只能声明一个内部关联对象,即关键字 companion 只能使用一次
-
对象表达式和对象声明之间的语义差异
- 对象表达式是在使用他们的地方立即执行的
- 对象声明是在第一次被访问到时延迟初始化的
- 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配
委托
-
有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理
-
类委托. Derived 声明中,by 子句表示,将 b 保存在 Derived 的对象实例内部,而且编译器将会生成继承自 Base 接口的所有方法, 并将调用转发给 b。
// 创建接口 interface Base { fun print() } // 实现此接口的被委托的类 class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } // 通过关键字 by 建立委托类 class Derived(b: Base) : Base by b fun main(args: Array<String>) { val b = BaseImpl(10) Derived(b).print() // 输出 10 }