和很多现代高级语言一样,Kotlin也是面向对象的。
类与对象
Kotlin的类基本语法为:
class Person {
var name = ""
var age = 0
fun eat() {
println(name + " is eating. He is " + age + " years old.")
}
}
这样就可以对类进行实例化并调用:
val p = Person()
val age=p.age
这里还有一个语法糖,在使用Kotlin时,我们不需要像Java那样定义 get/set 方法,在调用对象的属性时也不需要调用get/set方法来使用属性。使用Kotlin时,我们是直接对属性进行赋值和读取,因为Kotlin会自动将赋值、读取语句转换成get/set方法,方便开发者使用。
继承与构造函数
Kotlin进行类的继承流程与Java有着不小的差距。首先,想要一个类被继承,首先需要这个类可以被继承。这就是Kotlin不同的地方,在Kotlin中任何一个非抽象类默认都是不可以被继承的,相当于Java中给类声明了final关键字。因此,如果我们需要一个类可以被继承,需要在类前添加 修饰符”open“:
open class Person {
…
}
加上open关键字之后,我们就是在主动告诉Kotlin编译器,Person这个类是专门为继承而设计的,这样Person类就允许被继承了。
之后,我们需要让子类去继承父类。
在Java中继承的关键字是extends,而在Kotlin中变成了一个冒号,写法如下:
class Student : Person() {
var sno = ""
var grade = 0
}
与Java中不同,Kotlin中继承需要在继承类后面加上一对括号,为什么需要加上括号,这就涉及了Kotlin主构造函数和次构造函数的概念。
主构造函数是我们最常用的构造函数,每个类默认都会有一个不带参数的主构造函数,当然也可以显式地给它指明参数。主构造函数的特点是没有函数体,直接定义在类名的后面即可。比如下面这种写法:
class Student(val sno: String, val grade: Int) : Person() {
}
这里我们将学号和年级这两个字段都放到了主构造函数当中,这就表明在对Student类进行实例化的时候,必须传入构造函数中要求的所有参数。比如:
val student = Student("a123", 5)
这样我们就创建了一个Student的对象,同时指定该学生的学号是a123,年级是5。另外,由于构造函数中的参数是在创建实例的时候传入的,不像之前的写法那样还得重新赋值,因此我们可以将参数全部声明成val。
当然,主构造函数没有函数体,如果我们需要在主构造函数里编写一些逻辑,可以使用init结构体,所有主构造函数中的逻辑都可以写在里面:
class Student(val sno: String, val grade: Int) : Person() {
init {
println("sno is " + sno)
println("grade is " + grade)
}
}
这里,我们在创建实例之后一定会创建一个带有初始值的对象,且打印这些值。
但是这和那对括号又有什么关系呢?这就涉及了Java继承特性中的一个规定,子类中的构造函数必须调用父类中的构造函数,这个规定在Kotlin中也要遵守。
根据继承特性的规定,子类的构造函数必须调用父类的构造函数,可是主构造函数并没有函数体。Kotlin当然没有采用这种设计,而是用了另外一种简单但是可能不太好理解的设计方式:括号。子类的主构造函数调用父类中的哪个构造函数,在继承的时候通过括号来指定。
因此,如果我们在父类主构造函数上定义参数,在继承时也要在括号里定义。
class Student(val sno: String, val grade: Int, name: String, age: Int) :
Person(name, age) {
…
}
之后我们就可以来创建实例:
val student = Student("a123", 5, "Jack", 19)
之后,我们需要了解次构造函数,任何一个类只能有一个主构造函数,但是可以有多个次构造函数。次构造函数也可以用于实例化一个类,这一点和主构造函数没有什么不同,只不过它是有函数体的。
Kotlin规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)。这里我通过一个具体的例子就能简单阐明,代码如下:
class Student(val sno: String, val grade: Int, name: String, age: Int) :
Person(name, age) {
constructor(name: String, age: Int) : this("", 0, name, age) {
}
constructor() : this("", 0) {
}
}
次构造函数是通过constructor关键字来定义的,这里我们定义了两个次构造函数:第一个次构造函数接收name和age参数,然后它又通过this关键字调用了主构造函数,并将sno和grade这两个参数赋值成初始值;第二个次构造函数不接收任何参数,它通过this关键字调用了我们刚才定义的第一个次构造函数,并将name和age参数也赋值成初始值,由于第二个次构造函数间接调用了主构造函数,因此这仍然是合法的。
这样,我们就拥有了三种方法来实例化类:
val student1 = Student()
val student2 = Student("Jack", 19)
val student3 = Student("a123", 5, "Jack", 19)
接下来我们就再来看一种非常特殊的情况:类中只有次构造函数,没有主构造函数。这种情况真的十分少见,但在Kotlin中是允许的。当一个类没有显式地定义主构造函数且定义了次构造函数时,它就是没有主构造函数的。我们结合代码来看一下:
class Student : Person {
constructor(name: String, age: Int) : super(name, age) {
}
}
这里因为Student后面没有显示地定义主构造函数,同时又因为定义了次构造函数,所以现在Student类是没有主构造函数的。因为没有,所以继承Person时不需要加上括号。
另外,由于没有主构造函数,次构造函数只能直接调用父类的构造函数,上述代码也是将this关键字换成了super关键字,这部分就和Java很像了。
接口
Kotlin的接口和Java的很像,使用interface修饰,函数不要求有函数体:
interface Study {
fun readBooks()
fun doHomework()
}
这样就可以去实现接口了:
class Student(name: String, age: Int) : Person(name, age), Study {
override fun readBooks() {
println(name + " is reading.")
}
override fun doHomework() {
println(name + " is doing homework.")
}
}
在Java中,我们使用extends进行继承,使用implements进行接口使用,但在Kotlin中统一使用冒号,中间用逗号进行分隔。且因为接口没有构造函数,所以不需要写上括号。
因为接口中定义了待实现函数,所以我们使用接口后必须对函数进行实现。Kotlin中,我们使用override关键词来重写或者实现接口中的函数。
除此之外Kotlin还增加了一个额外的功能:允许对接口中定义的函数进行默认实现。代码如下:
interface Study {
fun readBooks()
fun doHomework() {
println("do homework default implementation.")
}
}
这里我们在 doHomework方法上加了函数体。如果接口中一个函数拥有了函数体,这个函数体的内容就是它的默认实现。现在当一个类去实现这个接口时,只会强制要求实现readBooks()函数,而doHomework()函数则可以自由选择实现或者不实现,不实现时就会自动使用默认的实现逻辑。
Java和Kotlin函数可见性修饰符对照表
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 当前类可见 | 当前类可见 |
protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 |
default | 同一包路径下的类可见(默认) | 无 |
internal | 无 | 同一模块中的类可见 |