Kotlin学习(二):类和接口
类声明
在Kotlin
中,类的声明与Java一样,也使用class
关键字。
class MyClass{
...
}
类声明由类名、类头(指定其类型参数、主构造函数等)以及由花括号包围的类体构成。类头与类体都是可选的; 如果一个类没有类体,可以省略花括号。
构造函数
在Kotlin中,一个类可以有一个主构造函数( primary constructor )及多个次构造函数( secondary constructor )。
主构造函数
主构造函数是类头的一部分,紧跟在类名的后面,参数是可选的。
class Person constructor(name: String ) {
//申明主构造函数
...
}
如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。
class Person(name: String) {
//声明主构造函数
...
}
主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的**初始化块(initializer blocks)**中。
class Person constructor(name: String) {
//申明主构造函数
init {
println("name = [ $name ]")
}
}
对比一下同样的java代码:
class Person{
public Person(String name){
System.out.println("name = [ "+name+" ]");
}
}
要注意的是,主构造函数的参数不仅可以用在 init 块中,还可以用于对类属性的初始化
class Person constructor(name: String) {
//申明主构造函数
var mName: String = name // 初始化成员属性
init {
println("name = [ $name ]")
}
}
var 和 val 关键字也可以用于主构造函数的参数,如果使用 var ,参数对于构造函数来说是变量,可以在构造函数内部修改变量的值,如果构造函数的参数使用 val 声明,参数就变成了常量,在构造函数内部不能修改该参数的值。要注意的是,即使使用 var 声明变 量,在构造函数内部修改参数变量值后,并不会把修改的值传到对象外部。
次构造函数
Kotlin 类除了可以声明一个主构造函数外,还可以声明若干个次构造函数。次构造函数需要在类中声明,前面必须加 constructor 关键字。并且如果类中声明了主构造函数,那么所有次构造函数都需要在声明后面调用主构造函数,或通过另外一个次构造函数间接的调用主构造函数
class Person constructor(var name: String) {
//申明主构造函数
var mName: String = name // 初始化成员属性
var age: Int = 0
var gender: Int = 0
init {
println("name = [ $name ]")
}
// 申明次构造函数(通过 this 直接调用了主构造函数)
constructor(name: String, age: Int) : this(name) {
this.age = age
}
// 申明次构造函数(通过 this 调用了次构造函数,间接的调用了主构造函数)
constructor(name: String, age: Int, gender: Int) : this(name, age) {
this.gender = gender
println("name = [${
name}], age = [${
age}], gender = [${
gender}]")
}
注意:在主构造函数参数中可以使用 var 和 val ,但在次构造函数参数中不能使用 var 和 val 。这就意味着,次构造函数的参数都是只读的,不能在构造器内部改变参数的值。
Kotlin 中的 Singleton 模式
如果没有在类中定义主构造函数, Kotlin 编译器也会自动生成一个没有参数的主构造函数,这一点和 Java 完全一样,只是 Java 并不分主构造函数和次构造函数,在 Java 中都统称为构造函数。无论是自动参数的构造函数,还是按前面的方式定义的构造函数,外部都是可访问的,也就public 类型的,如果要满足特殊需求,如实现单例模式( singleton ),可以使用 private 来声明周构造函数和次构造函数。
class Common private constructor() {
// kotlin 单例模式 伴生对象
// 在Kotlin 中并没有静态类成员的概念,但并不等于不能实现类似于静态类成员的功能
// 陪伴对象(Companion Objects)就是 Kotlin 用来解决这个问题的语法糖。
// 如果在 Kotlin 类中定义对象,那么就称这个对象为该类的陪伴对象。伴生对象要使用
// companion 关键字声明。
companion object {
fun getInstance() = Holder.instance
}
private object Holder {
val instance = Common()
}
创建类的实例
要创建一个类的实例,我们就像普通函数一样调用构造函数:
val invoice = Invoice()
val customer = Customer("Joe Smith")
注意 Kotlin 并没有 new 关键字。当然 ,在Kotlin中,还有一些特殊的类,如 Data 类、嵌套类。这些类的定义和使用方法,在这不做详细赘述。
类成员
Kotlin 中的类可以包含多种成员,除了前面所说的构造函数外,还有属性、函数、嵌套类与内部类等。
属性
声明属性
Kotlin 类中的属性既可以用关键字 var 声明为可变的,也可以用关键字 val 声明为只读的。
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
}
Getter 与 Setter
声明一个属性的完整语法是
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
其初始器(initializer)、getter 和 setter 都是可选的。属性类型如果可以从初始器 (或者从其 getter 返回值,如下文所示)中推断出来,也可以省略。
例如:
var allByDefault: Int? // 错误:需要显式初始化器,隐含默认 getter 和 setter
var initialized = 1 // 类型 Int、默认 getter 和 setter
如果属性是只读的,需要将属性声明为 val,并只添加一个 getter 形式。如果属性是读写的,需要使用 var 声明属性,并添加 getter 和 setter 形式。如果 getter 和setter 中只有一行实现代码,直接用等号(=〉分隔 getter 和代码即可。如果包含多行代码,需要使用{...}处理。
class Customer {
// 只读属性
val name: String
get() = "Bill"
var v: Int = 20
// 读写属性
var value: Int
get() = v
set(value) {
println("value 属性被设置")
v = value
}
//如果可以从 getter 推断出属性类型,则可以省略它
val isEmpty get() = v == 0 // 具有类型 Boolean
}
幕后字段
In Kotlin, a field is only used when needed as part of a property to hold its value in memory. Fields can not be declared directly.(在Kotlin中,字段仅在需要作为属性的一部分在内存中保存其值时使用。不能直接声明字段。)
在上面的例子中,value 属性使用了成员变量 v 来保存属性的值。 Kotlin 为我们提供了更便捷的方式解决这个问题,这就是 field 标识符。在属性的 getter 和 setter 中, 可以将 field 当做成员变量使用,也就是通过 field 读写属性值。
class Customer {
// 只读属性
val name: String
get() = "Bill"
var v: Int = 20
//如果可以从 getter 推断出属性类型,则可以省略它
val isEmpty get() = v == 0 // 具有类型 Boolean
// 读写属性
var value: Int = 0
get() = field //从 field 中读取属性值
set(value) {
println("value 属性被设置")
field = value // 将属性值写入 field 中
}
}
fun main(args: Array<String>) {
var c = Customer()
c.value = 30
println(c.value)
}
// 输出内容:
// value 属性被设置
// 30
延迟初始化属性与变量
一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:属性可以通过依赖注入来初始化, 或者在单元测试的 setup 方法中初始化。 这种情况下,你不能在构造函数内提供一个非空初始器。 但你仍然想在类体中引用该属性时避免空检测。
为处理这种情况,你可以用 lateinit
修饰符标记该属性:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接解引用
}
}
该修饰符只能用于在类体中的属性(不是在主构造函数中声明的 var
属性,并且仅当该属性没有自定义 getter 或 setter 时),而自 Kotlin 1.2 起,也用于顶层属性与局部变量。该属性或变量必须为非空类型,并且不能是原生类型。
在初始化前访问一个 lateinit
属性会抛出一个特定异常,该异常明确标识该属性被访问及它没有初始化的事实。
嵌套类
类可以嵌套在其他类中:
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer.Nested().foo() // == 2
您还可以使用嵌套接口。类和接口的所有组合都是可能的:可以在类中嵌套接口,在接口中嵌套类,在接口中嵌套接口。
interface OuterInterface {
class InnerClass
interface InnerInterface
}
class OuterClass {
class InnerClass
interface InnerInterface
}
标记为 inner 的嵌套类能够访问其外部类的成员。内部类会带有一个对外部类的对象的引用:
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
修饰符
类、对象、接口、构造函数、方法、属性和它们的 setter 都可以有 可见性修饰符。 (getter 总是与属性有着相同的可见性。) 在 Kotlin 中有这四个可见性修饰符:private
、 protected
、 internal
和 public
。 如果没有显式指定修饰符的话,默认可见性是 public
。这四个修饰符的作用如下:
private
:意味着只在这个类内部(包含其所有成员)可见。protected
:和private
类似,但 在子类中也可以访问。internal
: 任何在模块内部类都可以访问。public
: 任何类都可以访问
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 也不可见
}
类的继承
Kotlin 类的继承需要使用冒号( : )," : " 后面需要调用父类的构造函数。Kotlin 和 Java 一样,都是单继承的,也就是说一个类只能由一个父类。Kotlin 类默认是 final 的,默认时类不能继承,需要显示地使用 open 关键字允许继承。
open class Person {
// 需要使用 open 声明 Person 类,才允许其他类继承 Person
protected var mName: String = "Bill"
fun getName(): String {
return mName
}
}
class Student : Person(){
// Student 继承了Person类,现在属性mName和方法getName都可以访问
fun printName(){
println(getName())
}
}
重写方法和属性
在 Kotlin
中,不仅类默认是不可继承的,方法也是如此。因此如果要在子类中重写方法,就需要在父类相应方法前加 open
关键字,而且要在子类重写的方法前加 override
关键字。
open class Person {
// 需要使用 open 声明 Person 类,才允许其他类继承 Person
protected var mName: String = "Bill"
// 只有加 open 关键字,才能被子类重写
open fun getName(): String {
return mName
}
}
class Student : Person(){
// Student 继承了Person类,现在属性mName和方法getName都可以访问
fun printName(){
println(getName())
}
// 重写父类的 getName 方法
override fun getmName(): String {
return "< ${
super.getName()} >"
}
}
属性的重写方式与方法类似,被重写的属性必须使用 open
声明,子类中重写的属性必须用 override
声明。不过要注意的是, val 属性可以被重写为 var 属性,但反过来不可以。
open class Person {
// 需要使用 open 声明 Person 类,才允许其他类继承 Person
protected open val mName: String = "Bill" // 初始化成员属性
get() = field
// 只有加 open 关键字,才能被子类重写
open fun getName(): String {
return mName
}
}
class Student : Person(){
// Student 继承了Person类,现在属性mName和方法getName都可以访问
override var mName: String = "Mike"
get() = field
set(value) {
field = value
}
fun printName(){
println(getName())
}
// 重写父类的 getName 方法
override fun getmName(): String {
return "< ${
super.getName()} >"
}
}
接口
Kotlin 中的接口与 Java 中的接口类似,使用 interface 关键字声明。一个类可以实现多个接口 ,实现的方法和类继承相同。而且,接口中的属性和方法都是 open 的。
interface MyInterface {
// 定义 MyInterface 接口
fun process()
fun getName(): String {
// 可选的方法体
return "Bill"
}
}
class Child : MyInterface {
// 接口里面没有具体实现,必须重写
override fun process() {
println("process")
}
// 接口里面有具体实现,可选重写
override fun getName(): String {
return "Mike"
}
}
抽象类
类以及其中的某些成员可以声明为 abstract。 抽象成员在本类中可以不用实现。 需要注意的是,我们并不需要用 open
标注一个抽象类或者函数——因为这不言而喻。
我们可以用一个抽象成员覆盖一个非抽象的开放成员
open class Polygon {
open fun draw() {
}
}
abstract class Rectangle : Polygon() {
abstract override fun draw()
}