在任何一门面向对象的语言编程里,类(class)都是最基础,但是一项非常重要的组成部分。code说万物皆对象,对象生成的也就是类,在kotlin中也是这样。所以今天让我们看看kotlin中的类。
一、类的定义和声明
在kotlin中类可以包含构造器、类初始化代码快、属性、函数、内部类和对象声明
1.1 关键字
类声明的关键字是class,eg:
class TestKotlinClass{
}
其中当类没有结构体的时候,大括号可以省略,eg:
class TestKotlinClass
二、类的属性
2.1 属性的定义
上一节分享我们有讲到了属性是可以用var和val这两个关键字来定义的
- var:用这个var来修饰的变量是可读可写的
- val:用这个val来修饰的变量只能读,不能写
class AttriKotlin {
//val song :Int//Property must be initialized or be abstract
val song :Int = 0
var biao :String = "biao"
}
如上代码所示,在编辑器中当我们在类中定义属性的时候不允许我们不为属性初始化,除非使其抽象。但是在开发过程中我们会遇到先不初始化,在后续的逻辑中再使其初始化,kotlin也考虑到了这一点,提供了延迟初始化的方案kotlin延迟初始化
我们可以像使用普通函数那样使用构造函数创建类实例:
var attriKotlin = AttriKotlin() //kotlin中没有new关键字
要使用一个属性,只要用名称引用它即可
attriKotlin.biao //想要使用该属性使用"."就可以
attriKotlin.song
2.2 getter和setter
属性声明的完整语法
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
由此可以看出声明属性之后都有一个访问器,即getter和setter,访问器是可选的,也可以自定义。
如果属性类型可以从初始化语句或者类的成员函数中推断出来,那就可以省去类型,val不允许设置setter函数,因为它是只读的。
var attriOne:Int? //编译错误,需要一个初始化语句,默认实现了getter和setter
var attriTwo = 0 //推断类型为Int,默认实现了getter和setter
val attriThree = 1 //默认实现了getter
val attriFour:Int? //默认实现了getter,但是必须在构造器中初始化
init{//主构造器的初始化段,init为关键字
attriFour = 0
}
var attriFive:Int? //如果是var的话不能像上面这样声明,只能添加初始化语句或者在主构造器中作为参数传入或使用延迟初始化关键字,eg
class AttriKotlin constructor(var attriFive: Int?){
init {
attriFive = 0
}
}
var nullable: Int? = null //可空变量 "?"是不能省略的,不然就跟普通变量一样了
实例:
class AttriKotlin {
var biao: String = "biao"
get() = field.toUpperCase() // 将变量赋值后转换为大写
set(value) {
field = if (value.isNotEmpty()) value else "null"
}
var age: Int = 100
set(value) {
if (value < 10) { // 如果传入的值小于 10 返回该值
field = value
} else {
field = -1 // 如果传入的值大于等于 10 返回 -1
}
}
private val num: Int = 0
val isBland: Boolean
get() = this.num == 0
var xie: String = ""
get() = "不管如何我就是不会改"
set(value) {
field = if (value.isNotEmpty()) value else "null"
}
}
fun main(args:Array<String>){
var attriKotlin = AttriKotlin()
attriKotlin.biao = "songbiao"
println("name:${attriKotlin.biao}")
attriKotlin.biao = ""
println("name:${attriKotlin.biao}")
attriKotlin.age = 9
println("age:${attriKotlin.age}")
attriKotlin.age = 20
println("age:${attriKotlin.age}")
println("isBland:${attriKotlin.isBland}")
println("xie:${attriKotlin.xie}")
attriKotlin.xie = ""
println("xie:${attriKotlin.xie}")
attriKotlin.xie = "value"
println("xie:${attriKotlin.xie}")
}
//以上例子中都用到了field,field指的是属性本身
输出结果
name:SONGBIAO
name:NULL
age:9
age:-1
isBland:true
xie:不管如何我就是不会改
xie:不管如何我就是不会改
xie:不管如何我就是不会改
从上面例子中我们在访问器中有用到field,field是什么,为什么要用到,让我们来了解下Field
在java中的我们都知道可以定义不赋值的变量,系统会给这个变量默认值。并且可为null,这样在后续的逻辑代码中,我们需要去判空。而kotlin中的可空变量相对于Java来说相对简洁了很多。接下来让我们看看kotlin中的空安全
2.3 编译时常数
开发中我们难免会用到一些静态常量,这些在kotlin中称为编译时常量,可以用关键字const,通常和val一起使用
- 关键字:const
- const的条件(正确使用方式)
1、顶层声明(跳脱方法和类)
2、初始化为String或基本类型的值
3、在object修饰的类中声明,在kotlin中称为对象声明,它相当于Java中一种形式的单例类
4、在伴生对象中声明
例子:
在顶层声明
const val constAttri:String = "顶层声明"
在object修饰的类中声明
object ObjectClass{
const val constAttri:String = "在object对象中声明"
}
在伴生对象中声明
class AttriKotlin {
companion object {
const val constAttri:String = "kotlin"
}
}
调用
println("constAttri:$constAttri")
println("constAttri:${ObjectClass.constAttri}")
println("constAttri:${AttriKotlin.constAttri}")
打印
constAttri:顶层声明
constAttri:在object对象中声明
constAttri:在伴生对象中声明
2.4 番外---接口中的属性
在接口中属性的修饰符只能用public,eg:
interface Ikotlin{
public var name:String
private var age:Int//Abstract property in an interface cannot be private
protected var sex:String//Modifier 'protected' is not applicable inside 'interface
internal var birth:Int//Modifier 'internal' is not applicable inside 'interface'
}
从上面的例子可以看出在接口中使用private、protected、internal修饰符的时候会报错。属性的默认修饰符是public,因此public可以省略不写。
再看
var name:String = "biao"//Property initializers are not allowed in interfaces(接口中不允许属性初始值设定项)
从上面例子中可以看出接口中不允许属性初始化,如此我们好像不能像java那样直接从接口中引用属性,那怎么办列,只能通过定义一个类去实现该接口并重写该属性。eg:
interface Ikotlin{
var name:String
}
class IKotlinImpl:Ikotlin{
override var name: String
get() = "biao"
set(value) {}
}
val IName = IKotlinImpl().name
println("IName:$IName")
三、类的构造函数
类的代码执行循序-->主函数--次函数--其他代码按上到下执行 实现次函数的时候,主函数的代码会走,不管多少个次函数,只走一个(在说明类的构造器时引用)
- 在kotlin中构造函数分为主构造函数和次构造函数,允许多个次构造函数存在
- 关键字constructor
3.1 主构造函数
- 主构造函数是类头的一部分,类名的后面跟上构造函数的关键字以及类型参数。
- 主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀。
例子:
class TestKotlinClass constructor(name:String){
//name使用val修饰的,constructor关键字可以省略
init {
println("name--->$name")
}
}
class TestKotlinClass(name:String){
//name使用val修饰的,constructor关键字可以省略
init {
println("name--->$name")
}
}
属性定义可以在主构造函数中,eg
class Runnan constructor(var one:Int,val two:String){
init {
one = 5
println("one:$one\ntwo:$two")
}
}
fun main(args:Array<String>){
val runnan = Runnan(1, "two")
val one = runnan.one
println("one--$one")
}
结果:
one:5
two:two
one--5
什么时候constructor关键字可以省略呢?
- 在构造函数不具有注释符或者默认的可见性修饰符时,constructor关键字可以省略。
- 默认的可见性修饰符时public。可以省略不写。
想上面的例子中因为构造函数是默认public修饰符且没有注释符,故可以省略,来看看不能省略的例子
class PrivateClass private constructor(name:String){
//用到了private修饰符,故不能省略
}
class AptClass @Inject constructor(num: Int){
//用到了@Inject注解符,故不能省略
}
3.2 次构造函数
3.2.1 kotlin中支持二级构造函数,声明时需要加上constructor关键字作为前缀。
例子:
class MultConst(){
constructor(参数列表){
}
}
3.2.2 如果类有主构造函数,每个次构造函数都要,或直接或间接通过另一个次构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this 关键字。
例子:
class TestKotlinClass constructor(name:String){
init {
println("name--->$name")
}
constructor(name: String,value:Int) : this(name) {
println("name-->$name+===value-->$value")
}
constructor(name: String,value: Int,cn:String) : this(name) {
println("name-->$name&&cn-->$cn&&value-->$value")
}
}
fun main(args:Array<String>){
var testKotlinClass = TestKotlinClass("biao",24,"China")
testKotlinClass.biao = 2
println("了解class-->${testKotlinClass.biao}")
}
结果:
name--->biao
name-->biao&&cn-->China&&value-->24
了解class-->2
说明:二级构造函数中的参数name是委托了主构造函数中的name
由上面例子可以看出当实例化类调用的是二级构造函数的时候,会先走init函数,再走二次函数的代码。
3.2.3 当类的主构造函数都存在默认值时的情况
- 在JVM上,如果类主构造函数的所有参数都具有默认值,编译器将生成一个额外的无参数构造函数,它将使用默认值。 这使得更容易使用Kotlin与诸如Jackson或JPA的库,通过无参数构造函数创建类实例。
- 同理可看出,当类存在主构造函数并且有默认值时,二级构造函数也适用
例子:
class Runnan constructor(var one:Int = 0,val two:String = "two"){
init {
println("one:$one///two:$two")
}
constructor(three:Int) : this(){
println("three:$three")
}
constructor( one:Int = 1, two: String = "two", four:Int = 3):this(one, two){
println("one:$one///two:$two///four:$four")
}
}
fun main(args:Array<String>){
val runnan = Runnan()
val runnan1 = Runnan(1, "二")
val runnan2 = Runnan(3)
val runnan3 = Runnan(1, "二",30)
val one = runnan3.one
println("one--$one")
}
结果:
one:0///two:two
one:1///two:二
one:0///two:two
three:3
one:1///two:二
one:1///two:二///four:30
one--1
当实例化无参构造器的时候,用的是主构造器的默认值