美文网首页
Kotlin基础小结

Kotlin基础小结

作者: Dane_404 | 来源:发表于2019-03-19 23:21 被阅读0次

    一、类和继承

    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()

    相关文章

      网友评论

          本文标题:Kotlin基础小结

          本文链接:https://www.haomeiwen.com/subject/kcaomqtx.html