Chinese (Simplified) (中文(简体)) translation by Zhang Xiang Liang (you can also view the original English article)
Kotlin 是一种编译为Java字节码的现代编程语言。它是免费的, 开源,并有望使Android开发更有趣。
在 前面的文章中,您了解了更多关于Kotlin属性的内容,例如后期初始化,扩展和内联属性。不仅如此,您还了解了Kotlin中的高级类,如数据,枚举,嵌套和密封类。
在这篇文章中,您将继续通过学习抽象类,接口和继承来了解Kotlin中的面向对象编程。为了获得奖励,您还将了解类型别名。
1. 抽象类
Kotlin支持抽象类 - 就像Java一样,这些类是你永远不打算创建对象的类。如果没有一些具体的(非抽象的)子类,抽象类是不完整的或无用的,你可以从中实例化对象。抽象类的具体子类实现抽象类中定义的所有方法和属性 - 否则该子类也是抽象类! 我们用abstract 修饰符创建一个抽象类 (类似于Java)。
我们用abstract 修饰符创建一个抽象类 (类似于Java)。
abstract class Employee (val firstName: String, val lastName: String) { abstract fun earnings(): Double }
请注意,并非所有成员都必须是抽象的。换句话说,我们可以在抽象类中实现方法默认实现。
abstract class Employee (val firstName: String, val lastName: String) { // ... fun fullName(): String { return lastName + " " + firstName; } }
这里我们fullName() 在抽象类中 创建了非抽象函数 Employee。具体类(抽象类的子类)可以重写抽象方法的默认实现 - 但只有当方法具有open指定的修饰符时(您将在短时间内了解更多内容)。
我们也可以在抽象类中存储状态。
abstract class Employee (val firstName: String, val lastName: String) { // ... val propFoo: String = "bla bla" }
即使抽象类没有定义任何方法,我们也需要在实例化之前创建一个子类,如下例所示。
class Programmer(firstName: String, lastName: String) : Employee(firstName, lastName) { override fun earnings(): Double { // calculate earnings } }
我们的Programmer课程扩展了Employee抽象类。在Kotlin中,我们使用单个冒号字符(:)而不是Java extends 关键字来扩展类或实现接口。
然后,我们可以创建一个类型对象 Programmer 并调用它的方法 - 无论是在它自己的类还是超类(基类)中。
val programmer = Programmer("Chike", "Mgbemena") println(programmer.fullName()) // "Mgbemena Chike"
有一件事可能会让你大吃一惊,那就是我们有能力用var
(mutable)重写一个 val
(不可变的)属性 。
open class BaseA (open val baseProp: String) { } class DerivedA : BaseA("") { private var derivedProp: String = "" override var baseProp: String get() = derivedProp set(value) { derivedProp = value } }
确保你明智地使用这个功能!请注意,我们无法使用val
反向重写 var
属性 。
2.接口
接口只是一个相关方法的集合,它通常使您能够告诉对象要执行的操作,以及默认情况下如何执行此操作。(接口中的默认方法是添加到Java 8的新特性。)换句话说,接口是实现类必须遵守的合约。
使用interface Kotlin中的关键字定义接口 (类似于Java)。
class Result class Student interface StudentRepository { fun getById(id: Long): Student fun getResultsById(id: Long): List<Result> }
在上面的代码中,我们已经声明了一个StudentRepository
接口。这个接口包含两个抽象方法:getById
()和getResultsById
()。请注意,abstract在接口方法中包含关键字是多余的,因为它们已经是隐式抽象的。 .
没有一个或多个实现者,接口是没有用的 - 所以我们创建一个实现这个接口的类。
class StudentLocalDataSource : StudentRepository { override fun getResults(id: Long): List<Result> { // do implementation } override fun getById(id: Long): Student { // do implementation } }
这里我们创建了一个StudentLocalDataSource
实现StudentRepository
接口的类 。
我们使用override
修饰符来标记要从接口或超类重新定义的方法和属性 - 这与@OverrideJava
中的注释类似。
请注意Kotlin中以下附加的接口规则:
- 一个类可以实现尽可能多的接口,但它只能扩展一个类(类似于Java)。
- 该 override修改是在Java中的必修课科特林-不同。
- 除了方法外,我们还可以在Kotlin界面中声明属性。
- Kotlin接口方法可以有一个默认实现(类似于Java 8)。
我们来看一个具有默认实现的接口方法的例子。
interface StudentRepository { // ... fun delete(student: Student) { // do implementation } }
在前面的代码中,我们添加了delete()一个默认实现的新方法(尽管我没有为演示目的添加实际的实现代码)。
如果需要,我们也可以自由重写默认实现
class StudentLocalDataSource : StudentRepository { // ... override fun delete(student: Student) { // do implementation } }
如上所述,Kotlin接口可以具有属性 - 但请注意,它不能保持状态。(但是,记住抽象类可以维护状态。)所以下面的带有属性声明的接口定义将起作用。
interface StudentRepository { val propFoo: Boolean // will work // ... }
但是,如果我们试图通过给属性赋值来向接口添加一些状态,它将无法工作。
interface StudentRepository { val propFoo: Boolean = true // Error: Property initializers are not allowed in interfaces // .. }
然而,Kotlin中的接口属性可以有getter和setter方法(尽管只有后者如果该属性是可变的)。另请注意,接口中的属性不能有后台字段。
interface StudentRepository { var propFoo: Boolean get() = true set(value) { if (value) { // do something } } // ... }
如果需要,我们也可以覆盖接口属性,以便重新定义它。
class StudentLocalDataSource : StudentRepository { // ... override var propFoo: Boolean get() = false set(value) { if (value) { } } }
让我们来看看我们有一个类实现了多个接口的情况下,使用相同的方法签名。这个类如何决定调用哪个接口方法?
interface InterfaceA { fun funD() {} } interface InterfaceB { fun funD() {} }
这里我们有两个接口具有相同签名的方法funD()。让我们创建一个实现这两个接口并覆盖该funD()方法的类。
class classA : InterfaceA, InterfaceB { override fun funD() { super.funD() // Error: Many supertypes available, please specify the one you mean in angle brackets, e.g. 'super<Foo>' } }
编译器对调用super.funD()方法感到困惑,因为类实现的两个接口具有相同的方法签名。
为了解决这个问题,我们用尖括号将我们想要调用方法的接口名称包装起来。(IntelliJ IDEA或Android Studio会在您遇到问题时向您提供有关解决此问题的提示。)
class classA : InterfaceA, InterfaceB { override fun funD() { super<InterfaceA>.funD() } }
在这里,我们将要调用InterfaceA
的 funD
() 方法。问题解决了!
3.继承
通过获取现有类的(超类)成员并可能重新定义其默认实现来创建新类(子类)。这种机制在面向对象编程(OOP)中被称为继承。使Kotlin如此精彩的一件事是,它包含了面向对象和函数式编程范例 - 所有这些都在一种语言中。 Kotlin中所有类的基类都是Any。
Kotlin中所有类的基类都是Any。
class Person : Any { }
该Any类型与Object我们在Java中使用的类型相同。
public open class Any { public open operator fun equals(other: Any?): Boolean public open fun hashCode(): Int public open fun toString(): String }
该Any
类型包含以下成员: equals
(),hashcode
()以及toString
()方法(类似于Java)。
我们的类不需要明确地扩展这种类型。如果您没有明确指定新类继承的类,则该类将Any隐式扩展。出于这个原因,您通常不需要包含: Any 在代码中,我们在上面的代码中进行演示。 .
现在让我们考虑在Kotlin中创建类,并考虑继承。
class Student { } class GraduateStudent : Student() { }
在上面的代码中,GraduateStudent
类扩展了超类Student
。但是这段代码不会编译。为什么?因为类和方法final默认在Kotlin中。 换句话说,默认情况下它们不能被扩展 - 不像默认情况下打开类和方法的Java。
软件工程最佳实践建议您final
默认开始制作类和方法 - 即,如果它们不是专门用于在子类中重新定义或重写的。Kotlin团队(JetBrains)在开发这种现代语言时运用了这种编码理念和更多开发最佳实践。
对于我们允许从超类创建子类,我们必须用open修饰符明确地标记超类 。此修饰符也适用于应由子类覆盖的任何超类属性或方法。
open class Student { }
我们只需open在class关键字前加上修饰符。我们现在已经指示编译器允许我们的Student类可以被扩展。
如前所述,Kotlin课程的成员默认也是最终的。
open class Student { open fun schoolFees(): BigDecimal { // do implementation } }
在前面的代码中,我们将该schoolFees函数标记为 - open子类可以覆盖它。
open class GraduateStudent : Student() { override fun schoolFees(): BigDecimal { return super.schoolFees() + calculateSchoolFees() } private fun calculateSchoolFees(): BigDecimal { // calculate and return school fees } }
在这里,Student
超类的开放函数schoolFees
被GraduateStudent
类重写- 通过override
在fun
关键字之前添加修饰符。请注意,如果您重写超类或接口的成员,则覆盖成员也将为open
默认值,如下例所示:
class ComputerScienceStudent : GraduateStudent() { override fun schoolFees(): BigDecimal { return super.schoolFees() + calculateSchoolFess() } private fun calculateSchoolFess(): BigDecimal { // calculate and return school fees } }
尽管我们没有用修饰符open
在GraduateStudent
类中标记方法schoolFees
(),但我们仍然可以重写它 - 就像我们在ComputerScienceStudent
类中所做的那样。为了防止这种情况发生,我们必须将最重要的成员标记为final
。
请记住,我们可以通过在Kotlin中使用扩展函数向类添加新功能,即使它是最终的。有关扩展功能的更新,请查看我在Kotlin post中的高级功能。 另外,如果您需要了解如何在不继承最终类新属性的情况下进行回顾,请参阅高级属性和类 帖子中关于扩展属性的部分 。
如果我们的超类具有这样的主构造函数:
open class Student(val firstName: String, val lastName: String) { // ... }
然后任何子类都必须调用超类的主构造函数。
open class GraduateStudent(firstName: String, lastName: String) : Student(firstName, lastName) { // ... }
我们可以GraduateStudent照常创建一个类的对象:
val graduateStudent = GraduateStudent("Jon", "Snow") println(graduateStudent.firstName) // Jon
如果子类想从其次级构造函数调用超类构造函数,那么我们使用super关键字(类似于如何在Java中调用超类构造函数)。
open class GraduateStudent : Student { // ... private var thesis: String = "" constructor(firstName: String, lastName: String, thesis: String) : super(firstName, lastName) { this.thesis = thesis } }
如果你需要复习Kotlin中的类构造函数,请访问我的 类和对象 文章。
4.奖金:输入别名
我们可以在Kotlin中做的另一件令人敬畏的事情是给一个类型一个别名。
我们来看一个例子。
data class Person(val firstName: String, val lastName: String, val age: Int)
在上面的类中,我们可以使用Kotlin中的修饰符为属性别名分配String和Int类型 。该修饰符用于在Kotlin中创建任何类型的别名 - 包括您创建的别名。 Persontypealias
typealias Name = String typealias Age = Int data class Person(val firstName: Name, val lastName: Name, val age: Age)
正如你所看到的,我们已经创建了一个别名Name和
Age
在,类型分别为String
和Int
类型。 现在我们已更换firstName
和lastName
属性类型,以我们的别名Name
-和Int
类型Age
别名。请注意,我们没有创建任何新的类型 - 我们为这些类型创建了一个别名。
当您想要为您的Kotlin代码库中的类型提供更好的含义或语义时,这些可以很方便。所以明智地使用它们!
结论
在本教程中,您了解了Kotlin中更多关于面向对象编程的知识。我们涵盖以下内容:
- 抽象类
- 接口
- 遗产
- 类型别名
如果您通过我们的Kotlin From Scratch系列学习了Kotlin,请确保您输入了您看到的代码并在IDE上运行它。真正掌握一门新的编程语言(或任何编程概念)的一个很好的技巧就是要确保你不只是阅读学习资源或指南,而且还要输入实际的代码并运行它!
在Kotlin From Scratch 系列的下一个教程中 ,您将介绍Kotlin中的异常处理。再见!
要了解有关Kotlin语言的更多信息,我建议访问 Kotlin文档。或者在Envato Tuts上查看我们的其他一些Android应用程序开发帖子!
- ANDROID SDKJava vs. Kotlin:你应该使用Kotlin进行Android开发吗?Jessica Thornsby
- ANDROID SDKAndroid体系结构组件介绍Tin Megali
- Android SDK开始使用适用于Android的RxJava 2Jessica Thornsby