阅读完需:约 11 分钟
类
Kotlin中的类是使用class
关键字来声明:

class Invoice {
}
类声明由类名,类头(指定类型参数,主构造函数等)和类体组成,由大括号括起来。类头和类主体都是可选的; 如果类没有主体,可以省略花括号。如下 –
class Invoice

在java中变量可以不用初始化,但是kotlin中必须要初始化,因为要避免null异常。
构造函数

Kotlin中的类可以有一个主构造函数和一个或多个辅助构造函数。 主构造函数是类头的一部分:它在类名后面(和可选的类型参数)。

class Person constructor(firstName: String) {
}
如果主构造函数没有任何注释或可见性修饰符,那么可以省略constructor
关键字:

class Person(firstName: String) {
}



主构造函数不能包含任何代码。 初始化代码可以放在初始化程序块中,前缀为init
关键字:
class Customer(name: String) {
init {
logger.info("Customer initialized with value ${name}")
}
}
初始化程序块中可以使用主构造函数的参数。 它们也可以用在类体中声明属性的初始化器:
class Customer(name: String) {
val customerKey = name.toUpperCase()
}
实际上,要声明属性并从主构造函数初始化它们,Kotlin有一个简洁的语法:


加上var 或 val 同时定义构造器的参数和属性,而不加则只是构造器的参数不是属性。
class Person(val firstName: String, val lastName: String, var age: Int) {
// ...
}
与常规属性大体相同,主构造函数中声明的属性可以是多值(var
)或只读(val
)。
如果构造函数具有注释或可见性修饰符,则constructor
关键字是必需的,修饰符将在它之前:
class Customer public @Inject constructor(name: String) { ... }
作为辅助构造函数

类还可以声明辅助构造函数,它们以constructor
关键字作为前缀:
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}

如果不定义主构造器,而定义一个或多个副构造器,那么容易出现类初始化有多条,很紊乱,所以我们可以去继承父类的构造器,如果父类只有一个默认无参的构造器的话可以将 :super()
省略掉。

如果不去调用父类的构造器也可以去调用本类的其他构造器

如果有重载的需求,那么在主构造器上用默认参数就可以啦,这是比较推荐的写法,当然如果想让java代码看懂kotlin,可以加上这个注解。
如果类具有主构造函数,则每个辅助构造函数需要通过另一个辅助构造函数直接或间接地委派给主构造函数。 使用this
关键字对同一类的另一个构造函数进行委派:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
这个this的调用和上面的子类调用父类的构造器以及子类调用自身的其他构造器的原理是一样的。
如果一个非抽象类没有声明任何构造函数(主或辅助),那么它将不使用参数来生成主构造函数。 构造函数的可见性将是公开的。 如果不希望类具有公共构造函数,则需要声明具有非默认可见性的空主构造函数:
class DontCreateMe private constructor () {
}
在JVM上,如果主构造函数的所有参数都具有默认值,编译器将生成一个额外的无参数构造函数,它将使用默认值。 这使得更容易使用Kotlin与诸如Jackson或JPA的库,通过无参数构造函数创建类实例。
工厂函数
构造同名的工厂函数,函数名也可以与类的名字不同,工厂方法比构造函数更具有表现力,因为它可以通过名字知道类是如何构造出来的
class Person(var age: Int, var name: String = "unknown") {
override fun equals(other: Any?) = (other as? Person)?.name?.equals(name) ?: false
override fun hashCode() = name.hashCode()
}
val persons = HashMap<String, Person>()
//函数名也可以与类的名字不同
fun Person(name: String): Person {
return persons[name]
?: Person(1, name).also { persons[name] = it }
}
类似String有常见的工厂方法调用:
fun main() {
val str = String()//调用的是构造函数
val str1 = String(charArrayOf('1', '2'))//调用的是工厂函数 实际是一个函数指向了String的构造函数
}
当然我们也可以自己给系统String类添加工厂方法,一般写成函数名跟类名一样
fun String(ints: IntArray): String {
return ints.contentToString()
}
创建类的实例
要创建一个类的实例,需要调用类的构造函数,就像它是一个常规函数一样

接口
Kotlin接口类似于Java 8中的接口。它们可以包含抽象方法的定义以及非抽象方法的实现。但是,它们不能包含任何状态。

interface MyInterface {
var test: String //抽象属性
fun foo() //抽象方法
fun hello() = "Hello there" //具有默认实现的方法
}
- 创建接口 MyInterface。
- 该接口有一个抽象属性 test 和一个抽象方法 foo()。
- 该接口还具有非抽象方法 hello()。

interface MyInterface {
val test: Int //抽象属性
fun foo() : String //抽象方法(返回字符串)
fun hello() { //具有默认实现的方法
// body (optional)
}
}
class InterfaceImp : MyInterface {
override val test: Int = 25
override fun foo() = "Lol"
//其他代码
}
在kotlin中 :是有很多用途的,其中 :用来表示继承和实现类
接口还可以具有提供访问器实现的属性
interface MyInterface {
//带实现的属性
val prop: Int
get() = 23
}
class InterfaceImp : MyInterface {
//类主体
}
fun main(args: Array<String>) {
val obj = InterfaceImp()
println(obj.prop)
}
运行该程序时,输出为:23
这里,prop 不是抽象的,但是它在接口中是有效的,因为它提供了访问器的实现。
但是,您不能在接口内部执行类似 val prop:Int = 23 的操作。
在一个类中实现两个或多个接口
Kotlin不允许真正的多重继承。但是,可以在一个类中实现两个或多个接口
interface A {
fun callMe() {
println("来自接口A")
}
}
interface B {
fun callMeToo() {
println("来自接口B")
}
}
//实现两个接口A和B
class Child: A, B
fun main(args: Array<String>) {
val obj = Child()
obj.callMe()
obj.callMeToo()
}
运行该程序时,输出为:
来自接口A
来自接口B
解决重写冲突(多接口)
假设两个接口(A和B)具有相同名称的非抽象方法(假设callMe()方法)。您在一个类中实现了这两个接口(假设C)。 现在,如果使用 C 类的对象调用callMe()方法,则编译器将引发错误。 例如
interface A {
fun callMe() {
println("接口 A")
}
}
interface B {
fun callMe() {
println("接口 B")
}
}
class Child: A, B
fun main(args: Array<String>) {
val obj = Child()
obj.callMe()
}
要解决此问题,需要提供自己的实现。
interface A {
fun callMe() {
println("接口 A")
}
}
interface B {
fun callMe() {
println("接口 B")
}
}
class C: A, B {
override fun callMe() {
super<A>.callMe()
super<B>.callMe()
}
}
fun main(args: Array<String>) {
val obj = C()
obj.callMe()
}
现在,当您运行程序时,输出将是:
接口 A
接口 B
这里,在 C 类中提供了callMe()方法的显式实现。
语句 super<A>.callMe()调用类A的callMe()方法。类似地,super<B>.callMe()调用类B的callMe()方法。
抽象类
与Java一样,abstract 关键字用于在Kotlin中声明抽象类。无法实例化抽象类(不能创建抽象类的对象)。但是,您可以从它们中继承子类。
除非您明确使用 abstract 关键字将其抽象,否则抽象类的成员(属性和方法)是非抽象的。让我们举个实例:


abstract class Person {
var age: Int = 40
fun displaySSN(ssn: Int) {
println("我的社保号是: $ssn.")
}
abstract fun displayJob(description: String)
}
- 创建一个抽象类 Person 。您不能创建该类的对象。
- 该类具有非抽象属性 age和非抽象方法 displaySSN()。如果您需要在子类中覆盖这些成员,则应使用 open 关键字标记它们。
- 该类具有抽象方法 displayJob()。它没有任何实现,必须在其子类中重写。
重点:在抽象类中,如果没有用open关键字来标记,或者不是 abstract 方法那么它将无法被重写。
注意:抽象类总是开放的。 您不需要显式使用open关键字从它们继承子类。
abstract class Person(name: String) {
init {
println("我的名字是 $name.")
}
fun displaySSN(ssn: Int) {
println("我的社保号是 $ssn.")
}
abstract fun displayJob(description: String)
}
class Teacher(name: String): Person(name) {
override fun displayJob(description: String) {
println(description)
}
}
fun main(args: Array<String>) {
val jack = Teacher("Jack Smith")
jack.displayJob("我是一名数学老师。")
jack.displaySSN(23123)
}
Getter 和 Setter
在编程中,getter 用于获取属性的值。同样,setter 用来设置属性的值。
在Kotlin中,getter 和 setter是可选的,如果未在程序中创建它们,它们将自动生成。

Kotlin中的以下代码
class Person {
var name: String = "defaultValue"
}
等同于
class Person {
var name: String = "defaultValue"
// getter
get() = field
// setter
set(value) {
field = value
}
}


示例:更改属性的值
fun main(args: Array<String>) {
val maria = Girl()
maria.actualAge = 15
maria.age = 15
println("玛莉亚: 实际年龄 = ${maria.actualAge}")
println("玛莉亚: 虚拟年龄 = ${maria.age}")
val angela = Girl()
angela.actualAge = 35
angela.age = 35
println("安戈洛: 实际年龄 = ${angela.actualAge}")
println("安戈洛: 虚拟年龄 = ${angela.age}")
}
class Girl {
var age: Int = 0
get() = field
set(value) {
field = if (value < 18)
18
else if (value >= 18 && value <= 30)
value
else
value-3
}
var actualAge: Int = 0
}
属性引用

在kotlin中可以用 :: 来引用类里的内容
class Person(age: Int, name: String) {
var age: Int = age //property
get() {
return field
}
set(value) {
println("setAge: $value")
field = value
}
var name: String = name
get() {
return field // backing field
}
set(value) {
}
}
fun main() {
val ageRef = Person::age // 没有被实例化就要用类名::方法名
val person = Person(18, "Bennyhuo")
val nameRef = person::name
ageRef.set(person, 20)
nameRef.set("Andyhuo")
}
完整例子:
AbsClass 抽象类
abstract class AbsClass {
abstract fun absMethod()
open fun overridable(){}
fun nonOverridable(){} // 不能被重写因为没有open或者abstract
}
SimpleInf 接口
interface SimpleInf {
val simpleProperty: Int // property
fun simpleMethod()
}
SimpleClass 实现和继承的类
open class SimpleClass(var x: Int, val y: String)
: AbsClass(), SimpleInf {
// override 代表了这是重写的
override val simpleProperty: Int
get() {
return 2
}
val z : Long
get() {
return simpleProperty * 2L
}
override fun absMethod() {}
override fun simpleMethod() {}
fun y(){}
fun zzz(string: String){
}
final override fun overridable(){
}
}
// 这个类可以继承 SimpleClass 是因为 SimpleClass类前面加上了 open 不然无法继承
// 但是如果不想让某一个方法被重写那么加上 final 比如:final override fun overridable()
class SimpleClass2(x: Int, y: String): SimpleClass(x, y){
}
实例化
fun main() {
val simpleClass = SimpleClass(9, "Hello")
println(simpleClass.simpleProperty)
println(simpleClass.x)
println(simpleClass.y)
println(simpleClass.z)
simpleClass.y()
simpleClass.zzz("Sleeping ZZZ!")
}