一. 类的创建
1. 类的声明.
Kotlin 类可以包含:构造函数和初始化代码块、函数、属性、内部类、对象声明。
Kotlin 中使用关键字 class 声明类,后面紧跟类名:
class Apple{ // 类名为 Apple
// 大括号内是类体构成
}
当类没有结构体的时候,大括号可以省略。如:
class Apple
类的修饰符 | 描述 |
---|---|
abstract | 抽象类 |
final | 类不可继承,默认属性 |
enum | 枚举类 |
open | 类可继承,类默认是final的 |
annotation | 注解类 |
访问权限修饰符 | 描述 |
---|---|
private | 仅在同一个文件中可见 |
protected | 同一个文件中或子类可见 |
public | 所有调用的地方都可见 |
internal | 同一个模块中可见 |
2. 类的构造函数
在kotlin中有两种类型的构造函数:
- 主构造函数(主构造器)
- 次级构造函数(次级构造器)
在Kotlin类中只有一个主构造函数(主构造器),而辅助构造函数(次级构造器)可以是一个或者多个。
2.1 主构造函数
主构造函数是类头的一部分:它跟在类名(和可选的类型参数)后。
标准写法:class 类名 construction(参数1,参数2….){ }
如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字,否则不能省略。
不能省略:
class Person private constructor(name:String){
val mName: String = name
...
}
可以省略:
class Person(name: String) {
val mName: String = name
...
}
如果不需要将构造函数中参数同时作为类属性,也可以写成如下形式(constructor表示构造函数,里面执行初始化的处理):
class Person{
constructor(name:String){
....
}
}
由于主构造函数没有函数体。如果需要在主构造函数中编写代码该怎么做?初始化的代码可以放 到以 init 关键字作为前缀的初始化块(initializer blocks)中,可以在这里编写要在主构造函数中完成的业务,init{...}中能使用构造函数中的参数:
class Person(username: String, age: Int){
private val username: String
private var age: Int
init{
this.username = username
this.age = age
}
}
注意,主构造的参数可以在初始化块中使用。它们也可以在 类体内声明的属性初始化器中使用:
class Person(name: String) {
val name= name+"..."
...
}
事实上,声明属性以及从主构造函数初始化属性,Kotlin 有简洁的语法:
class Person(var name: String, var age: Int) {
...
}
与普通属性一样,主构造函数中声明的属性可以是 可变的(var)或只读的(val)。
2.2 次构造函数
类也可以声明前缀有 constructor的次构造函数,可以声明多个次构造函数:
class Person {
constructor() {
...
}
constructor(name: String) {
...
}
constructor(name: String,age:Int) {
...
}
}
(1)kotlin声明了主构造器
如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数 用 this 关键字即可:
class Person (name : String?,age : Int) {
var name : String?
var age : Int
init {
println("执行初始化块")
this.name = name
this.age = age
}
constructor() : this(null,0) {//直接委托调用主构造器
}
constructor(name : String) : this() {//间接委托调用主构造器
}
}
(2)kotlin没声明主构造器
kotlin没声明主构造器,重载构造器不需要调用当前类的其他构造器,调用构造器时也会执行初始化块。
class Person {
var name : String?
var age : Int
init {
println("执行初始化块")
}
//在各自的构造器中对属性进行赋值
constructor() {
this.name = null
this.age = 0
}
constructor(name : String) {
this.name = name
this.age = 0
}
constructor(name : String, age : Int) {
this.name = name
this.age = age
}
}
如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的 不带参数的主构造函数。构造函数的可见性是 public。如果你不希望你的类 有一个公有构造函数,你需要声明一个带有非默认可见性的空的主构造函数(此时这个类不能够实例化):
class Person private constructor () {
}
注意:在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值。
1、当只写了主构造函数,没有次构造函数,就会覆盖了默认的空造函数,此时创建对象只能通过你写的主构造函数来调用
2、当只写了次构造函数,不写主构造函数,创建对象必须通过次构造函数调用,若不写空的参数构造函数,则调用空的构造函数会报错
3、当写了既写了主构造,又写了次构造函数,可以直接调用主构造函数或者调用次构造函数创建对象。次构造函数最终都是调用主构造函数来创建对象的。
二.类的继承.
在 Kotlin 中所有类都有一个共同的超类 Any,这对于没有超类型声明的类是默认超类:
class Person // 从 Any 隐式继承
Any不是 java.lang.Object;尤其是,它除了 equals()、hashCode()和toString()外没有任何成员。
在Kotlin中,所有类在默认情况下都是无法被继承的,简而言之,就是说Kotlin中,所有类在默认情况下都是final的,但如何才能被继承,Kotlin给我们提供了一个关键字open,只有被open修饰的类才可以被继承(关键字open的作用与final相反),否则编译器会报错。
open class Parent(p: Int)//申明该类是open的
class Child(p: Int) : Parent(p)//继承open类
覆盖方法:
//父类
open class Parent{
open fun method(){}
fun unOpen(){ //没有open显示指定,这个函数不能被重写覆盖,
println("unOpen")
}
}
//子类
class Child: Parent() {
///这里必须使用override关键字来修饰,
override fun method() {
super.method()
}
final override fun open(){ //Child的子类不可再被重写覆盖
super.open()
}
}
覆盖属性和覆盖方法类似。
三.抽象类.
和Java一样,在kotlin中,抽象类用关键字abstract修饰,抽象类的成员可以在本类中提供实现,也可以不实现而交给子类去实现,不实现的成员必须用关键字abstract声明:
abstract class AbsBase{
abstract fun method()
}
在kotlin中,被继承的类需要用关键字open声明,表明该类可以被继承,但是抽象类或者抽象函数是不用 open 标注的。但是如果子类要实现抽象类的非抽象函数,需要在抽象类中将其声明为open。
抽象类就是为继承设计的,因此即使不用open关键字修饰,抽象类也是可以被继承的。
使用abstract关键字修饰的抽象函数也可以不加open。
抽象类中的非抽象方法如果不用open修饰的话,不能被子类覆盖。
abstract class AbsBase{
abstract fun method() // 如果子类要实现需声明为抽象
open fun method1(){//非抽象方法如果要类子类实现,需要声明为open
println("输出")
}
}
class Child : AbsBase() {
override fun method() {
//抽象类的实现
}
override fun method1() {
super.method1()
println("子类实现")
}
}
四.接口
定义.
接口用interface作为关键字,基本上和Java的接口类似,但是kotlin的接口不仅可以有抽象方法,也可以有已实现的方法。
interface Animal {
val kind: String
val color: String
fun doing()
fun eat() {
println("吃骨头")
}
}
class Dog : Animal {
override val kind = "小黑狗"
override val color = "黑色的"
override fun doing() {
println("正在玩")
}
}
接口和抽象类区别.
- 抽象类可以为属性设置值和getter、setter,而接口不可以。
- 抽象类和接口中都可以有默认的实现方法,但接口中的方法不能有状态,即涉及到属性的值需要到实现类中设置。
- 子类只能继承一个父类,但可以实现多个接口。
解决覆盖冲突.
我们可以继承多个接口,可能会出现继承了同一个方法的多个实现。调用应该使用super<类名>.同名方法的形式调用,举个例子:
interface A {
fun foo() {
println("A")
}
fun bar()
}
interface B {
fun foo() {
println("B")
}
fun bar() {
println("bar")
}
}
class C : A {
override fun bar() {
println("C.bar")
}
}
class D : A, B {
override fun foo() {
super.foo()
super.foo()
println("D.foo")
}
override fun bar() {
super.bar()
}
}
fun main(args: Array) {
var d = D()
d.foo()
d.bar()
}