对于那些只需要保存数据的类型,我们常常需要为其重写toString
、equals
等函数,针对于这种情况下,Kotlin为我们提供了专门的数据类,数据类不仅能像普通类一样使用,并且自带我们需要的额外成员函数,比如打印到输出、比较实例、复制实例等。
//在class前面添加data关键字表示为一个数据类
data class User(val name: String, val age: Int)
数据类声明后,编译器会根据主构造函数中声明的所有属性自动为其生成以下函数:
.equals()
/.hashCode()
.toString()
生成的字符串格式类似于"User(name=John, age=42)"
.componentN()
与按声明顺序自动生成用于解构的函数.copy()
用于对对象进行拷贝fun main() {
var s1 = Student("张三", 18)
println(s1) // Student(name=张三, age=18)
var s2 = Student("张三", 18)
println(s1 == s2) // true
var (name, age) = s1
println("name:$name,age:$age") // name:张三,age:18
}
data class Student(val name: String,val age: Int)
当然,为了确保生成代码的一致性和有效性,数据类必须满足以下要求:
val
或var
。注意:编译器会且只会根据主构造函数中定义的属性生成对应函数,如果有些时候我们不希望某些属性被添加到自动生成的函数中,我们需要手动将其移出主构造函数
data class Person(val name: String) {
var age: Int = 0 //age属性不会被处理
}
//在类前面添加enum表示这是一个枚举类型
enum class LightState {
GREEN, YELLOW, RED //直接在枚举类内部写上所有枚举的名称,一般全部用大写字母命名
}
同样的,枚举类也可以具有成员
//同样可以定义成员变量,但是不能命名为name,因为name拿来返回枚举名称了
enum class LightState(val color: String) {
GREEN("绿灯"), YELLOW("黄灯"), RED("红灯"); //枚举在定义时也必须填写参数,如果后面还要编写成员函数之类的其他内容,还需在末尾添加分号结束
fun isGreen() = this == LightState.GREEN //定义一个函数也是没问题的
}
fun main() {
val state: LightState = LightState.RED //直接得到红灯
println(state.name) //自带name属性,也就是我们编写的枚举名称,这里是RED
}
枚举类型可以用于when
表达式进行判断,因为它的状态是有限的
val state: LightState = LightState.RED
val message: String = when(state) {
LightState.RED -> "禁止通行"
LightState.YELLOW -> "减速通行"
LightState.GREEN -> "正常通行"
}
println(message) //结果为: 禁止通行
有些时候,可能我们并不需要那种通过class
关键字定义的对象,而是以匿名的形式创建一个临时使用的对象,在使用完之后就不再需要了,这种情况完全没有必要定义一个完整的类型,我们可以使用匿名类的形式去编写。
// 使用object关键字声明一个匿名类并创建其对象,可以直接使用变量接收得到的对象
val obj = object {
val name: String = ""
override fun toString(): String = "我是一个匿名类" //匿名类默认继承于Any,可以直接重写其toString
}
println(obj)
可以看到,匿名类除了没名字之外,也可以定义成员,只不过这种匿名类不能定义任何构造函数,因为它是直接创建的,这种写法我们也可以叫做对象表达式。
匿名类不仅可以直接定义,也可以作为某个类的子类定义,或是某个接口的实现
interface Person {
fun chat()
}
fun main() {
val obj: Person = object : Person { //直接实现Person接口
override fun chat() = println("实现的接口 chat")
}
obj.chat() //当做Person的实现类使用
}
特别的,对于只存在一个抽象函数的接口称为函数式接口或单一抽象方法(SAM)接口,函数式接口可以有N个非抽象成员,但是只能有一个抽象成员。对于函数式接口,可以使用我们前面介绍的Lambda表达式来使代码更简洁
fun interface KRunnable { //在接口声明前面添加fun关键字
fun invoke()
}
fun main() {
val runnable = KRunnable { //支持使用Lambda替换
println("我是函数invoke的实现")
}
runnable.invoke()
}
object
关键字除了用于声明匿名类型,也可以用于声明单例类。单例类是什么意思呢?就像它的名字一样,在整个程序中只能存在一个对象,也就是单个实例,不可以创建其他的对象,始终使用的只能是那一个对象。
//声明的一个单例类
object Singleton {
private var name = "你干嘛"
override fun toString() = "我叫$name"
}
fun main() {
//通过类名直接得到此单例类的对象
val singleton = Singleton
//不可以通过构造函数的形式创建对象
println(singleton)
}
object Singleton {
fun test() = println("原神,启动!")
}
fun main() {
Singleton.test() //单例定义的函数直接使用类名即可调用
}
现在我们希望一个类既支持单例类那样的直接调用,又支持像一个普通class那样使用,这时该怎么办呢
使用companion
关键字在内部编写一个伴生对象
class Student(val name: String, val age: Int) {
//使用companion关键字在内部编写一个伴生对象,它同样是单例的
companion object Tools {
//伴生对象定义的函数可以直接通过外部类名调用
fun create(name: String, age: Int) = Student(name, age)
}
}
fun main() {
//现在Student不仅具有对象的函数,还可以通过类名直接调用其伴生对象通过的函数
val student = Student.create("小明", 18)
println(student.toString())
}
有些时候,我们可能会编写一些类给其他人使用,但是我们不希望他们随意继承使用我们提供的类,我们只希望在我们提供的框架内部自己进行使用,这时我们就可以将类或接口设定为密封的。
package com.test
sealed class A //声明密封类很简单,直接添加sealed关键字即可
sealed class B: A() //密封类同一个模块或包中可以随意继承,并且子类也可以是密封的