基础语法

  • 函数定义使用关键字 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
    }