Kotlin基础小结

Java其实不是编译型语言,java编译之后不是计算机可识别的二进制文件,而是一种特殊的class文件,这种文件只有在Java虚拟机(Android是ART)才能识别,这个虚拟机才是编译器的角色,所以 java是解释型语言。所以Kotlin只要编译成同样规格的class文件,就行了。
Kotlin几乎杜绝了空指针异常。

一、类和继承

1、类

  • 如果一个类没有实体,可以省略花括号:class Empty

2、构造函数

  • 在 Kotlin 中的一个类可以有一个主构造函数和一个或多个次构造函数。主构造函数是类头的一部分:它跟在类名(和可选的类型参数)后:

    class Person constructor(firstName: String) {
    }
    
  • 如果主构造函数没有任何注解或者可见性修饰符,可以省略这个constructor关键字:

    class Person(firstName: String) {
    }
    
  • 主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks)中:

    class Customer(name: String) {
        init {
             logger.info("Customer initialized with value ${name}")
        }
    }
    
  • 注意,主构造的参数可以在初始化块中使用。它们也可以在类体内声明的属性初始化器中使用:

    class Customer(name: String) {
          val customerKey = name.toUpperCase()
    }
    
  • 事实上,声明属性以及从主构造函数初始化属性,Kotlin 有简洁的语法:

    class Person(val firstName: String, val lastName: String, var age: Int) {
         // ……
    }
    
  • 与普通属性一样,主构造函数中声明的属性可以是可变的(var)或只读的(val)。

  • 如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前面:

    class Customer public @Inject constructor(name: String) { …… }
    

3、次构造函数

  • 前缀为constructor

    class Person {
      constructor(parent: Person) {
          parent.children.add(this)
      }
    }
    
  • 如果类有一个主构造函数,每个次构造函数需要委托给主构造函数,可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用this关键字即可:

    class Person(val name: String) {
      constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
      }
    }
    
  • 如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数。构造函数的可见性是 public。如果你不希望你的类有一个公有构造函数,你需要声明一个带有非默认可见性的空的主构造函数:

    class DontCreateMe private constructor () {
    }
    
  • 参数可以直接在构造函数初始化:

    class Customer(val customerName: String = "")
    

4、实例

  • Kotlin并没有new关键字,要创建一个类的实例,我们就像普通函数一样调用构造数

    val invoice = Invoice()
    val customer = Customer("Joe Smith")
    

5、继承

  • 在Kotlin中所有类都有一个共同的超类Any,这对于没有超类型声明的类是默认超类:
    class Example // 从Any隐式继承

  • 继承用冒号:

    open class Base(p: Int)
    class Derived(p: Int) : Base(p)
    
  • 类上的open标注这个类才可以被继承,默认情况下Kotlin中所有的类都是 final。

  • 如果类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。 注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:

    class MyView : View {
      constructor(ctx: Context) : super(ctx)
      constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
    }
    

6、覆盖方法

  • Kotlin 需要显式标注可覆盖的成员(我们称之为开放)和覆盖后的成员:

    open class Base {
      open fun v() {}
      fun nv() {}
     }
    class Derived() : Base() {
       override fun v() {}
     }
    
  • 可覆盖的成员必须用open修饰,覆盖后用override修饰。

  • 标记为override的成员本身是开放的,也就是说,它可以在子类中覆盖。如果你想禁止再次覆盖,使用final关键字:

    open class AnotherDerived() : Base() {
      final override fun v() {}
    }
    

7、覆盖属性

  • 属性覆盖与方法覆盖类似,每个声明的属性可以由具有初始化器的属性或者具有getter 方法的属性覆盖。

    open class Foo {
      open val x: Int get { …… }
    }
    class Bar1 : Foo() {
      override val x: Int = ……
    }
    
  • 你也可以用一个 var 属性覆盖一个 val 属性,但反之则不行。这是允许的,因为一个val 属性本质上声明了一个 getter 方法,而将其覆盖为 var 只是在子类中额外声明一个setter 方法。

8、覆盖规则

  • 在 Kotlin 中,实现继承由下述规则规定:如果一个类从它的直接超类继承相同成员的多个实现, 它必须覆盖这个成员并提供其自己的实现(也许用继承来的其中之一)。 为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super ,如 super<Base> :

    open class A {
      open fun f() { print("A") }
      fun a() { print("a") }
    }
    interface B {
      fun f() { print("B") } // 接口成员默认就是“open”的
      fun b() { print("b") }
    }
    class C() : A(), B {
      // 编译器要求覆盖 f():
      override fun f() {
        super<A>.f() // 调用 A.f()
        super<B>.f() // 调用 B.f()
      }
    }
    

二、属性和字段

1、声明属性

  • 变量用var,常量用val。

  • 延迟初始化属性:
    一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:属性可以通过依赖注入来初始化, 或者在单元测试的 setup 方法中初始化。 这种情况下,你不能在构造函数内提供一个非空初始器。 但你仍然想在类体中引用该属性时避免空检查。
    为处理这种情况,你可以用 lateinit 修饰符标记该属性:

    public class MyTest {
      lateinit var subject: TestSubject
      @SetUp fun setup() {
        subject = TestSubject()
      }
      @Test fun test() {
         subject.method() // 直接解引用
       }
    }
    
  • 该修饰符只能用于在类体中(不是在主构造函数中)声明的 var 属性,并且仅当该属性没有自定义 getter 或 setter 时。该属性必须是非空类型,并且不能是原生类型。在初始化前访问一个 lateinit 属性会抛出一个特定异常,该异常明确标识该属性被访问及它没有初始化的事实。

三、可见性修饰符

1、private

  • 意味着只在这个类内部(包含其所有成员)可见。
    2、protected
  • 和 private 一样 + 在子类中可见。
    3、Internal
  • 能见到类声明的本模块内的任何客户端都可见其internal成员,一个模块是编译在一起的一套 Kotlin 文件。
    4、public
  • 能见到类声明的任何客户端都可见其 public 成员。

四、扩展

1、扩展函数

  • fun 要扩展的类. 函数名[(参数1:类型,参数2:类型)]{}
    代码演示:在Int类扩展sum函数后,任何Int类型的对象都可以调用该函数

    fun Int.sum(a:Int,b:Int) = a + b
    然后调用:2.sum(1,2)
    
  • 需要注意的是,扩展不能真正的修改他们所扩展的类,通过定义一个扩展,你并没有在一个类中插入新成员, 仅仅是可以通过该类型的变量用点表达式去调用这个新函数。

2、扩展属性

  • 扩展属性需要注意点就是:扩展的属性不像原有属性一样自带 get()、set(),需要自己声明,而且必须声明。
    例如:给C类分别添加a,b,c属性。 a属性正常,b属性要么更改为val ,要么添加set()函数。c必须添加get()函数。

    val C.a : Int    //正常
    get() = 1
    var C.b : Int    //必须声明为val
    get() = 1
    val C.c : Int    //必须定义get()
    

3、扩展声明为成员

  • 在一个类内部你可以为另一个类声明扩展。在这样的扩展内部,有多个隐式接收者——其中的对象成员可以无需通过限定符访问。扩展声明所在的类的实例称为分发接收者,扩展方法调用所在的接收者类型的实例称为扩展接收者。

    class D {
      fun bar() { …… }
    }
    class C {
      fun baz() { …… }
      fun D.foo() {
      bar() // 调用 D.bar
      baz() // 调用 C.baz
    }
    fun caller(d: D) {
      d.foo() // 调用扩展函数
     }
    
  • 对于分发接收者和扩展接收者的成员名字冲突的情况,扩展接收者优先。要引用分发接收者的成员你可以使用限定的this语法。

    class C {
      fun D.foo() {
        toString() // 调用 D.toString()
        this@C.toString() // 调用 C.toString()
      }
    }
    

五、数据类

1、数据类

  • 我们经常创建一些只保存数据的类。在这些类中,一些标准函数往往是从数据机械推导而来的。在 Kotlin 中,这叫做 数据类 并标记为 data :

    data class User(val name: String, val age: Int)
    data class User(val name: String = "", val age: Int = 0)  //指定默认值
    

2、复制

  • 在很多情况下,我们需要复制一个对象改变它的一些属性,但其余部分保持不变。 copy()函数就是为此而生成。对于上文的 User 类,其实现会类似下面这样:

    val jack = User(name = "Jack", age = 1)
    val olderJack = jack.copy(age = 2)
    

3、数据类和解构声明

  • 为数据类生成的 Component 函数 使它们可在解构声明中使用:

    val jane = User("Jane", 35)
    val (name, age) = jane
    println("$name, $age years of age") // 输出 "Jane, 35 years of age"
    
  • 解构声明就是把一个对象解构成很多变量

六、密封类

1、密封类

  • 密封类用来表示受限的类继承结构:当一个值为有限集中的类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。

  • 要声明一个密封类,需要在类名前面添加 sealed 修饰符。虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明。

    sealed class Expr {
      data class Const(val number: Double) : Expr()
      data class Sum(val e1: Expr, val e2: Expr) : Expr()
      object NotANumber : Expr()
    }
    
  • 使用密封类的关键好处在于使用 when 表达式 的时候,如果能够验证语句覆盖了所有情况,就不需要为该语句再添加一个else子句了。

    fun eval(expr: Expr): Double = when(expr) {
      is Const -> expr.number
      is Sum -> eval(expr.e1) + eval(expr.e2)
      NotANumber -> Double.NaN
      // 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
    }
    

七、嵌套类

1、嵌套类

  • 类可以嵌套在其他类中

    class Outer {
      private val bar: Int = 1
      class Nested {
        fun foo() = 2
      }
    }
    val demo = Outer.Nested().foo() // == 2
    

2、内部类

  • 类可以标记为inner以便能够访问外部类的成员。内部类会带有一个对外部类的对象的引用:

    class Outer {
      private val bar: Int = 1
      inner class Inner {
        fun foo() = bar
      }
    }
    val demo = Outer().Inner().foo() // == 1
    

3、匿名内部类

  • 使用对象表达式创建匿名内部类实例:

     window.addMouseListener(object: MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
             // ……
        }
       override fun mouseEntered(e: MouseEvent) {
               // ……
      }
    })
    

八、对象

1、伴生对象

  • 与Java或C#不同,Kotli中类没有静态方法,如果你需要写一个可以无需用一个类的实例来调用、但需要访问类内部的函数(例如,工厂方法),你可以把它写成该类内对象声明中的一员。
    类内部的对象声明可以用 companion 关键字标记:

    class MyClass {
      companion object Factory {
         fun create(): MyClass = MyClass()
      }
    }
    val instance = MyClass.create()
    
  • 可以省略伴生对象的名称,在这种情况下将使用名称Companion:

    class MyClass {
       companion object {
       }
      }
    val x = MyClass.Companion
    
  • 即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象
    的实例成员

九、委托

1、类委托

  • 委托模式已经证明是实现继承的一个很好的替代方式, 而 Kotlin 可以零样板代码地原生支持它。 类Derived可以继承一个接口Base,并将其所有共有的方法委托给一个指定的对象:

    interface Base {
      fun print()
    }
    class BaseImpl(val x: Int) : Base {
      override fun print() { print(x) }
    }
    class Derived(b: Base) : Base by b
      fun main(args: Array<String>) {
      val b = BaseImpl(10)
      Derived(b).print() // 输出 10
    }
    
  • 如果我们为Derived添加override fun print() { print("abc") },该程序会输出“abc”而不是“10”。

2、委托属性

  • 语法是:val/var <属性名>: <类型> by <表达式>。在by后面的表达式是该 委托, 因为属性对应的get()(和 set())会被委托给它的getValue()和setValue()方法。 属性的委托不必实现任何的接口,但是需要提供一个getValue()函数(和setValue())——对于var属性)。

    class Example {
      var p: String by Delegate()
    }
    class Delegate {
       operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
       return "$thisRef, thank you for delegating '${property.name}' to me!"
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
       println("$value has been assigned to '${property.name} in $thisRef.'")
     }
    }
    
  • 当我们从委托到一个Delegate实例的p读取时,将调用Delegate中的getValue()函数, 所以它第一个参数是读出p的对象、第二个参数保存了对p自身的描述 (例如你可以取它的名字)。 例如:

    val e = Example()
    println(e.p)
    输出结果:
    Example@33a17727, thank you for delegating‘p’to me!
    

3、延迟属性 Lazy

  • 延迟属性(lazy properties): 其值只在首次访问时计算。

  • lazy() 是接受一个lambda并返回一个Lazy <T>实例的函数,返回的实例可以作为实现延迟属性的委托:第一次调用get()会执行已传递给lazy()的lamda表达式并记录结果,后续调用get()只是返回记录的结果:

    val lazyValue: String by lazy {
      println("computed!")
      "Hello"
    }
    fun main(args: Array<String>) {
      println(lazyValue)
      println(lazyValue)
    }
    这个例子输出:
    computed!
    Hello
    Hello
    

4、可观察属性 Observable

  • 可观察属性(observable properties): 监听器会收到有关此属性变更的通知。

  • Delegates.observable() 接受两个参数:初始值和修改时处理程序(handler)。 每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值和新值:

    import kotlin.properties.Delegates
    class User {
       var name: String by Delegates.observable("<no name>") {
             prop, old, new ->
             println("$old -> $new")
      }
    }
    fun main(args: Array<String>) {
        val user = User()
        user.name = "first"
        user.name = "second"
    }
    这个例子输出:
    <no name> -> first
    first -> second
    

十、空安全

1、可空类型与非空类型

  • Kotlin 的类型系统旨在从我们的代码中消除NullPointerException。

  • 在Kotlin中,类型系统区分一个引用可以容纳null(可空引用)还是不能容纳(非空引用)。 例如,String 类型的常规变量不能容纳null:

    var a: String = "abc"
    a = null // 编译错误
    如果要允许为空,我们可以声明一个变量为可空字符串,写作String?:
    var b: String? = "abc"
    b = null // ok
    再看下面的例子:
    var a: String = "abc"
    var b: String? = "abc"
    val l = a.length  //正常,应为a不会为空
    val l = b.length // 错误:变量“b”可能为空
    

2、安全的调用

  • b?.length,如果b非空,就返回b.length,否则返回null。

  • 如果要只对非空值执行某个操作,安全调用操作符可以与let一起使用:

    val listWithNulls: List<String?> = listOf("A", null)
    for (item in listWithNulls) {
      item?.let { println(it) } // 输出 A 并忽略 null
    }
    

3、Elvis 操作符

  • 当我们有一个可空的引用r时,我们可以说“如果r非空,我使用它;否则使用某个非空的值x”:

    val l = b?.length ?: -1
    如果?:左侧表达式非空,elvis 操作符就返回其左侧表达式,否则返回右侧表达式。 请注意,当且仅当左侧为空时,才会对右侧表达式求值。
    

4、!! 操作符

  • 我们可以写b!!,这会返回一个非空的b值 (例如:在我们例子中的String)或者如果 b为空,就会抛出一个NPE异常:

    val l = b!!.length
    

5、安全的类型转换

  • 如果对象不是目标类型,那么常规类型转换可能会导致 ClassCastException 。 另一个选择是使用安全的类型转换,如果尝试转换不成功则返回 null :

    val aInt: Int? = a as? Int
    

6、可空类型的集合

  • 如果你有一个可空类型元素的集合,并且想要过滤非空元素,你可以使用 filterNotNull 来实现。

    val nullableList: List<Int?> = listOf(1, 2, null, 4)
    val intList: List<Int> = nullableList.filterNotNull()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容