Kotlin是一种现代编程语言,可编译为Java字节码。 它是免费的, 开放 源码 ,并承诺使编码为Android更有趣。
在上一篇文章中 ,您了解了Kotlin中函数的高级用法,例如扩展函数,闭包,高阶函数和内联函数。
在本文中,您将通过学习类来了解Kotlin中面向对象编程的知识:构造函数和属性,转换以及Kotlin简化的更高级的类功能。
1.班级
类是将功能和数据组合在一起以执行一些相关任务的程序单元。 我们使用class
关键字在Kotlin中声明一个类,类似于Java。
class Book
前面的代码是最简单的类声明-我们刚刚创建了一个名为Book
的空类。 即使它不包含使用其默认构造函数的主体,我们仍然可以实例化此类。
val book = Book()
正如您在上面的代码中所观察到的,我们没有使用new
关键字实例化此类,这在其他编程语言中很常见。 new
在Kotlin中不是关键字。 这在创建类实例时使我们的源代码简洁。 但是请注意,在Java中实例化Kotlin类将需要new
关键字。
// In a Java file
Book book = new Book()
类的构造函数和属性
让我们研究一下如何向我们的类中添加构造函数和属性。 但是首先,让我们看一下Java中的典型类:
/* Java */
public class Book {
private String title;
private Long isbn;
public Book(String title, Long isbn) {
this.title = title;
this.isbn = isbn;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Long getIsbn() {
return isbn;
}
public void setIsbn(Long isbn) {
this.isbn = isbn;
}
}
在上面的Book
模型类中,我们有以下内容:
- 两个字段:
title
和isbn
- 单个构造函数
- 这两个字段的获取器和设置器(很幸运,IntelliJ IDEA可以帮助我们生成这些方法)
现在让我们看看如何用Kotlin编写前面的代码:
/* Kotlin */
class Book {
var title: String
var isbn: Long
constructor(title: String, isbn: Long) {
this.title = title
this.isbn = isbn
}
}
一个很整洁的课! 现在,我们将代码行数从20条减少到了9条。Constructor constructor()
函数在Kotlin中称为辅助构造函数 。 此构造函数等效于实例化类时调用的Java构造函数。
令人惊讶的是,这些属性的getter和setter是由Kotlin编译器在幕后为我们自动生成的。 请注意,我们没有为这些属性指定任何可见性修饰符,因此默认情况下它们是公开的。 换句话说,可以从任何地方访问它们。
让我们看一下Kotlin中同一类的另一个版本:
class Book constructor(title: String, isbn: Long) {
var title: String
var isbn: Long
init {
this.title = title
this.isbn = isbn
}
}
在这段代码中,我们删除了辅助构造函数。 取而代之的是,我们在类头中声明了一个构造函数,称为主要构造函数 。 主构造函数没有放置任何代码块的位置,因此我们利用init
修饰符初始化来自主构造函数的传入参数。 请注意,创建类实例后,立即执行init
代码块。
如您所见,我们的代码仍然有很多样板。 让我们进一步减少它:
class Book constructor(var title: String, var isbn: Long)
现在,我们的Book
类只是一行代码。 太棒了! 注意,在主要构造函数参数列表中,我们使用var
关键字直接在主要构造函数内定义了可变属性: title
和isbn
。
我们还可以将默认值添加到构造函数内部的任何类属性中。
class Book constructor(var title: String = "default value", var isbn: Long)
事实上,我们也可以省略constructor
的关键字,但只有当它没有任何可见性修饰符( public
, private
,或protected
)或任何注释。
class Book (var title: String = "default value", var isbn: Long)
我必须说,这是一个非常整洁的课!
现在,我们可以创建一个这样的类实例:
val book = Book("A Song of Ice and Fire", 9780007477159)
val book2 = Book(1234) // uses the title property's default value
访问和设置属性
在Kotlin中,我们可以通过类对象book
来获取属性,然后是点分隔符.
,然后是属性名称title
。 这种访问属性的简洁样式称为属性访问语法。 换句话说,我们不必像在Java中那样调用属性getter方法来访问或调用setter来在Kotlin中设置属性。
println(book.title) // "A Song of Ice and Fire"
由于isbn
属性是使用var
关键字(读写)声明的,因此我们还可以使用赋值运算符=
来更改属性值。
book.isbn = 1234
println(book.isbn) // 1234
让我们看另一个例子:
class Book (
var title: String,
val isbn: Long
)
val book = Book("A Song of Ice and Fire", 9780007477159)
book.isbn = 1234 // error: read-only property
book.title = "Things Fall Apart" // reassigned title with value
在这里,我们通过使用val
关键字将isbn
参数更新为不可变(只读)。 我们实例化了一个类实例book
并为title
属性重新分配了值“ Things Fall Apart”。 请注意,当我们尝试将isbn
属性值重新分配为1234
,编译器抱怨。 这是因为使用val
关键字定义了该属性是不可变的。
Java互操作性
请注意,通过在主要构造函数中使用var
修饰符声明参数,Kotlin编译器(在后台)帮助我们生成了属性访问器:getter和setter。 如果使用val
,它将仅生成吸气剂。
/* Kotlin */
class Book (
var title: String,
val isbn: Long
)
这意味着Java调用者可以分别通过分别调用属性的setter或getter方法来简单地获取或设置属性字段。 请记住,这取决于用于定义Kotlin属性的修饰符: var
或val
。
/* Java */
Book book = new Book("A Song of Ice and Fire", 9780385474542)
println(book.getTitle()) // "A Song of Ice and Fire"
book.setTitle("Things Fall Apart") // sets new value
println(book.getTitle()) // "Things Fall Apart"
book.getIsbn() // 9780385474542
book.setIsbn(4545454) // won't compile
自定义获取器和设置器
在本节中,我将向您展示如何根据需要在Kotlin中为属性创建自定义访问器(获取器和设置器)。 如果要在将值设置为类属性之前对其进行验证或验证,则创建自定义设置器会很有用。 当您要更改或修改应返回的值时,自定义属性获取器可能会很有用。
创建自定义设置器
因为我们要为属性创建自己的自定义getter或setter,所以必须在类主体中定义该属性,而不是在构造函数标头中定义。
class Book (val isbn: Long) {
var title = "default value"
}
这就是为什么我们将可变(读写) title
属性移到类主体中并为其赋予默认值的原因(否则它将无法编译)。
class Book (val isbn: Long) {
var title = "default value"
set(value) {
if (!value.isNotEmpty()) {
throw IllegalArgumentException("Title must not be empty")
}
field = value
}
}
您可以看到我们在属性定义的下方为title
定义了自己的setter方法set(value)
-请注意,您不能修改此set()
方法签名,因为这是编译器期望的自定义属性setter函数。
传递给set
方法的参数value
代表用户分配给属性的实际值-您可以根据需要更改参数名称,但value
是更可取的。 我们通过检查值是否为空来验证该value
。 如果为空,则停止执行并引发异常。 否则,将值重新分配给特殊field
变量。
set
方法中的此特殊field
变量字段是属性的后备字段的别名-后备字段仅是您要修改或使用该字段数据时属性使用的字段。 与value
不同,您不能重命名此特殊field
变量。
创建一个自定义吸气剂
为Kotlin中的属性创建自定义吸气剂非常容易。
class Book (val isbn: Long) {
var title = "default value"
//... set method
get() {
return field.toUpperCase()
}
}
在get
方法内部,我们仅返回一个修改后的field
-在本例中,我们以大写形式返回了书名。
val book = Book(9780007477159)
book.title = "A Song of Ice and Fire"
println(book.title) // "A SONG OF ICE AND FIRE"
println(book.isbn) // 9780007477159
请注意,每次我们为title
属性设置一个值时,都会执行其set
方法块-每次检索它的get
方法都一样。
有关构造函数的更多信息
正如我之前所讨论的,在Kotlin中,我们有两种类型的构造函数:主构造函数和辅助构造函数。 我们可以自由地将它们合并在一个类中,如下面的示例所示:
class Car(val name: String, val plateNo: String) {
var new: Boolean = true
constructor(name: String, plateNo: String, new: Boolean) : this(name, plateNo) {
this.new = new
}
}
注意,我们不能像辅助构造函数那样在辅助构造函数内声明属性。 如果要执行此操作,则必须在类主体中声明它,然后在辅助构造函数中对其进行初始化。
在上面的代码中,我们为Car
类设置了new
属性的默认值(请记住, new
不是Kotlin中的关键字)—然后,我们可以根据需要使用辅助构造函数对其进行更改。 在Kotlin中,每个辅助构造函数都必须调用主构造函数,或者调用另一个调用该主构造函数的辅助构造函数—我们使用this
关键字来实现这一点。
还要注意,我们可以在一个类中包含多个辅助构造函数。
class Car(val name: String, val plateNo: String) {
var new: Boolean? = null
var colour: String = ""
constructor(name: String, plateNo: String, new: Boolean) : this(name, plateNo) {
this.new = new
}
constructor(name: String, plateNo: String, new: Boolean, colour: String ) :
this(name, plateNo, new) {
this.colour = colour
}
}
如果类扩展了超类,则可以使用super
关键字(类似于Java)来调用超类构造函数(我们将在以后的文章中讨论Kotlin的继承)。
// directly calls primary constructor
val car1 = Car("Peugeot 504", "XYZ234")
// directly calls 1st sec. constructor
val car2 = Car("Peugeot 504", "XYZ234", false)
// directly calls last sec. constructor
val car3 = Car("Peugeot 504", "XYZ234", false, "grey")
如前所述,要在类中显式包含构造函数的可见性修饰符,我们必须包含constructor
关键字-默认情况下,构造函数是公共的。
class Car private constructor(val name: String, val plateNo: String) {
//...
在这里,我们将构造函数设为私有 -这意味着用户无法直接使用其构造函数实例化对象。 如果您希望用户改为调用另一个方法(一种工厂方法)来间接创建对象,这将很有用。
2.任意类型
在Kotlin中,类型层次结构中最顶层的类型称为Any
。 这等效于Java Object
类型。 这意味着Kotlin中的所有类都显式地继承自Any
类型,包括String
, Int
, Double
等。 Any
类型包含三种方法: equals
, toString
和hashcode
。
我们还可以在总是返回异常的函数中使用Kotlin中的Nothing
类,换句话说,就是那些不会正常终止的函数。 当函数返回Nothing
,我们知道它将引发异常。 Java中不存在此类的等效类型。
fun throwException(): Nothing {
throw Exception("Exception message)
}
在单元测试中测试错误处理行为时,这可以派上用场。
3.可见性修改器
可见性修饰符可帮助我们限制API对公众的可访问性。 我们可以为类,接口,对象,方法或属性提供不同的可见性修饰符。 Kotlin为我们提供了四个可视性修改器:
上市
这是默认设置,任何具有此修饰符的类,函数,属性,接口或对象都可以从任何地方访问。
私人的
声明为private
的顶级函数,接口或类只能在同一文件中访问。
在类,对象或接口内声明为private
任何函数或属性,仅对该相同类,对象或接口的其他成员可见。
class Account {
private val amount: Double = 0.0
}
受保护的
protected
修饰符只能应用于类,对象或接口内的属性或函数,而不能应用于顶级函数,类或接口。 带有此修饰符的属性或函数只能在定义它的类和任何子类中访问。
内部
在具有模块(Gradle或Maven模块)的项目中,只能在该模块内部访问用该模块internal
声明的internal
修饰符指定的类,对象,接口或函数。
internal class Account {
val amount: Double = 0.0
}
4.智能铸造
转换意味着获取另一个类型的对象并将其转换为另一个对象类型。 例如,在Java中,我们先使用instanceof
运算符确定特定对象类型是否为另一类型,然后再进行转换。
/* Java */
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
circle.calCircumference(3.5);
}
如您所见,我们检查了shape
实例是否为Circle
,然后必须将shape
引用显式转换为Circle
类型,以便可以调用circle
类型的方法。
关于Kotlin的另一个令人敬畏的事情是,它在转换时的编译器很聪明。 现在让我们看看Kotlin的版本。
/* Kotlin */
if (shape is Circle) {
shape.calCircumference(3.5)
}
漂亮整齐! 编译器很聪明地知道,仅当shape
对象是Circle
的实例时才执行if
块,因此对我们来说,强制转换机制已经完成。 现在,我们可以轻松地在if
块内调用Circle
类型的属性或函数。
if (shape is Circle && shape.hasRadius()) {
println("Circle radius is {shape.radius}")
}
在这里,仅当第一个条件为true
时,才会调用if
头中&&
之后的最后一个条件。 如果shape
不是Circle
,则不会评估最后一个条件。
5.显式投射
我们可以使用as
运算符(或不安全的强制转换运算符)将类型的引用显式转换为Kotlin中的另一种类型。
val circle = shape as Circle
circle.calCircumference(4)
如果显式强制转换操作非法,请注意将抛出ClassCastException
。 为了防止在转换时引发异常,我们可以将安全的转换运算符(或可as?
空的转换运算符) as?
。
val circle: Circle? = shape as? Circle
6.对象
Kotlin中的对象比Java对象更类似于JavaScript对象。 请注意,Kotlin中的对象不是特定类的实例!
对象与类非常相似。 以下是Kotlin中对象的一些特征:
- 它们可以具有属性,方法和一个
init
块。 - 这些属性或方法可以具有可见性修改器。
- 他们不能有构造函数(主要的或辅助的)。
- 他们可以扩展其他类或实现接口。
现在让我们深入研究如何创建对象。
object Singleton {
fun myFunc(): Unit {
// do something
}
}
我们将object
关键字放在要创建的对象的名称之前。 实际上,当我们使用object
构造在Kotlin中创建对象时,我们正在创建单例,因为仅存在一个对象实例。 当我们讨论与Java的对象互操作性时,您将学到更多有关此的知识。
您可以在项目中的任何位置访问对象或单例对象,只要您导入其包即可。
Singleton.myFunc()
如果您是Java编码器,那么这通常是我们创建单例的方式:
public class Singleton {
private static Singleton INSTANCE = null;
// other instance variables can be here
private Singleton() {};
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return(INSTANCE);
}
// other instance methods can follow
}
如您所见,使用Kotlin object
构造使创建单例变得简洁明了。
Kotlin中的对象也可以用来创建常量。 通常,在Java中,我们通过将其设为公共的static final字段来在类中创建常量,如下所示:
public final class APIConstants {
public static final String baseUrl = "https://www.myapi.com/";
private APIConstants() {}
}
可以将Java中的这段代码更简洁地转换为Kotlin:
package com.chike.kotlin.constants
object APIConstants {
val baseUrl: String = "http://www.myapi.com/"
}
在这里,我们宣布的不断APIConstants
与属性baseUrl
包内com.chike.kotlin.constants
。 在baseUrl
,为我们创建了一个Java私有静态最终成员baseUrl
,并使用字符串URL对其进行了初始化。
要在Kotlin的另一个软件包中使用此常量,只需导入该软件包。
import com.chike.kotlin.constants.APIConstants
APIConstants.baseUrl
Java互操作性
Kotlin在后台将对象转换为最终的Java类。 此类具有一个私有静态字段INSTANCE
,该字段包含该类的单个实例(单个实例)。 以下代码显示了用户如何简单地从Java调用Kotlin对象。
/* Java */
Singleton.INSTANCE.myFunc()
在此,使用公共静态最终成员INSTANCE
(包括公共最终函数myFunc()
生成了一个名为Singleton
的Java类。
为了使Kotlin中的对象函数或属性成为生成的Java类的静态成员,我们使用@JvmStatic
批注。 使用方法如下:
object Singleton {
@JvmStatic fun myFunc(): Unit {
// do something
}
}
通过将@JvmStatic
批注应用于myFunc()
,编译器已将其转换为静态函数。
现在,Java调用者可以像普通的静态成员调用一样调用它。 请注意,使用INSTANCE
静态字段来调用成员仍然可以使用。
/* Java */
Singleton.myFunc()
7.伴侣对象
现在我们已经了解了Kotlin中的对象,让我们深入研究另一种称为伴侣对象的对象。
因为Kotlin不支持Java中的静态类,方法或属性,所以Kotlin团队为我们提供了一种更强大的替代方法,称为伴随对象 。 伴随对象基本上是属于一个类的对象,此类被称为对象的伴随类。 这也意味着我为对象提到的特征也适用于伴随对象。
创建伴侣对象
与Java中的静态方法类似,伴随对象不是与类实例相关联,而是与类本身相关联,例如,工厂静态方法具有创建类实例的工作。
class Person private constructor(var firstName: String, var lastName: String) {
companion object {
fun create(firstName: String, lastName: String): Person = Person(firstName, lastName)
}
}
在这里,我们将构造函数设为private
-这意味着类外部的用户无法直接创建实例。 在我们的伴随对象块中,我们有一个函数create()
,它创建一个Person
对象并返回它。
调用伴侣对象函数
companion
对象实例化是惰性的。 换句话说,它将仅在第一次需要时实例化。 当创建companion
类的实例或访问companion
对象成员时,将发生companion
对象的实例化。
让我们看看如何在Kotlin中调用伴随对象函数。
val person = Person.create("Cersei", "Lannister")
println(person.firstName) // prints "Cersei"
如您所见,这就像正常调用Java中的静态方法一样。 换句话说,我们只调用类,然后调用成员。 请注意,除了函数之外,我们还可以在随播对象中包含属性。
class Person private constructor(var firstName: String, var lastName: String) {
init {
count++
}
companion object {
var count: Int = 0
fun create(firstName: String, lastName: String): Person = Person(firstName, lastName)
init {
println("Person companion object created")
}
}
}
还要注意, companion
类可以无限制地访问其同伴对象中声明的所有属性和函数,而同伴对象则不能访问该类的成员。 我们可以在companion
对象内部有一个init
代码块-在创建companion
对象时立即调用该代码块 。
Person.create("Arya", "Stark")
Person.create("Daenerys", "Targaryen")
println(Person.count)
执行以上代码的结果将是:
Person companion object created
2
请记住,只能有一个类companion
对象的单个实例。
我们也可以自由地为我们的伴随对象提供名称。
// ...
companion object Factory {
var count: Int = 0
fun create(firstName: String, lastName: String): Person = Person(firstName, lastName)
}
// ...
在这里,我们给它起了一个叫Factory
的名字。 然后我们可以在Kotlin中这样称呼它:
Person.Factory.create("Petyr", "Baelish")
这种样式比较冗长,因此更倾向于采用以前的方式。 但这在从Java调用伴随对象函数或属性时可能会派上用场。
就像我之前说的,同伴对象也可以包括属性或函数,实现接口,甚至扩展一个类。
interface PersonFactory {
fun create(firstName: String, lastName: String): Person
}
class Person private constructor(var firstName: String, var lastName: String) {
companion object : PersonFactory {
override fun create(firstName: String, lastName: String): Person {
return Person(firstName, lastName)
}
}
}
在这里,我们有一个PersonFactory
接口,只有一个create()
函数。 现在看一下我们新的修改后的companion
对象,它实现了此接口(您将在后面的文章中了解Kotlin中的接口和继承)。
Java互操作性
在幕后,伴随对象的编译类似于Kotlin对象的编译方式。 在我们自己的情况下,为我们生成了两个类:final Person
类和内部静态final类Person$Companion
。
Person
类包含一个称为Companion
的最终静态成员—该静态字段是Person$Companion
内部类的对象。 Person$Companion
内部类也有自己的成员,其中一个是称为create()
的公共最终函数。
注意,我们没有给同伴对象命名,因此生成的静态内部类是Companion
。 如果我们给它起了一个名字,那么生成的名字就是我们在Kotlin中给它起的名字。
/* Java */
Person person = Person.Companion.create("Jon", "Snow");
在这里,Kotlin中的伴随对象没有名称,因此我们使用编译器为Java调用者提供的名称Companion
来调用它。
应用于@JvmStatic
对象成员的@JvmStatic
注释的工作方式与常规对象的工作方式类似。
伴侣对象扩展
class ClassA {
companion object {
}
}
fun ClassA.Companion.extFunc() {
// ... do implementation
}
ClassA.extFunc()
在这里,我们在伴随对象ClassA.Companion
上定义了扩展函数extFunc()
。 换句话说, extfunc()
是伴随对象的扩展。 然后,我们可以调用扩展,就好像它是伴随对象的成员函数一样(不是!)。
在后台,编译器将创建一个静态实用程序函数extFunc()
。 作为该实用程序功能的参数的接收者对象是ClassA$Companion
。
结论
在本教程中,您学习了Kotlin中的基本类和对象。 我们介绍了有关类的以下内容:
另外,您还了解了Kotlin中的对象和伴随对象如何轻松替换Java中编写的静态方法,常量和单例。 但这还不是全部! 关于Kotlin的课程,还有更多的知识要学习。 在下一篇文章中,我将向您展示Kotlin为面向对象编程提供的更酷的功能。 再见!
翻译自: https://code.tutsplus.com/tutorials/kotlin-from-scratch-classes-and-objects--cms-29590