kotlin入门>2-类与对象1

内容:

  • 类与继承
  • 属性与字段
  • 接口

一、类

使用关键字 class 声明类。

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

1
2
3
//类名:Person;
//类头:constructor(firstName: String)
class Person constructor(firstName: String) { /*……*/ }

1、构造函数:

  • 主构造函数:constructor 可省略,但村长注解(如@Inject)或可见性修饰符(如public)时,不可省略;只可有一个。不能包含任何代码

    1
    class Customer public @Inject constructor(name: String) { /*……*/ }
  • 主构造函数中属性声明:可变的(var)或只读的(val)皆可

    1
    2
    //简洁语法,参数初始化
    class Person(val firstName: String, val lastName: String, var age: Int) { /*……*/ }
  • 初始化块:init 关键字,初始化的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println) //①

    init {
    println("First initializer block that prints ${name}") //②
    }

    val secondProperty = "Second property: ${name.length}".also(::println) //③

    init {
    println("Second initializer block that prints ${name.length}") //④
    }
    }

    fun main() {
    InitOrderDemo("hello")
    }

在实例初始化期间,初始化块按照它们出现在类体中的顺序执行,与属性初始化器交织在一起。执行顺序为①②③④

  • 次构造函数:可声明多个
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Person(val name: String) {
    var children: MutableList<Person> = mutableListOf<Person>();
    constructor(name: String, parent: Person) : this(name) {
    parent.children.add(this)
    println("Constructor block") //②
    }

    init {
    println("Init block") //①
    }
    }

委托到同一个类的另一个构造函数用 this 关键字。

初始化块中的代码实际上会成为主构造函数的一部分。 委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块中的代码都会在次构造函数体之前执行。即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块,执行顺序为①②

非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数。构造函数的可见性是 public。可声明为私有构造函数:

1
class DontCreateMe private constructor () { /*……*/ }

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

1
class Customer(val customerName: String = "")
  • 类的实例:如函数一样直接调用,无需new 关键字

  • 类成员:
    构造函数与初始化块
    函数
    属性
    嵌套类与内部类
    对象声明

2、继承

2.1、超类Any

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

    1
    class Example // 从 Any 隐式继承
  • 如需声明一个显式的超类型,请在类头中把超类型放到冒号之后:

    1
    2
    open class Base(p: Int)
    class Derived(p: Int) : Base(p)
  • 构造函数继承:

    • 如果派生类有一个主构造函数,其基类型可以(并且必须) 用基类的主构造函数参数就地初始化。
    • 如果派生类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。 注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:
      1
      2
      3
      4
      class MyView : View {
      constructor(ctx: Context) : super(ctx)
      constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
      }

2.2、覆盖成员

  • 覆盖方法:

    • 可覆盖的成员用open 修改(称之为开放),不写则子类不允许定义同名函数。final类不可用open
    • 覆盖后的成员用override,不写则报错
    • 禁止覆盖用final
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      open class Shape {
      open fun draw() { /*……*/ }
      fun fill() { /*……*/ }
      }

      class Circle() : Shape() {
      override fun draw() { /*……*/ }
      }

      open class Rectangle() : Shape() {
      final override fun draw() { /*……*/ }
      }
  • 覆盖属性:

    • 属性覆盖与方法覆盖类似
    • 在超类中声明然后在派生类中重新声明的属性必须以 override 开头,并且它们必须具有兼容的类型。
    • 可以用一个 var 属性覆盖一个 val 属性,但反之则不行。
      因为一个 val 属性本质上声明了一个 get 方法, 而将其覆盖为 var 只是在子类中额外声明一个 set 方法。(val 修饰的没有 set 方法,var 有)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      interface Shape {
      val vertexCount: Int
      }

      class Rectangle(override val vertexCount: Int = 4) : Shape // 总是有 4 个顶点

      class Polygon : Shape {
      override var vertexCount: Int = 0 // 以后可以设置为任何数
      }

2.3、派生类初始化顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
open class Base(val name: String) {

init { println("Initializing Base") }

open val size: Int =
name.length.also { println("Initializing size in Base: $it") }
}

class Derived(
name: String,
val lastName: String
) : Base(name.capitalize().also { println("Argument for Base: $it") }) {

init { println("Initializing Derived") }

override val size: Int =
(super.size + lastName.length).also { println("Initializing size in Derived: $it") }
}

fun main() {
println("Constructing Derived(\"hello\", \"world\")")
val d = Derived("hello", "world")
}
1
2
3
4
5
6
Constructing Derived("hello", "world")
Argument for Base: Hello
Initializing Base
Initializing size in Base: 5
Initializing Derived
Initializing size in Derived: 10

Tip: 设计一个基类时,应该避免在构造函数、属性初始化器以及 init 块中使用 open 成员。

2.4、调用超类实现

  • super 关键字调用其超类的函数与属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    open class Rectangle {
    open fun draw() { println("Drawing a rectangle") }
    val borderColor: String get() = "black"
    }

    class FilledRectangle : Rectangle() {
    override fun draw() {
    super.draw()
    println("Filling the rectangle")
    }

    val fillColor: String get() = super.borderColor
    }
  • 内部类中访问外部类的超类,可以通过由外部类名限定的 super 关键字来实现:super@Outer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class FilledRectangle: Rectangle() {
    fun draw() { /* …… */ }
    val borderColor: String get() = "black"

    inner class Filler {
    fun fill() { /* …… */ }
    fun drawAndFill() {
    super@FilledRectangle.draw() // 调用 Rectangle 的 draw() 实现
    fill()
    println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // 使用 Rectangle 所实现的 borderColor 的 get()
    }
    }
    }

2.5、覆盖规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
open class Rectangle {
open fun draw() { /* …… */ }
}

interface Polygon {
fun draw() { /* …… */ } // 接口成员默认就是“open”的
}

class Square() : Rectangle(), Polygon {
// 编译器要求覆盖 draw():
override fun draw() {
super<Rectangle>.draw() // 调用 Rectangle.draw()
super<Polygon>.draw() // 调用 Polygon.draw()
}
}

3、抽象类

  • 关键词:abstract
  • 无需open 来标注一个抽象类或函数
  • 抽象成员覆盖一个非抽象的开放成员
1
2
3
4
5
6
7
open class Polygon {
open fun draw() {}
}

abstract class Rectangle : Polygon() {
override abstract fun draw()
}

二、属性与字段

1、属性

  • 声明:var(可变) 或 val(不可变,只读)
  • 引用:名称引用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Address {
    var name: String = "Holmes, Sherlock"
    var street: String = "Baker"
    var city: String = "London"
    var state: String? = null
    var zip: String = "123456"
    }

    fun copyAddress(address: Address): Address {
    val result = Address() // Kotlin 中没有“new”关键字
    result.name = address.name // 将调用访问器
    result.street = address.street
    // ……
    return result
    }

2、Getters 与 Setters

  • 声明属性完整语法:

    1
    2
    3
    var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

    Tip: 其初始器(initializer)、getter 和 setter 都是可选的

  • 自定义的访问器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //Boolean可省略:
    //val isEmpty get() = this.size == 0 // 具有类型 Boolean
    val isEmpty: Boolean //无幕后字段
    get() = this.size == 0

    var stringRepresentation: String
    get() = this.toString()
    set(value) { //value可以任意名称
    setDataFromString(value) // 解析字符串并赋值给其他属性
    }

    如果你需要改变一个访问器的可见性或者对其注解,但是不需要改变默认的实现, 你可以定义访问器而不定义其实现:

    1
    2
    3
    4
    5
    var setterVisibility: String = "abc"
    private set // 此 setter 是私有的并且有默认实现

    var setterWithAnnotation: Any? = null
    @Inject set // 用 Inject 注解此 setter
  • 幕后字段

    • 在 Kotlin 类中不能直接声明字段。
    • field 标识符在访问器中引用幕后字段
      1
      2
      3
      4
      var counter = 0 // 注意:这个初始器直接为幕后字段赋值
      set(value) {
      if (value >= 0) field = value
      }
  • 幕后属性

    1
    2
    3
    4
    5
    6
    7
    8
    private var _table: Map<String, Int>? = null
    public val table: Map<String, Int>
    get() {
    if (_table == null) {
    _table = HashMap() // 类型参数已推断出
    }
    return _table ?: throw AssertionError("Set to null by another thread")
    }

3、编译期常量

  • 修饰符:const,修饰已知值的属性,为编译期常量
  • 要求:

4、延迟初始化属性与变量

属性声明为非空类型必须在构造函数中初始化。属性可以通过依赖注入来初始化, 或者在单元测试的 setup 方法中初始化。 这种情况下,你不能在构造函数内提供一个非空初始器。 但你仍然想在类体中引用该属性时避免空检测。

  • 修饰符:lateinit
    只能用于在类体中的属性
    不是在主构造函数中声明的 var 属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
    subject = TestSubject()
    }

    @Test fun test() {
    subject.method() // 直接解引用
    }
    }

5、委托属性

通过使用委托属性实现为库

三、接口 interface

1、定义

  • 接口可以既包含抽象方法的声明也包含实现。
  • 与抽象类不同的是,接口无法保存状态。
  • 接口可以有属性但必须声明为抽象或提供访问器实现。
  • 一个类或者对象可以实现一个或多个接口。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    interface MyInterface {
    fun bar()
    fun foo() {
    // 可选的方法体
    }
    }

    //实现
    class Child : MyInterface {
    override fun bar() {
    // 方法体
    }
    }

2、属性

  • 接口中声明的属性可以是抽象的
  • 或接口中声明的属性提供访问器的实现。
    在接口中声明的属性不能有幕后字段(backing field),因此接口中声明的访问器不能引用它们。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    interface MyInterface {
    val prop: Int // 抽象的

    val propertyWithImplementation: String
    get() = "foo"

    fun foo() {
    print(prop)
    }
    }

    class Child : MyInterface {
    override val prop: Int = 29
    }

3、接口继承

一个接口可以从其他接口派生,从而既提供基类型成员的实现也声明新的函数与属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Named {
val name: String
}

interface Person : Named {
val firstName: String
val lastName: String

override val name: String get() = "$firstName $lastName"
}

data class Employee(
// 不必实现“name”
override val firstName: String,
override val lastName: String,
val position: Position
) : Person

4、覆盖冲突

实现多个接口时,可能会遇到同一方法继承多个实现的问题。这时,就需要指明如何去实现的多个接口中的相同函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface A {
fun foo() { print("A") }
fun bar()
}

interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}

class C : A {
override fun bar() { print("bar") }
}

class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}

override fun bar() {
super<B>.bar()
}
}

上例中,接口 A 和 B 都定义了方法 foo() 和 bar()。 两者都实现了 foo(), 但是只有 B 实现了 bar() (bar() 在 A 中没有标记为抽象, 因为没有方法体时默认为抽象)。因为 C 是一个实现了 A 的具体类,所以必须要重写 bar() 并实现这个抽象方法。

然而,如果我们从 A 和 B 派生 D,我们需要实现我们从多个接口继承的所有方法,并指明 D 应该如何实现它们。这一规则既适用于继承单个实现(bar())的方法也适用于继承多个实现(foo())的方法。

四、可见性修饰符

1、修饰符:

  • public:声明随处可见,默认可见性
  • private:私有,当前作用域可见
  • internal:模块可见
  • protected:私有+子类可见

类、对象、接口、构造函数、方法、属性和它们的 setter 都可以有 可见性修饰符。

2、修饰符应用

2.1、包 (package) 内

1
2
3
4
5
6
7
8
9
10
// 文件名:example.kt
package foo

private fun foo() { …… } // 在 example.kt 内可见

var arz: Int = 4 //默认为public
public var bar: Int = 5 // 该属性随处可见
private set // setter 只在 example.kt 内可见

internal val baz = 6 // 相同模块内可见

注:要使用另一包中可见的顶层声明,仍需将其导入进来。

2.2、类和接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
}

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

2.3、构造函数

指定类的主构造函数的可见性,需要添加一个显式 constructor 关键字

1
class C private constructor(a: Int) { …… }

这里的构造函数是私有的。默认情况下,所有构造函数都是 public,这实际上等于类可见的地方它就可见(即 一个 internal 类的构造函数只能在相同模块内可见).

2.4、局部声明

局部变量、函数和类不能有可见性修饰符。(???)

2.5、模块

可见性修饰符 internal 意味着该成员只在相同模块内可见。更具体地说, 一个模块是编译在一起的一套 Kotlin 文件:

  • 一个 IntelliJ IDEA 模块;
  • 一个 Maven 项目;
  • 一个 Gradle 源集(例外是 test 源集可以访问 main 的 internal 声明);
  • 一次 <kotlinc> Ant 任务执行所编译的一套文件。
-------------我是有底线的-------------