Kotlin学习系列----类和继承,补充? ?: ?. !!符号

相关资料:
Kotlin官网: https://kotlinlang.org/
Kotlin的中文文档: http://www.kotlindoc.cn/
kotlin官方中文翻译文档网址(这个翻译较准确):https://www.kotlincn.net/docs/reference/
知乎上看到的一篇视频教学地址:https://zhuanlan.zhihu.com/p/23101437

先补充上篇Kotlin学习系列—-定义变量及基本属性里出现?与可能会碰到的? ?: ?. !!这些符号,然后我们一起来看看Kotlin的类和继承。

补充:
在Kotlin中申明一个变量,如果类型后面不加?则不能直接给此变量赋值为null,在类型后面加上?就变成了可空类型,而可空类型可以直接赋值为null,其中?.就是当前面的变量!= nuil 时正常调用,如果为null就为null,!!就是当变量为null时,抛出空指针异常。

        var name: String = "pixiaozhi"
        print(name.length)//正常使用即可,因为是非空类型,可以放心使用,不用判断if(name != null)

        var name1: String? = null//可空类型,可以赋值为null
        print(name1.length)//Error:Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

        //JAVA用法
        if(name1 != null){
            print(name1.length)
        }

        //kotlin的用法
        print(name1?.length) //当name1是null时,会输出null
        print(name1!!.length)//当name1是null是报npe(空指针)错误

        //?:操作符,elvis操作符,这个其实和可空类型没啥关系,也不是Java中的三目运算符。
        var nameLen: Int = name?.length ?: 0 //仅仅在左边的表达式(name?.length)结果为空时才会计算?:后面的表达式(0

先了解可见性修饰符

在 Kotlin 中有这四个可见性修饰符:privateprotectedinternalpublic。 如果没有显式指定修饰符的话,默认可见性是 public。

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

注意 对于Java用户:

  • Kotlin 中外部类不能访问内部类的 private 成员。

  • 覆盖一个 protected 成员并且没有显式指定其可见性,该成员还会是 protected 可见性。如下

open class Outer {
    private val a = 1
    protected open val b = 2
    internal val c = 3
    val d = 4  // 默认 public

    protected class Nested {
        public val e: Int = 5
    }
}

class Subclass : Outer() {
    // a 不可见
    // b、c、d 可见
    // Nested 和 e 可见

    override val b = 5   // “b”为 protected,这里我们覆盖掉b并用val声明(没有写可见性修饰符)
}

class Unrelated(o: Outer) {
    // o.a、o.b 不可见
    // o.c 和 o.d 可见(相同模块)
    // Outer.Nested 不可见,Nested::e 也不可见
}

Kotlin 中使用关键字 class 声明类

class ClassName {
   ...
}

类声明由类名、类头(指定其类型参数、主构造函数等)和由大括号包围的类体构成。类头和类体都是可选的; 如果一个类没有类体,可以省略花括号。

class ClassEmpty

构造函数

在 Kotlin 中的一个类可以有一个主构造函数和一个或多个次构造函数

主构造函数是类头的一部分:它跟在类名(和可选的类型参数)后。

class Person constructor(firstName: String) {
}

如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。

class Person(firstName: String) {
}

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

class Age private constructor(a: Int) { …… }

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

class Customer(name: String) {
    init {
        logger.info("Customer initialized with value ${name}")
    }
}

注意,主构造的参数可以在初始化块中使用。它们也可以在类体内声明的属性初始化器中使用:


class Customer(name: String) {
    val customerKey = name.toUpperCase()
}

事实上,声明属性以及从主构造函数初始化属性,Kotlin 有简洁的语法:
Kotlin学习系列----类和继承,补充? ?: ?. !!符号_第1张图片

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

次构造函数

Kotlin能够使用constructor声明多个次要的构造器:

class Person {
    constructor(parent: Person) {
        //...
    }
}

如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可(我知道只看概念有点空洞的感觉,我也是反复读文档好几次才理解过来),直接代码说明:

先创建person类
Kotlin学习系列----类和继承,补充? ?: ?. !!符号_第2张图片
其中次构造器传递参数name,委托给主构造器。

然后在主函数中调用
Kotlin学习系列----类和继承,补充? ?: ?. !!符号_第3张图片

相信前两个打印结果都没什么问题,
第1个主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值,所以打印“姓名:皮小智 年龄:18”。
第2个使用主构造器并赋值,打印“姓名:张三 年龄:23”。
第3个使用次构造函数:我们使用了带有一个参数(name)的构造器,Kotlin就会查找相匹配的构造器,这里与之匹配的是我们写的次构造器。name是我们传递的参数“李四”,age并没有传递,我们次要构造器age的默认参数是20,而主构造器的默认参数是18(当时我是故意这样写,目的是想看看次构造函数里面默认参数能不能覆盖主构造函数里面的),最终打印结果是“姓名:李四 年龄:20”,也就是说次构造函数里面默认参数能覆盖主构造函数默认参数。

注意:

  • Kotlin 并没有 new 关键字
  • 在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值。这使得 Kotlin更易于使用像 Jackson 或者 JPA 这样的通过无参构造函数创建类的实例的库。

继承

在 Kotlin 中所有类都有一个共同的超类 Any,这对于没有超类型声明的类是默认超类:

注意 Any 不是 java.lang.Object;尤其是,它除了 equals()、hashCode()和toString()外没有任何成员


//隐式继承

class Example // 从 Any 隐式继承


//显示继承

/*如果该类有一个主构造函数,其基类型必须用(基类型的)主构造函数参数就地初始化。*/

//创建基类Base
class Base(p: Int)

//创建Derived类继承Base
class Derived(p: Int) : Base(p)

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

class MyView : View {
    constructor(ctx: Context) : super(ctx)

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

覆盖方法

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

open class Base {
    open fun v() {}
    fun nv() {}
}
class Derived() : Base() {
    override fun v() {}
}

Derived.v() 函数上必须加上 override标注。如果没写,编译器将会报错。 如果函数没有标注 open 如 Base.nv(),则子类中不允许定义相同签名的函数, 不论加不加 override。在一个 final 类中(没有用 open 标注的类),开放成员是禁止的。

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

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

覆盖属性

属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以 override 开头,并且它们必须具有兼容的类型。每个声明的属性可以由具有初始化器的属性或者具有 getter 方法的属性覆盖。

open class Foo {
    open val x: Int get() { …… }
}

class Bar1 : Foo() {
    override val x: Int = ……
}

你也可以用一个 var 属性覆盖一个 val 属性,但反之则不行。这是允许的,因为一个 val 属性本质上声明了一个 getter 方法,而将其覆盖为 var 只是在子类中额外声明一个 setter 方法。

请注意,你可以在主构造函数中使用 override 关键字作为属性声明的一部分。

interface Foo {
    val count: Int
}

class Bar1(override val count: Int) : Foo

class Bar2 : Foo {
    override var count: Int = 0
}

覆盖规则

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

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.f() // 调用 A.f()
        super.f() // 调用 B.f()
  }
}

同时继承 A 和 B 没问题,并且 a() 和 b() 也没问题因为 C 只继承了每个函数的一个实现。 但是 f() 由 C 继承了两个实现,所以我们必须在 C 中覆盖 f() 并且提供我们自己的实现来消除歧义。

谢谢大家观看!希望我们能一起进步(^_^)!


为了向别人、向世界证明自己而努力拼搏,而一旦你真的取得了成绩,才会明白:人无须向别人证明什么,只要你能超越自己。

你可能感兴趣的:(Kotlin学习,Kotlin,class,construct,继承,文档)