深入kotlin- 类

Posted 颐和园

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入kotlin- 类相关的知识,希望对你有一定的参考价值。

Kotlin 中定义类有些地方不同于 java。

不需要 public

一个类默认就是 public 的,所以不用显示地声明一个类为 public。

不需要花括号

如果一个类是空实现,可以不需要

class MyClass

主构造方法

Kotlin 规定每个类允许有一个主构造方法和多个次要构造方法。

主构造方法是类名的一部分

主构造方法定义类名之后,用 constructor 关键字声明:

class MyClass contructor(username: String)
	...

这种形式定义的构造方法就是主构造方法。它的声明属于类头的一部分。

constructor 可以省略

如果主构造方法没有任何注解修饰或者访问修饰符,则 constructor 可以省略,这种情况下更像是在定义一个函数而不是类,比如:

class MyClass(username:String)
	...

主构造器中不包含代码

你没看错,kotlin 中主构造方法中没有代码(没有方法体)。那么初始化成员变量怎么办?答案是放在 init 代码块:

class MyClass(username: String)
	init 
		println("init MyClass")
		username = ""
	

init 块中的代码会在主构造器被调用时调用,相当于将主构造方法的方法头和方法体分开定义,方法头放在类头后面,方法体放在类体内(init 块)。

构造参数可以用于 init 和属性初始化

如上面的代码中所示,在 init 块中,可以直接使用主构造方法中定义的参数。

此外,主构造方法参数也可以用于给属性赋初值:

class MyClass(username: String)
	private val username = username 

没有 new 关键字

当实例化对象时,直接调用构造方法即可,没有new 关键字(同 swift)。当你创建 MyClass 实例时,你会发现 init 中的代码被执行,控制台中会打印 init MyClass字样:

var myClass = MyClass("Chris") // 这里会打印 init MyClass

默认构造方法(无参构造方法)

如果一个非抽象类,没有定义任何构造方法,编译器会自动提供一个默认的主构造方法(无参),访问级别 public。

此外,如果主构造方法的所有参数都提供了默认值,比如:

class Person(val username:String = "xxx")

那么编译器会自动创建一个无参构造方法,同时这个无参构造方法会使用所提供的默认值初始化。这样做的目的是为了和一些 Java 框架(比如 Spring)兼容(IoC框架通常使用无参构造方法创建实例)。

次要构造方法

次要构造方法不是必须的,如果有,那么可以有多个。次要构造方法是放在类体内定义的(而非类头)。次要构造方法没有方法名,只有关键字 constructor:

constructor(username: String, age: Int)
	...

kotlin 中的次要构造方法必须直接或间接地调用主构造方法,这点同 swift 一样(参考 swift 中指定构造方法的概念):

constructor(username: String, age: Int):this(username)
	println(username+","+age)
	this.age = age

注意this(username)的写法,这里调用了主构造方法。注意,主构造方法先调用,然后才是次要构造方法体内的代码。

间接调用主构造方法

constructor(username: String, age: Int, address: String):this(username,age)
	this.address = address

这个次要构造方法没有直接调用主构造方法,而是调用了另外一个次要构造方法,这样就相当于间接地调用了主构造方法。

私有的主构造方法

某些情况下,你可能想让一个类不能够从外部实例化,那么你可以让主构造函数变成 private(这时 constructor 不可省略) :

class Person private constructor(username: String)

在定义主构造方法的同时定义属性

Kotlin 提供一种简化的构造方法定义形式,在定义构造参数的同时就对属性进行初始化:

class Person (private val username: String, private val age:Int)

如你所见,类的成员变量或属性现在直接在主构造方法中定义,代替了构造参数。它们和构造参数的区别在于,作用域不同。在主构造方法定义的属性,其中整个类的作用域内有效,而构造参数仅在 init 中有效,或者在定义属性初始化值时有效。

属性

非 optional 属性必须初始化

这样声明一个属性:

private var username: String

IDE 提示属性必须被初始化,要么是一个 abstract 属性。如果你使用 abstract 修饰,则带来两个问题:一,abstract 和 pirvate 不兼容,你必须将 private 去掉,二,类必须是一个 abstract 类。

如果你要对 username 进行初始化,那么必须注意,对于非 optional 属性,你不能赋值为 null(或者你将它改成 optional 的)。

在 init 块中初始化

不需要在声明变量时就初始化,你也可以选择在 init 块中初始化。Kotlin 运行时会自动检测你在 init 中的初始化动作并判定为属性已经初始化:

init
	println(username)
	this.username = username

这样,属性 username 上的警告就会消失。注意,init 块中username引用的是构造参数的 username, this.username引用的才是 username 属性。

get 方法

Kotlin 中 get 方法的定义是极度简化的:

val age
	get() = 20

age 是一个只读属性所以用 val,同时其 get 方法只是一个返回整型的表达式,因此函数使用了简化的赋值形式。age 的类型可以通过 get 的返回值推断,因此无需明确指定。

自动提供的 get/set 方法

以下是一个读写属性的例子:

var address: String = address

在不明确定义 get/set 的情况下,kotlin 编译器自动生成对应的 get/set 方法。

内置变量 field 和 value

如果想在 get 方法中直接访问变量所对应的私有变量的值(backing field),可以用 field 关键字(类似于 O-C 中以 _ 开头的私有变量):

get()
	return field

在 set 方法中,也可以直接对 field 赋值:

set(value)
	field = value

这里,value 并不是 kotlin 的关键字,它只是一个参数名,你可以修改为其它。这点不同于 swift ,swift 中通过 newValue 关键字来表示即将赋给属性的新值。

实际上,默认的 get/set 实现就是上面的样子,同时 IDE 会提示冗余的实现,意思是它们与默认实现相同,建议你删除。

修改可见性

可以仅仅修改 get/set 的访问级别,而不修改默认实现:

private var name:String
	private set
	private get

注意,get 方法的访问级别必须和属性的访问级别相同。

延迟初始化

与 java 不同,kotlin 的属性要求必须明确提供初值,或者修改为 optional。这带来了一些不便。为了解决这个问题,kotlin提供了延迟初始化的概念。

lateinit var name:String

lateinit 关键字告诉 kotlin,不检测该属性的初初始化情况,这个值会在实例化之后进行初始化。但是需要程序员遵守以下规则:

  1. 非空(非optional)类型的属性必须中构造方法中初始化。
  2. 如果是依赖注入或者UnitTest,可以使用 lateinit 关键字修饰该属性。但存在如下限制:
    • 只能用于类体中声明的属性,不能用于主构造器中声明的属性。
    • 该属性不能定义 set/get 方法。
    • 只能用于非空(非 optional)属性,且类型不是原生数据类型(如 Int、Double)。

继承

类默认不可继承

在 java 中除了 Final 修饰的类外,都是可继承的。但是中 kotlin 中,默认所有类都 final 的、不可继承的。如果需要继承某个类,那么需要显式地将这个类标记为 open。

open class Parent(name:String, age: Int)

必须调用父类的构造方法

要继承某个类,使用:关键字(kotlin 中取消了 extend 关键字)。同时需要显式地调用父类构造方法。

父类的构造方法可能有参数,也可能没有参数。

如果没有参数很简单,这样就可以了:

class Child:Parent()

如果父类有参数,那么继承时必须传递参数给父类的构造方法。这有两种传递方法:利用主构造器传参,或者利用次要构造器传参。

利用主构造器传参

定义子类主构造器,利用子类主构造器中的参数,来调用父类的构造方法,。

class Child(name:String, age:Int): Parent(name, age)

利用次要构造器传参

如果一个类没有主构造方法时怎么办?在次要构造器中调用。

如果一类没有主构造方法,同时他又要继承某个类,那么他必须在次要构造方法中调用父类构造方法:

class Child: Parent 
	constructor(name:String, age:Int): super(name, age)
	
	

这里,:super(name, age) 表示调用父类的构造方法。

重写

必须使用 override

kotlin 中,重写时必须使用 override 关键字。这与 java 不同。

默认不可重写

父类中的方法默认是 final 的,不可以被重写。必须标记为 open 才能被子类重写。

这对于属性重写也是一样的。无论方法还是属性的重写,都必须中父类中明确标记为 open。

open 可以被 final 中断

父类的 open 方法或属性被子类重写之后仍然是 open 的,子类的子类仍然可以重写这个方法/属性,但是如果你想终止这种“可重写”的关系延续,那么你可以在子类中,将该方法修饰为 final,这样子类的子类将不能重写该方法/属性:

open class Child:Person()
	final override func name() 
	

在主构造器中 override

在主构造器中重写属性也是可以的:

class Child(override val name: String): Parent()

调用父类实现

通过 super 调用父类的实现。

override fun method()
	super.method()
	...

重写 getter 方法

override val name: String
	get() = super.name+" and child"

var 不能重写为 val

Kotlin 中,属性的可变性可以被子类重写。但是只能是将 val 属性重写为 var 属性(不可变->可变),但反过来不行。换句话说,只能将只读属性重写为读写属性。

以上是关于深入kotlin- 类的主要内容,如果未能解决你的问题,请参考以下文章

深入kotlin- 接口和抽象类

深入kotlin- 接口和抽象类

Kotlin中变量不同于Java: var 对val(KAD 02)

Kotlin基础-枚举类

深入kotlin-数据类和密封类

深入kotlin - 嵌套类和内部类