基于Kotlin的Spring Boot微服务实战
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.2 类

本节介绍如何定义Kotlin中的类、属性、接口,以及特殊类、泛型、委托等语法的概念和用法。

3.2.1 类、属性、接口

在Kotlin中,使用关键字class声明类。类声明由类名、类头(指定其类型参数、主构造函数等)及由花括号包围的类体构成,其中类体中包括构造函数、初始化块、函数、属性、嵌套类与内部类、对象声明。类头与类体都是可选的;如果一个类没有类体,可以省略花括号。Kotlin中的一个类可以有一个主构造函数及一个或多个次构造函数。如果主构造函数没有任何注解或者可见性修饰符,可以省略constructor关键字。主构造函数不能包含任何代码。初始化代码可以放到以init关键字作为前缀的初始化块(initializer block)中。

主构造函数中声明的属性可以是可变的(var)或只读的(val),构造函数的属性可以有默认值。类也可以声明前缀为constructor的次构造函数。如果类有一个主构造函数,每个次构造函数需要委托给主构造函数,可以直接委托或者通过其他次构造函数进行间接委托。委托到同一个类的另一个构造函数用this关键字即可。所有初始化块中的代码都会在次构造函数体之前执行。即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块。

要创建一个类的实例,可以像普通函数一样调用构造函数。Kotlin中没有new关键字,Kotlin中的所有类都有一个共同的超类Any,其对没有超类型声明的类来说是默认超类。Any有三个方法:equals()、hashCode()与toString(),因此,为所有Kotlin类都定义了这些方法。

如果类要被继承,需要用open关键字修饰。Kotlin对可覆盖的成员(我们称之为开放)及覆盖后的成员需要进行显式修饰。如果派生类有一个主构造函数,那么其基类类型可以(并且必须)用基类的主构造函数参数就地初始化。如果派生类没有主构造函数,那么每个次构造函数必须使用super关键字初始化其基类类型,或委托给另一个构造函数做到这一点。

Derived.draw()函数必须加上override修饰符,如果没加,编译器将会报错。如果函数没有标注open,那么子类中不允许定义相同签名的函数。将open修饰符添加到final类(即没有open的类)的成员上会不起作用。标记为override的成员本身是开放的,也就是说,它可以在子类中被覆盖。如果你想禁止再次被覆盖,可使用final关键字。

类及其中的某些成员可以被声明为abstract。抽象成员在本类中可以不用实现,可以用一个抽象成员覆盖一个非抽象的开放成员。

Kotlin类中的属性既可以用关键字var声明为可变的,也可以用关键字val声明为只读的。要使用一个属性,只要用名称引用它即可。声明一个属性的完整语法如下:

可以自定义setter、getter方法,举例如下:

常量可以用const修饰。如果要推后初始化属性和变量,可以用lateinit关键字。

Kotlin的接口可以既包含抽象方法的声明也包含具体实现。与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现,可使用关键字interface来定义接口。

类、对象、接口、构造函数、方法、属性和它们的setter都可以有可见性修饰符。Kotlin有四个可见性修饰符:private、protected、internal和public,默认可见性是public。private只在类内部(包含其所有成员)可见;protected在子类中可见。能见到类声明的本模块内的任何客户端都可见其internal成员;能见到类声明的任何客户端都可见其public成员。如果你覆盖一个protected成员并且没有显式指定其可见性,那么该成员还会是protected的。

3.2.2 特殊类

Kotlin中的特殊类有如下几种。

数据类:我们经常创建一些只保存数据的类,用data标记。数据类的主构造函数需要至少有一个参数;所有参数标记为val或var;数据类不能是抽象、开放、密封或者内部的。如果生成的类需要含有一个无参的构造函数,则所有的属性必须指定默认值。

密封类:用来表示受限的类继承结构,即一个值为有限的几种类型且不能有任何其他类型。密封类的子类是包含状态的多个实例。密封类需要在类名前面添加sealed修饰符。所有子类都必须在与密封类自身相同的文件中声明。密封类是抽象的,其构造函数默认是private的。

嵌套类:嵌套在其他类内部的类。标记为inner的嵌套类能够访问其外部类的成员。

枚举类:其最基本的用法是实现类型安全的枚举,每一个枚举都可以初始化。枚举常量还可以声明其带有相应的方法及覆盖了基类方法的匿名类。

3.2.3 泛型

Kotlin中的类也可以有类型参数。Kotlin的类型系统有声明处型变(declaration-site variance)与类型投影(type projection)。

Kotlin用out关键字表示泛型是协变类型的。当一个类C的类型参数T被声明为out时,它就只能出现在C的成员的输出位置,这样,C<Base>可以安全地作为C<Derived>的超类。类C在参数T上是协变的,或者说T是一个协变的类型参数。C是T的生产者,而不是T的消费者。out修饰符也被称为型变注解,并且由于它在类型参数声明处提供,所以我们称之为声明处型变。

与out相反,Kotlin又补充了一个型变注解:in。它使得一个类型参数逆变:只可以被消费而不可以被生产。

将类型参数T声明为out非常方便,但是有些类实际上不能被限制为只返回T。例如Array,该类在T上既不能是协变的也不能是逆变的。

对于如下例子,无法将一个Array<Int>数组复制到Array<Any>数组。Array<T>在T上是不型变的。

我们唯一要确保的是,copy()不会做任何坏事。如果想阻止它写到from,可以用这样的语句:

这就是类型投影。我们所说的from不仅仅是一个数组,还是一个受限制的(投影的)数组,其只可以调用返回类型为类型参数T的方法,如上,这意味着只能调用get()。同理,也可以用in投影一个类型。可以传递一个CharSequence数组或一个Object数组给fill()函数。

3.2.4 委托

委托模式已经被证明是实现继承的一个很好的替代方式。DelagateDerived类可以通过将其所有公有成员委托给指定对象来实现一个接口Delagate。DelagateDerived的超类型列表中的by子句表示b将会在DelagateDerived内部存储,并且编译器将生成转发给b的所有Delagate的方法。编译器会使用override覆盖的实现而不是委托对象中的实现。以这种方式重写的成员不会在委托对象的成员中调用,委托对象的内部成员只能访问自己实现的接口成员。

Kotlin支持委托属性、延迟属性、可观察属性等。语法是:val/var <属性名>: <类型> by <表达式>。by后面的表达式是委托类,这是因为属性对应的get()(与set())会被委托给它的getValue()与setValue()方法。属性的委托不必实现任何接口,但是需要提供一个getValue()函数(与setValue()—对于var属性来说)。例如: