在Kotlin中,类用关键字class来定义
如果一个类具有类体,那么需要使用{ }来写类体内容,如果不需要类体,那么只需要定义类名就可以了
// 定义一个alarmClock类class AlarmClock { fun alarm() = println("叮铃铃...")}// 定义没有类体的alarm类class AlarmJava 中 默认类都是public 的 ,kotlin 类默认都是final修饰的,不能用来继承,需要添加open 修饰符
泛型修饰符多用于协变
在Kotlin中,构造函数与Java大有不同。
Kotlin的一个类中可以有一个主构造函数和多个次构造函数
所谓主构造函数其实就是类头括号中的一部分,例如
class Person constructor(name: String) {...}如果主构造函数没有任何的注解或者可见性的修饰符,那么可以省略constructor关键字
class Person(name: String) {...}但是这样我们仅仅是给Persion类传入了name这个参数,并没有让他给成员变量赋值,这样的效果和如下的Java代码一样
class Person1 { public Person1(String name) { }}所以如何让我们传入的name成为成员变量呢?
在Kotlin的类中,有一个init关键字,可以作类的为初始化块,可以理解为Java构造方法中的代码部分。
如果我们想给成员变量赋值,可以这么做
class Person(name: String) { var name = "no name" init { this.name = name }}但是这样其实是仿照Java的对象模式,而且这里使用了var的变量
如果想直接一步到位,可以
class Person(name: String) { val name = name}Kotlin支持下面这样的化简,一步到位,与上述代码是一样的功能
class Person(val name: String) {}当然,和函数一样,我们可以给这个成员变量赋默认值
class Person(val name: String = "woodwhale") {}次构造函数就直接在类体中,使用constructor声明
class Person(val pets: MutableList<Pet> = mutableListOf())class Pet { constructor(owner: Person) { owner.pets.add(this) // adds this pet to the list of its owner's pets }}上述代码的Pet类没有使用主构造函数而使用了次构造函数进行初始化
class Person(val name: String) { val children: MutableList<Person> = mutableListOf() constructor(name: String, parent: Person) : this(name) { parent.children.add(this) }}观察上述代码,如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可
请注意,初始化块中的代码实际上会成为主构造函数的一部分。委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块与属性初始化器中的代码都会在次构造函数体之前执行,例如:
class Constructors { init { println("Init block") } constructor(i: Int) { println("Constructor $i") }}fun main() { Constructors(1)}/* Init block Constructor 1 进程已结束,退出代码0*/如果我们的一个类的主构造函数可见性设置为private,那么我们将无法直接创建这个对象

可以使用单例模式来实现,这里举一个懒汉式的例子:
fun main() { DontCreateMe.get()}class DontCreateMe private constructor () { companion object { private val me: DontCreateMe? = null get() { return field ?: DontCreateMe() } fun get(): DontCreateMe { return me!! } }}这里的companion object 是伴生类,之后会将到
对象就是实例化一个类,我们可以理解一下——类是一个蓝图,而对象是蓝图设计出来的产物
创建对象在Kotlin中不需要使用new关键字,和python类似,只需要在类名之后写上一个()就可以
fun main() { val person = Person()}class Person如果类带有参数,这样处理就好了
fun main() { val person = Person("woodwhale",18) println("姓名: ${person.name}, 年龄: ${person.age}")}class Person(val name: String, val age: Int)/* 姓名: woodwhale, 年龄: 18 进程已结束,退出代码0*/一个类中可以有如下的成员:
上述的成员中,需要讲解的就是嵌套类和内部类,还有一个对象声明,其他的内容之前都讲述过了
首先在Java中我们只听说过内部类这种名词,嵌套类是什么呢?
在Kotlin中,内部类用专门的inner来修饰,而类中没有使用inner的类,就属于嵌套类
例如:
// 嵌套类class A { class B}// 内部类class C { inner class D}值得注意的是,内部类可以访问外部类的属性
在Kotlin中,没有Java传统的getter和setter,我们直接通过实例对象.属性的方式来访问或者赋值
但是如果我想在get和set的过程中进行操作呢?例如,我想每次都获取Person类中name的大写
在Kotlin中,为了解决上述问题,在类中有get和set两个函数。如果我们没有使用的话,那么就是默认的。
使用方式:
var <propertyName>[: <PropertyType>] [= <property_initializer>] [<getter>] [<setter>]这个get和set是针对一个属性来写的,如果一个类中有很多的属性,那么这些属性都可以使用自己的get和set方法
举个例子:
fun main() { val student = Student() student.name = "woodwhale" println(student.name)}class Student { var name: String = "" get() = field.lowercase() set(value) { field = value.uppercase() println(field) }}/* WOODWHALE woodwhale 进程已结束,退出代码0*/在上述代码中,我们在get函数中,返回了 field.lowercase(),这里的field指的就是name这个属性,但是不能直接使用name.lowercase(),因为这样的name也会有get和set,会导致无限递归从而内存溢出。而set函数中,我们给field赋值value.uppercase()也就是转为大写。
注意:
在Kotlin中我们可以直接使用object关键字来声明一个对象,这个对象所有的属性可以直接访问。对象声明非常适合用来写Utils类
注意,使用object声明得到的对象是一个单例,引用这个对象只需要使用使用其名就可以
object DataProviderManager { fun registerDataProvider(provider: DataProvider) { // …… } val allDataProviders: Collection<DataProvider> get() = // ……}fun main() { DataProviderManager.registerDataProvider(……)}因为对象声明得到的是一个单例,如果引入变量,那么这两个变量指向的地址相同
var data1 = DataProviderManagervar data2 = DataProviderManagerdata1.name = "test"print("data1 name = ${data2.name}") 在Kotlin中,没有static关键字,那么如何在类中声明一个静态常量或者函数(方法)呢
这个时候,我们就使用Kotlin中的伴生对象,使用关键字companion 标记,这样,类中的伴生对象就与这个类进行了关联,我们可以直接通过这个外部类访问伴生对象中的内部元素
class MyClass { companion object Factory { fun create(): MyClass = MyClass() }}// 访问到对象的内部元素val instance = MyClass.create()当然,我们可以省略掉这个伴生对象的名字,在上述代码中是Factory,我们可以直接调用如下:
class MyClass { companion object { fun create(): MyClass = MyClass() }}val instance = MyClass.Companion.create()注意:
一个类中只能声明一个伴生对象,也就是companion只能使用一次
尽管伴生对象看起来像其他语言中的静态成员,但是他们在运行的时候是真实对象的实例成员,我们还可以让伴生对象实现接口
interface Factory<T> { fun create(): T}class MyClass { companion object : Factory<MyClass> { override fun create(): MyClass = MyClass() }}对象表达式和对象声明之间有一个重要的语义差别:
在Kotlin中,如果我们在类中普通的声明一个成员属性,那么需要赋初始值,因为Kotlin为了避免null的出现。
如果我们不想进行更多的开销去初始化一个用的很少的成员属性,我们可以使用延迟初始化的方式。
使用lateinit关键字修饰属性,我们就不必声明其初始值,编译器或默认你的这个属性会在使用前被用户赋值
class MyTest { lateinit var subject: String}注意:
lateinit修饰符只能用于在类体中的属性(不是在主构造函数中声明的var属性,并且仅当该属性没有定义get和set的手)lateinit修饰符修饰的属性不能是原生类型(Int等),可以是String等lateinit修饰符修饰的属性赋值就直接读取,会报错value.isInitialized属性来判断value是否被初始化过在Kotlin中,所有的类都有一个公共的父类(超类),这个类是Any,类似于Java中的Object,对于没有声明父类的类,其父类默认是Any。
Kotlin和Java在继承这块和一样,只能单继承,但是可以多实现(实现多个接口)
在默认情况下,Kotlin的类是final的,他们不能被继承,如果想让这个类可以被继承,需要使用open关键字标记这个类
open class Base // 该类开放继承如果需要声明一个子类,可以使用如下方式,使用:表示继承或实现
open class Base(p: Int)class Derived(p: Int) : Base(p)如果子类有一个主构造函数,那么必须使用父类的主构造函数的参数当场初始化
如果子类没有主构造函数,那么每个次构造函数的都使用super关键字初始化,或者委托给另一个次构造函数,不同的次构造函数可以调用父类的不同的构造函数
class MyView : View { constructor(ctx: Context) : super(ctx) constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)}在Java中,我们使用@override注解的方式来声明一个重写的方法,而在Kotlin中,我们直接使用override修饰符来进行修饰函数就可以了
open class Shape { open fun draw() { /*……*/ } fun fill() { /*……*/ }}class Circle() : Shape() { override fun draw() { /*……*/ }}需要注意的是,重写的方法也需要加上open来修饰,如果不使用open修饰(例如Shape.fill()),那么子类中将不能拥有一个名为fill()的函数。在此基础上,如果一个类没有用open修饰,那么这个类中的方法即使用open来修饰也没有作用
子类的子类也可以重写方法,如果一个子类不想让其子类再次重写该方法,可以加上final修饰符
open class Rectangle() : Shape() { final override fun draw() { /*……*/ }}与Java不同的是,Kotlin可以重写属性,而Java的子类中的属性可以与父类同名,这样父类中的属性就成了隐藏属性。
Kotlin中的重写属性,同样也是使用override来修饰属性,前提是父类中的这个属性被open修饰
open class Shape { open val vertexCount: Int = 0}class Rectangle : Shape() { override val vertexCount = 4}我们可以使用var来覆写val的属性,但是反过来就不行,原理就是val只有get方法,如果给val重写成了var,那么这个set如何管理呢?
我们可以直接在主构造器中使用override关键字作为属性声明的一部分
// 形状接口interface Shape { val vertexCount: Int}class Rectangle(override val vertexCount: Int = 4) : Shape // 总是有 4 个顶点在子类实例化对象的过程中,第一步是完成父类的初始化,父类初始化之后,就到了子类进行初始化
fun main() { Derived("w","d")}open class Base(val name: String) { init { println("Initializing Base") } open val size: Int = name.length.also { println("Initializing size in Base: $it") }}class Derived( name: String, val lastName: String,) : Base(name.capitalize().also { println("Argument for Base: $it") }) { init { println("Initializing Derived") } override val size: Int = (super.size + lastName.length).also { println("Initializing size in Derived: $it") }}/* Argument for Base: W Initializing Base Initializing size in Base: 1 Initializing Derived Initializing size in Derived: 2 进程已结束,退出代码0*/这意味着,父类构造函数执行时,子类中声明或覆盖的属性都还没有初始化。如果在父类初始化逻辑中(直接或通过另一个覆盖的 open 成员的实现间接)使用了任何一个这种属性,那么都可能导致不正确的行为或运行时故障。设计一个父类时,应该避免在构造函数、属性初始化器以及 init 块中使用 open 成员。
在子类中,使用super关键字可以调用父类(超类)中的函数和属性
open class Rectangle { open fun draw() { println("Drawing a rectangle") } val borderColor: String get() = "black"}class FilledRectangle : Rectangle() { override fun draw() { super.draw() println("Filling the rectangle") } // 调用父类中的borderColor val fillColor: String get() = super.borderColor}如果在一个内部类中需要访问外部类的父类(超类),那么美可以通过由外部类名限定的super关键字来实现,super@OuterClass
class FilledRectangle: Rectangle() { override fun draw() { val filler = Filler() filler.drawAndFill() } inner class Filler { fun fill() { println("Filling") } fun drawAndFill() { // 调用 Rectangle 的 draw() 实现 super@FilledRectangle.draw() fill() // 使用 Rectangle 所实现的 borderColor 的 get() println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") } }}在Kotlin中,如果一个类从它的直接超类继承相同成员的多个实现,那么这个类必须覆盖掉这个成员,并且自己提供实现方式。
为了表示从哪个超类继承了实现,我们使用带尖括号的super<Base>来限定使用那个超类
open class Rectangle { open fun draw() { /* …… */ }}interface Polygon { fun draw() { /* …… */ } // 接口成员默认就是“open”的}class Square() : Rectangle(), Polygon { // 编译器要求覆盖 draw(): override fun draw() { super<Rectangle>.draw() // 调用 Rectangle.draw() super<Polygon>.draw() // 调用 Polygon.draw() }}在上述例子中,Square类既继承Rectangle类,又实现了Polygon接口。但是父类和接口中都有draw()这个方法函数,为了消除歧义,Square类必须自己实现draw()方法函数,可以是使用super实现,也可以自己重写
类或者其中的成员可以声明为abstract,表示抽象类或者抽象成员
抽象成员在当前这个类中不需要实现
抽象类不需要标注open
我们一般是让一个类实现抽象类
class Polygon : Rectangle(){ override fun draw() {}}abstract class Rectangle { abstract fun draw()}当然,我们也可以用一个抽象成员覆盖一个非抽象的开放成员
open class Polygon { open fun draw() {}}abstract class Rectangle : Polygon() { abstract override fun draw()}在面向对象中,接口很重要!在Kotlin中,也是使用关键字 interface来定义接口
interface MyInterface { fun bar() fun foo() { // 可选的方法体 }}接口的实现和继承相似,也是使用:,只不过继承是: 类名(),接口是: 接口名
class Child : MyInterface { override fun bar() { println(114514) }}接口中可以定义属性,要么是抽象的,要么提供get函数来实现。接口声明的属性没有幕后字段(backing field),也就是无法使用field来指明这个属性
fun main() { val child = Child() println(child.propertyWithImplementation) child.sout1()}interface MyInterface { val test: Int // 抽象的 val propertyWithImplementation: String get() = "default" fun sout1() { print(test) }}class Child : MyInterface { override val test: Int = 29 override fun sout1() { super.sout1() }}/* default 29 进程已结束,退出代码0*/一个接口可以从其他接口进行继承,从而提供父类的成员
interface Named { val name: String}// 继承Named接口interface Person : Named { val firstName: String val lastName: String // 重写属性 override val name: String get() = "$firstName $lastName"}// 实现Person接口data class Employee( // 不必实现“name” override val firstName: String, override val lastName: String, val position: Position) : Person实现多个接口的时候,我们可能会遇到同一个方法结成多个实现的问题
我们的解决方式和继承一样,使用super<interface>.funciton()来实现
interface A { fun foo() { print("A") } fun bar()}interface B { fun foo() { print("B") } fun bar() { print("bar") }}class C : A { override fun bar() { print("bar") }}class D : A, B { override fun foo() { super<A>.foo() super<B>.foo() } override fun bar() { super<B>.bar() }}在上述代码中,接口A和B都定义了foo()和bar()方法,两者都实现了foo(),但是只有B实现了bar(),因为C是实现了A,所以一定要重写bar()方法。而D实现了A和B,所以需要两个方法都重写,可以使用super进行选择
在Kotlin中,如果一个接口有且仅有一个抽象成员,可以有多个非抽象成员,那么我们可以使用函数式接口来缩短代码,使代码更加简洁
例如有这样一个接口
fun interface IntPredicate { fun accept(i: Int): Boolean}如果我们使用最原本的方法来实现这个接口中的accept()方法,需要这样写
// 创建一个类的实例val isEven = object : IntPredicate { override fun accept(i: Int): Boolean { return i % 2 == 0 }}但是,因为这个接口中,有且仅有一个抽象成员,那么可以使用函数式接口来缩短代码量
// 通过 lambda 表达式创建一个实例,这样得到的isEven对象具有重写好的accept方法val isEven = IntPredicate { it % 2 == 0 }使用方式也和普通对象一样
fun interface IntPredicate { fun accept(i: Int): Boolean}val isEven = IntPredicate { it % 2 == 0 }fun main() { println("Is 7 even? - ${isEven.accept(7)}")}由于篇幅限制,本章仅仅讲述Kotlin的面向对象,之后将补充Kotlin的重点知识!