前言
本文主要讲解类和接口,主要包括类的声明、构造器、类成员、修饰符、类的继承、接口、抽象类。
Kotlin文章列表
Kotlin文章列表: 点击此处跳转查看
目录
1.1 类的声明
在 Kotlin 中,类的声明使用关键字 class
。下面是一个简单的类声明的示例:
class MyClass {
// 类的成员变量
var property1: Int = 0
var property2: String = ""
// 类的成员函数
fun myFunction() {
// 函数体
}
}
在上面的示例中,MyClass
是一个简单的类,具有两个成员变量 property1
和 property2
,以及一个成员函数 myFunction
。可以通过使用点符号来访问类的成员,例如 myClassInstance.property1
来访问 property1
的值,其中 myClassInstance
是 MyClass
类的一个实例。
除了普通的类声明之外,还可以使用一些修饰符来定义类的属性和函数的可见性。例如,private
关键字可以用于限制成员的可见性,使其只在类内部可访问。还可以使用 open
关键字来声明一个类可被继承,使用 abstract
关键字声明一个抽象类等等。
1.2 构造器
1.2.1 主构造器
在 Kotlin 中,可以使用主构造函数来声明类的主要构造逻辑。主构造函数是类头部的一部分,并跟在类名后面。
以下是一个使用主构造函数的类声明示例:
class MyClass constructor(parameter1: Type1, parameter2: Type2) {
// 构造函数中的初始化逻辑
init {
// 使用参数进行初始化
// 可以在这里执行其他的构造逻辑
}
// 类的成员变量
var property1: Type1 = parameter1
var property2: Type2 = parameter2
// 类的成员函数
fun myFunction() {
// 函数体
}
}
在上面的示例中,MyClass
使用主构造函数接收两个参数 parameter1
和 parameter2
。这些参数可以在类的成员变量中使用。在构造函数中的 init
块中,可以对成员变量进行初始化,也可以执行其他的构造逻辑。
需要注意的是,在 Kotlin 中,如果主构造函数没有任何注解或可见性修饰符,可以省略 constructor
关键字。
你还可以在主构造函数中声明默认参数,例如:
class MyClass(parameter1: Type1, parameter2: Type2 = defaultValue) {
// ...
}
在这种情况下,如果调用 MyClass
构造函数时省略了 parameter2
参数,它将使用默认值 defaultValue
。
1.2.2 第二构造器
在 Kotlin 中,除了主构造函数,你还可以使用次要构造函数(secondary constructor)来定义类的其他构造逻辑。次要构造函数提供了额外的构造选项,使你可以使用不同的参数组合来创建对象。
以下是一个使用次要构造函数的类声明示例:
class MyClass {
var property1: Int = 0
var property2: String = ""
constructor(parameter1: Int) {
// 使用参数进行初始化
property1 = parameter1
}
constructor(parameter1: Int, parameter2: String) {
// 使用参数进行初始化
property1 = parameter1
property2 = parameter2
}
// 类的成员函数
fun myFunction() {
// 函数体
}
}
在上面的示例中,MyClass
声明了两个次要构造函数。第一个次要构造函数接收一个 Int
类型的参数,并将其用于初始化 property1
。第二个次要构造函数接收一个 Int
类型的参数和一个 String
类型的参数,并将它们用于初始化 property1
和 property2
。
使用次要构造函数时,需要注意以下几点:
-
次要构造函数必须直接或间接地调用主构造函数或另一个已存在的次要构造函数。
-
使用
this
关键字调用同一个类的其他构造函数。 -
在次要构造函数内部,可以执行其他的构造逻辑。
1.2.3 Kotlin中的Singleton模式
在 Kotlin 中,可以使用对象声明(Object Declaration)来实现 Singleton 模式。Singleton 是一种设计模式,用于确保一个类只有一个实例,并提供全局访问点。
在 Kotlin 中,可以通过以下方式声明一个 Singleton:
object MySingleton {
// 单例对象的属性和方法
var property1: Int = 0
var property2: String = ""
fun myFunction() {
// 函数体
}
}
在上面的示例中,MySingleton
是一个对象声明,它具有属性 property1
和 property2
,以及函数 myFunction
。这些属性和函数可以直接通过 MySingleton.property1
、MySingleton.property2
和 MySingleton.myFunction()
来访问,无需创建该类的实例。
对象声明在首次访问时被延迟初始化,且只会初始化一次。因此,MySingleton
对象在应用程序生命周期中只会存在一个实例。
使用 Singleton 可以在需要共享状态或提供全局访问点的情况下非常有用。例如,日志记录器、数据库连接池等场景都可以使用 Singleton 来实现。
1.2.4 Kotlin函数中的默认参数
在 Kotlin 中,可以为函数参数提供默认值,这样在调用函数时可以选择性地省略这些参数。默认参数允许你定义函数的一组默认值,简化了函数调用并提供了更大的灵活性。
以下是一个使用默认参数的函数示例:
fun greet(name: String = "Guest", message: String = "Hello") {
println("$message, $name!")
}
在上面的示例中,greet
函数有两个参数:name
和 message
。这两个参数都有默认值,分别为 "Guest"
和 "Hello"
。如果调用该函数时没有提供这些参数的值,将使用默认值。
可以以多种方式调用带有默认参数的函数:
greet() // 使用所有参数的默认值,输出:Hello, Guest!
greet("John") // 使用默认的 message 参数值,输出:Hello, John!
greet("Alice", "Hi") // 提供所有参数的值,输出:Hi, Alice!
注意,如果需要仅为某些参数提供值而保留其他参数的默认值,可以使用命名参数来显式指定参数的值:
greet(message = "Hey", name = "Bob") // 使用命名参数,输出:Hey, Bob!
通过默认参数,可以在函数定义时提供合理的默认值,从而简化函数调用并为调用者提供更大的灵活性。
1.2.5 创建类的实例
在 Kotlin 中,可以使用 val
或 var
关键字来创建类的实例。val
用于声明一个不可变的引用,而 var
用于声明一个可变的引用。
以下是创建类实例的示例:
class MyClass {
var property1: Int = 0
var property2: String = ""
fun myFunction() {
println("Hello from MyClass")
}
}
// 创建类实例
val myObj = MyClass()
// 访问类的属性和调用方法
myObj.property1 = 42
myObj.property2 = "Hello, World!"
myObj.myFunction()
在上面的示例中,我们首先定义了一个 MyClass
类,该类具有属性 property1
和 property2
,以及方法 myFunction
。然后,我们使用 val
关键字创建一个名为 myObj
的不可变引用,并通过调用类的构造函数创建了一个 MyClass
的实例。
要访问类的属性和调用方法,我们使用点符号 (.
) 来访问实例的成员。例如,myObj.property1
用于访问和设置 property1
的值,myObj.myFunction()
用于调用 myFunction
方法。
如果你希望在类实例化时初始化属性,可以在构造函数中接收参数,并在构造函数中使用它们进行初始化。例如:
class MyClass(val property1: Int, var property2: String) {
fun myFunction() {
println("Hello from MyClass")
}
}
// 创建类实例并传递参数
val myObj = MyClass(42, "Hello, World!")
在上述示例中,我们在 MyClass
构造函数中定义了 property1
和 property2
参数,并将它们用作属性的初始化值。
1.3 类成员
1.3.1 属性的基本用法
在 Kotlin 中,类的属性用于存储对象的状态或数据。属性可以包含数据,并且可以通过 getter 和 setter 方法进行访问和修改。
以下是 Kotlin 类属性的基本用法:
class MyClass {
// 可变属性
var mutableProperty: String = "Initial Value"
// 不可变属性
val immutableProperty: Int = 42
// 自定义 getter 和 setter
var customProperty: String = ""
get() = field.toUpperCase() // 自定义 getter 方法
set(value) {
field = value.trim() // 自定义 setter 方法
}
}
在上面的示例中,MyClass
类包含了几个属性。具体说明如下:
mutableProperty
是一个可变属性,类型为String
,可以被修改。immutableProperty
是一个不可变属性,类型为Int
,只能在对象初始化时赋值,并且不能再修改。customProperty
是一个自定义 getter 和 setter 的属性。在 getter 方法中,我们将属性值转换为大写字母;在 setter 方法中,我们将接收到的值去除首尾空格后再赋给属性。
属性的访问方式如下:
val obj = MyClass()
// 访问可变属性
println(obj.mutableProperty) // 输出:Initial Value
obj.mutableProperty = "New Value"
println(obj.mutableProperty) // 输出:New Value
// 访问不可变属性
println(obj.immutableProperty) // 输出:42
// 访问自定义属性
obj.customProperty = " Example "
println(obj.customProperty) // 输出:EXAMPLE
上述示例展示了如何访问属性以及如何使用自定义 getter 和 setter 方法。
需要注意的是,Kotlin 还提供了简化属性声明的方式,例如 var/var
代替 get/set
方法的定义,以及 field
关键字用于访问属性的幕后字段。这些是更高级的属性概念,可以在更复杂的场景中使用。
1.3.2 属性的getter和setter形式
在 Kotlin 中,类的属性可以具有默认的 getter 和 setter,也可以自定义 getter 和 setter 方法来控制属性的访问和修改行为。
以下是 Kotlin 类属性的 getter 和 setter 形式的示例:
class MyClass {
var property: String = ""
get() {
println("Getting property value")
return field
}
set(value) {
println("Setting property value")
field = value
}
}
在上面的示例中,MyClass
类的 property
属性具有自定义的 getter 和 setter 方法。在 getter 方法中,我们输出一条消息并返回属性的值;在 setter 方法中,我们输出一条消息并将传入的值赋给属性的幕后字段(使用 field
关键字表示)。
属性的访问方式如下:
val obj = MyClass()
obj.property = "New Value" // 调用自定义 setter
println(obj.property) // 调用自定义 getter
上述示例中,我们首先创建了一个 MyClass
对象 obj
。然后,我们使用 obj.property
来设置属性的值,它将调用自定义的 setter 方法并输出一条消息。接下来,我们使用 obj.property
来获取属性的值,它将调用自定义的 getter 方法并输出一条消息。
需要注意的是,默认情况下,如果不提供自定义的 getter 和 setter 方法,Kotlin 会为属性生成默认的 getter 和 setter。如果属性声明为 val
,则只会生成默认的 getter 方法,无法进行修改。
此外,Kotlin 还提供了一种简化的属性声明方式,即通过 val/var
关键字声明属性,而不需要显式地定义 getter 和 setter 方法。
1.3.3 保存属性值的字段
在 Kotlin 中,属性可以使用幕后字段(backing field)来保存其实际值。幕后字段是属性在内部使用的实际存储,它可以被属性的 getter 和 setter 方法访问和修改。
以下是 Kotlin 类属性使用幕后字段的示例:
class MyClass {
var property: String = ""
get() {
println("Getting property value")
return field
}
set(value) {
println("Setting property value")
field = value
}
}
在上面的示例中,MyClass
类的 property
属性使用 field
作为幕后字段。在 getter 方法中,我们使用 field
来访问属性的实际值;在 setter 方法中,我们使用 field
来修改属性的实际值。
属性的访问方式如下:
val obj = MyClass()
obj.property = "New Value" // 调用自定义 setter
println(obj.property) // 调用自定义 getter
上述示例中,我们首先创建了一个 MyClass
对象 obj
。然后,我们使用 obj.property
来设置属性的值,它将调用自定义的 setter 方法并输出一条消息。接下来,我们使用 obj.property
来获取属性的值,它将调用自定义的 getter 方法并输出一条消息。
field
是 Kotlin 中预留的关键字,用于表示幕后字段。你可以根据需要为属性选择不同的幕后字段名称。如果你没有显式指定幕后字段名称,Kotlin 会根据需要自动为属性生成一个默认的幕后字段。
需要注意的是,幕后字段只在属性的自定义 getter 和 setter 方法内部可见。在类的其他地方,应该使用属性的名称来访问属性。
1.3.4 函数
在 Kotlin 中,类的成员函数用于封装对象的行为和操作。函数定义在类内部,可以访问类的属性和其他成员函数。
以下是 Kotlin 类成员函数的示例:
class MyClass {
var property: String = ""
fun myFunction() {
println("Hello from myFunction")
println("Property value: $property")
}
fun anotherFunction(param: Int): Int {
return param * 2
}
}
在上面的示例中,MyClass
类定义了两个成员函数:myFunction
和 anotherFunction
。myFunction
打印一条消息并访问类的属性值。anotherFunction
接收一个 Int
类型的参数 param
,并返回该参数的两倍。
函数的调用方式如下:
val obj = MyClass()
obj.property = "Value"
obj.myFunction()
val result = obj.anotherFunction(5)
println(result)
上述示例中,我们首先创建了一个 MyClass
对象 obj
。然后,我们通过 obj.property
设置属性的值。接下来,我们调用 obj.myFunction()
来执行 myFunction
函数,并输出相应的消息和属性值。最后,我们使用 obj.anotherFunction(5)
调用 anotherFunction
函数,并将返回的结果存储在 result
变量中并打印。
需要注意的是,成员函数可以访问类的属性和其他成员函数。在函数内部,可以使用 this
关键字来引用当前对象,从而访问对象的成员。
1.3.5 嵌套类
在 Kotlin 中,嵌套类是一个类被嵌套在另一个类内部的类。嵌套类在外部类的作用域之外是可见的,可以使用外部类的名称进行访问。
以下是 Kotlin 中嵌套类的示例:
class Outer {
private val outerProperty: String = "Outer Property"
class Nested {
fun nestedFunction() {
println("Nested Function")
// 无法访问外部类的成员
}
}
}
在上面的示例中,Outer
类包含一个嵌套类 Nested
。嵌套类可以访问自身的成员,但无法直接访问外部类的成员。
要访问嵌套类,可以使用外部类的名称作为限定符:
val nestedObj = Outer.Nested()
nestedObj.nestedFunction()
上述示例中,我们首先通过 Outer.Nested()
创建了一个 Nested
类的实例 nestedObj
。然后,我们调用 nestedObj.nestedFunction()
来执行嵌套类的函数。
需要注意的是,嵌套类和内部类(使用 inner
关键字声明的类)是不同的概念。嵌套类不持有对外部类实例的引用,而内部类可以访问外部类的成员并持有对外部类实例的引用。
1.4 修饰符(Modifiers)
在 Kotlin 中,修饰符(Modifiers)用于修改类、函数、属性和其他声明的行为和访问级别。以下是一些常用的修饰符:
- 访问修饰符(Access Modifiers):
public
:默认的修饰符,对所有地方可见。private
:只在同一个文件内可见。protected
:对同一个文件和子类可见。internal
:对同一个模块(module)内的所有地方可见。
- 可见性修饰符(Visibility Modifiers):
public
:对所有地方可见。private
:只在同一个类内可见。protected
:对同一个类和子类可见。internal
:对同一个模块内的所有地方可见。
- 继承修饰符(Inheritance Modifiers):
open
:允许类被继承。默认情况下,类是不可继承的。final
:阻止类被继承,函数被重写。
- 重写修饰符(Override Modifiers):
override
:标记一个函数或属性是重写父类的。必须用在子类中。
- 其他修饰符:
abstract
:用于抽象类和抽象函数。final
:用于阻止函数被重写,属性被修改。const
:用于标记常量,要求在编译时确定其值。
这些修饰符可以根据需要在声明中使用,以达到所需的行为和访问级别。注意,不同的修饰符适用于不同的声明类型(例如,类、函数、属性等)。
需要注意的是,在 Kotlin 中有一些修饰符是默认的(例如,public
),因此在大多数情况下不需要显式地使用它们。但是,了解这些修饰符的含义和使用场景对于编写清晰、安全和可维护的代码非常重要。
1.5 类的继承
1.5.1 Kotlin类如何继承
在 Kotlin 中,类的继承通过使用冒号(:
)来表示。一个类可以继承自另一个类,称为父类(或超类),并继承父类的属性和函数。
以下是 Kotlin 中类的继承的基本语法:
open class ParentClass {
// 父类的属性和函数
}
class ChildClass : ParentClass() {
// 子类的属性和函数
}
在上面的示例中,ChildClass
是继承自 ParentClass
的子类。通过在类名后面使用冒号,后跟父类的名称,可以实现继承关系。
需要注意的是,为了允许继承,父类必须使用 open
关键字进行标记。在默认情况下,类是不可继承的(即,类是 final
的)。通过使用 open
关键字,我们可以将类标记为可继承的。
在子类中,可以访问并重写父类的属性和函数。使用 override
关键字可以重写父类的成员。在重写的函数中,可以使用 super
关键字引用父类的实现。
以下是一个示例,展示了类的继承和重写的用法:
open class Person(val name: String) {
open fun introduce() {
println("My name is $name.")
}
}
class Student(name: String, val grade: Int) : Person(name) {
override fun introduce() {
super.introduce()
println("I'm a student in grade $grade.")
}
}
在上面的示例中,Person
是一个简单的父类,Student
是继承自 Person
的子类。子类重写了父类的 introduce
函数,并使用 super.introduce()
调用了父类的实现。然后,在子类的 introduce
函数中添加了额外的打印语句。
通过以下方式使用继承和重写:
val person = Person("John")
person.introduce() // 输出:"My name is John."
val student = Student("Alice", 10)
student.introduce() // 输出:"My name is Alice.\nI'm a student in grade 10."
在上述示例中,我们首先创建了一个 Person
对象 person
,并调用其 introduce
函数。然后,我们创建了一个 Student
对象 student
,并调用其 introduce
函数。注意,子类 Student
继承了父类 Person
的属性 name
。
1.5.2 重写方法
在 Kotlin 中,要重写父类的方法,需要在子类中使用 override
关键字。这样做可以确保子类中的方法与父类中的方法具有相同的签名,并且在运行时调用时会调用子类的方法。
以下是重写方法的基本语法:
open class ParentClass {
open fun methodName() {
// 父类方法的实现
}
}
class ChildClass : ParentClass() {
override fun methodName() {
// 子类方法的实现
}
}
在上面的示例中,ChildClass
是继承自 ParentClass
的子类,并重写了父类中的 methodName
方法。子类中的 methodName
方法使用 override
关键字进行标记,以表示它是对父类方法的重写。
需要注意的是,父类中被重写的方法必须使用 open
关键字进行标记,以允许子类对其进行重写。而子类中的重写方法可以选择使用 override
关键字进行标记,以明确指示该方法是对父类方法的重写(尽管没有使用 override
也能进行重写,但建议使用 override
显式标记,以提高代码的可读性)。
在子类中重写父类的方法时,可以使用 super
关键字来引用父类的实现。例如,可以使用 super.methodName()
调用父类的方法。
以下是一个示例,展示了如何重写父类的方法:
open class Shape {
open fun draw() {
println("Drawing a shape.")
}
}
class Circle : Shape() {
override fun draw() {
super.draw()
println("Drawing a circle.")
}
}
在上面的示例中,Shape
是一个基类,Circle
是继承自 Shape
的子类。子类重写了父类的 draw
方法,并在子类的方法中使用 super.draw()
调用了父类的实现。然后,在子类的方法中添加了额外的打印语句。
使用重写方法的示例:
val shape: Shape = Circle()
shape.draw()
在上述示例中,我们创建了一个 Shape
类型的变量 shape
,但实际上它引用的是 Circle
对象。然后,我们调用 shape.draw()
方法。由于多态性的存在,实际上调用的是 Circle
类中重写的 draw
方法。输出结果将首先打印 “Drawing a shape.”,然后打印 “Drawing a circle.”。
1.5.3 重写属性
在 Kotlin 中,可以通过重写属性来修改从父类继承的属性的行为。属性的重写需要使用 override
关键字,并且可以对父类属性的访问器(getter 和 setter)进行重写。
以下是重写属性的基本语法:
open class ParentClass {
open val propertyName: Type = initial value
}
class ChildClass : ParentClass() {
override var propertyName: Type
get() {
// 重写 getter
}
set(value) {
// 重写 setter
}
}
在上面的示例中,ChildClass
是继承自 ParentClass
的子类,并重写了父类的 propertyName
属性。子类中的 propertyName
属性使用 override
关键字进行标记,并重写了父类属性的访问器。
在重写属性的访问器中,可以自定义属性的行为,例如提供不同的值或添加额外的逻辑。
需要注意的是,与重写方法类似,父类中被重写的属性必须使用 open
关键字进行标记,以允许子类对其进行重写。而子类中的重写属性可以选择使用 override
关键字进行标记,以明确指示该属性是对父类属性的重写(尽管没有使用 override
也能进行重写,但建议使用 override
显式标记,以提高代码的可读性)。
以下是一个示例,展示了如何重写父类的属性:
open class Shape {
open val description: String = "Shape"
}
class Circle : Shape() {
override val description: String = "Circle"
}
在上面的示例中,Shape
是一个基类,Circle
是继承自 Shape
的子类。子类重写了父类的 description
属性,并提供了不同的值。
使用重写属性的示例:
val shape: Shape = Circle()
println(shape.description)
在上述示例中,我们创建了一个 Shape
类型的变量 shape
,但实际上它引用的是 Circle
对象。然后,我们打印 shape.description
属性。由于多态性的存在,实际上访问的是 Circle
类中重写的 description
属性。输出结果将是 “Circle”。
需要注意的是,重写属性的访问器也可以调用父类属性的访问器,使用 super
关键字,例如 super.propertyName
。
1.6 接口
在 Kotlin 中,接口(Interfaces)是一种定义行为和功能的方式,类可以实现一个或多个接口来继承接口中定义的方法和属性。接口定义了一组要求,而实现接口的类需要提供对应的实现。
以下是定义接口的基本语法:
interface InterfaceName {
// 接口中的属性和方法
}
接口中可以包含属性和方法声明,但不能包含实际的实现代码。接口的属性可以是抽象的(没有初始值)或具有访问器的属性(可以有自定义的 getter 和 setter)。接口的方法可以是抽象的(没有实现体)或具有默认实现。
接口的实现使用 :
符号,类可以通过关键字 implements
或者直接通过 :
实现接口。
以下是一个简单的接口示例:
interface Printable {
fun print()
}
class Document : Printable {
override fun print() {
println("Printing document...")
}
}
在上面的示例中,我们定义了一个名为 Printable
的接口,它声明了一个抽象的方法 print()
。然后,我们创建了一个类 Document
,它实现了 Printable
接口,并提供了对应的实现。
要注意的是,在类中实现接口的方法时,需要使用 override
关键字进行标记,以明确指示这是对接口方法的实现。
使用接口的示例:
val document = Document()
document.print()
在上述示例中,我们创建了一个 Document
类的实例,并调用了实现的 print()
方法。输出结果将是 “Printing document…”。
接口可以用于在不同的类之间建立共同的协议,并实现多态性。类可以实现多个接口,通过逗号分隔。
interface Interface1 {
// ...
}
interface Interface2 {
// ...
}
class MyClass : Interface1, Interface2 {
// ...
}
1.7 抽象类
在 Kotlin 中,抽象类(Abstract Class)是一种不能被实例化的类,它可以包含抽象方法和非抽象方法。抽象类用于提供一个基类的模板,定义通用的属性和方法,但需要子类来实现其中的抽象方法。
以下是定义抽象类的基本语法:
abstract class AbstractClassName {
// 抽象方法和非抽象方法
}
抽象类使用 abstract
关键字进行标记,其中包含的抽象方法没有实现体,而非抽象方法可以有实现。
子类继承抽象类时,必须提供对抽象方法的具体实现。如果子类不提供对抽象方法的实现,那么子类也必须声明为抽象类。
以下是一个简单的抽象类示例:
abstract class Shape {
abstract fun calculateArea(): Double
fun printDescription() {
println("This is a shape.")
}
}
class Circle(private val radius: Double) : Shape() {
override fun calculateArea(): Double {
return Math.PI * radius * radius
}
}
在上面的示例中,Shape
是一个抽象类,它声明了一个抽象方法 calculateArea()
和一个非抽象方法 printDescription()
。然后,我们创建了一个 Circle
类,它继承自 Shape
抽象类,并提供了对 calculateArea()
方法的具体实现。
需要注意的是,在子类中重写抽象方法时,必须使用 override
关键字进行标记,以明确指示这是对抽象方法的实现。
使用抽象类的示例:
val circle = Circle(5.0)
circle.printDescription()
val area = circle.calculateArea()
println("Area: $area")
在上述示例中,我们创建了一个 Circle
类的实例,并调用了从抽象类 Shape
继承的 printDescription()
方法。然后,我们计算了圆的面积,并将其打印出来。
抽象类是一种有助于提供代码复用和约束子类行为的工具。抽象类可以包含具体的属性和方法,以及抽象的方法,这使得抽象类可以充当一种中间层,实现通用的逻辑,并强制子类提供特定的行为。