Kotlin从零开始:类和对象

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模型类中,我们有以下内容:

  • 两个字段: titleisbn
  • 单个构造函数
  • 这两个字段的获取器和设置器(很幸运,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关键字直接在主要构造函数内定义了可变属性: titleisbn

我们还可以将默认值添加到构造函数内部的任何类属性中。

class Book constructor(var title: String = "default value", var isbn: Long)

事实上,我们也可以省略constructor的关键字,但只有当它没有任何可见性修饰符( publicprivate ,或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属性的修饰符: varval

/* 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类型,包括StringIntDouble等。 Any类型包含三种方法: equalstoStringhashcode

我们还可以在总是返回异常的函数中使用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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值