该书“头先。 科特林»

图片 嗨,habrozhiteli! 我们有一本书出版,使用Head First技术研究Kotlin,它超出了解决特定问题的语法和说明。 本书将为您提供所需的一切-从语言的基础到高级方法。 您可以练习面向对象和函数式编程。

剪辑下方显示摘录“数据类”。

处理数据


没有人愿意浪费时间并重做已经完成的事情。 大多数应用程序使用类来存储数据。 为了简化工作,Kotlin的创建者提出了数据类的概念。 在本章中,您将学习数据类如何帮助您编写更优雅,更简洁的代码,而这是您以前梦dream以求的。 我们将研究数据类的辅助函数,并学习如何将数据对象分解为组件。 同时,我们将描述默认参数值如何使代码更灵活,并向您介绍所有超类的祖先Any。

==运算符调用一个等于函数


如您所知,==运算符可用于验证相等性。 每次执行==语句时,都会调用一个称为equals的函数。 每个对象都包含一个equals函数,此函数的实现确定==运算符的行为。

默认情况下,用于检查相等性的equals函数检查两个变量引用是否指向同一对象。

要了解其工作原理,请想象两个名为w1和w2的Wolf变量。 如果w1和w2包含对一个Wolf对象的引用,则将它们与==运算符进行比较时,结果为true:

图片

但是,如果w1和w2包含对不同Wolf对象的引用,则将它们与==运算符进行比较,即使对象包含相同的属性值,其结果也为false。

图片

如前所述,equals函数自动包含在您创建的每个对象中。 但是此功能从何而来?

等于从超类Any继承


每个对象都包含一个称为equals的函数,因为其类从名为Any的类继承了一个函数。 Any类是所有类的祖先:一切的结果超类。 您定义的每个类都是Any的子类,您无需在程序中指出这一点。 因此,如果编写一个名为myClass的类代码,则如下所示:

class MyClass { ... } 

编译器将自动将其转换为以下格式:
图片

每个类都是Any的子类,并继承其行为。 每个类都是Any的子类,您不必在程序中报告此情况。

任何继承的重要性


包含Any作为生成的超类具有两个重要的优点:

  • 它确保每个类都继承通用行为。 Any类定义系统操作所依赖的重要行为。 而且由于每个类都是Any的子类,所以此行为将由您创建的所有对象继承。 因此,Any类定义了一个称为equals的函数,因此,该函数自动被所有对象继承。
  • 这意味着多态可以与任何对象一起使用。 每个类都是Any的子类,因此您创建的任何对象都将Any类作为其最终超类型。 这意味着您可以使用任何参数或任何返回类型创建一个可与任何类型的对象一起使用的函数。 这也意味着您可以使用以下形式的代码创建多态数组以存储任何类型的对象:

 val myArray = arrayOf(Car(), Guitar(), Giraffe()) 

编译器注意到数组中的每个对象都有一个通用的Any原型,因此创建了一个Array类型的数组。

Any类继承的一般行为值得仔细研究。

从Any继承的常见行为


Any类定义了每个类继承的几个函数。 以下是基本功能及其行为的示例:

  • 等于(任意:任意):布尔值
    检查两个对象是否被视为“相等”。 默认情况下,如果函数用于检查一个对象,则该函数返回true;否则,该函数返回false-用于不同的对象。 在幕后,每次在程序中使用==运算符时,都会调用equals函数。

 val w1 = Wolf() val w1 = Wolf() val w2 = Wolf() val w2 = w1 println(w1.equals(w2)) println(w1.equals(w2)) false (equals  false, true (equals  true,   w1  w2   w1  w2        .)      —   ,   w1 == w2. 

  • hashCode():整数
    返回对象的哈希码。 某些数据结构经常使用哈希码来有效地存储和检索值。

 val w = Wolf() println(w.hashCode()) 

523429237 (哈希码w的值)

  • toString():字符串
    返回表示对象的String消息。 默认情况下,该消息包含类名和数字,我们通常不关心它们。

 val w = Wolf() println(w.toString()) 

狼@ 1f32e575

默认情况下,equals函数检查两个对象是否是相同的实际对象。

equals函数确定==运算符的行为。

Any类为列出的所有功能提供默认实现,并且所有类都继承了这些实现。 但是,您可以覆盖这些实现以更改所有列出的功能的默认行为。

两个对象的简单等效检查


在某些情况下,您需要更改equals函数的实现以更改==运算符的行为。

假设您有一个Recipe类,该类可让您创建用于存储配方的对象。 在这种情况下,如果两个Recipe对象包含相同配方的描述,则可能会认为它们相等(或等效)。 假设Recipe类定义了两个属性-title和isVegetarian:

 class Recipe(val title: String, val isVegetarian: Boolean) { } 

如果==运算符用于比较两个具有相同属性title和isVegetarian的Recipe对象,则它将返回true:

 val r1 = Recipe("Chicken Bhuna", false) val r2 = Recipe("Chicken Bhuna", false) 

图片

尽管您可以通过编写其他代码来覆盖equals函数来更改==运算符的行为,但Kotlin开发人员提供了更为方便的解决方案:他们创建了数据类的概念。 让我们看看这些类是什么以及如何创建它们。

数据类允许您创建数据对象。


数据类是用于创建用于存储数据的对象的类。 它包括一些对数据有用的工具,例如equals函数的新实现,该函数检查两个数据对象是否包含相同的属性值。 如果两个对象包含相同的数据,则可以认为它们相等。

要定义数据类,请在常规数据定义之前添加data关键字。 以下代码将先前创建的Recipe类转换为数据类:

 data class Recipe(val title: String, val isVegetarian: Boolean) { } 

数据前缀将常规类转换为数据类。

如何基于数据类创建对象


数据类对象的创建方式与常规类对象相同:通过调用此类的构造函数。 例如,以下代码创建一个新的Recipe数据对象,并将其分配给名为r1的新变量:

 val r1 = Recipe("Chicken Bhuna", false) 

数据类自动覆盖它们的equals函数,以更改==运算符的行为,以便根据每个对象的属性值检查对象的相等性。 例如,如果您创建两个具有相同属性值的Recipe对象,则将两个对象与==运算符进行比较将得出结果为true,因为相同的数据存储在其中:

 val r1 = Recipe("Chicken Bhuna", false) val r2 = Recipe("Chicken Bhuna", false) //r1 == r2  true 

r1和r2被视为“相等”,因为两个Recipe对象包含相同的数据。

除了继承自超类Any,数据类的equals函数的新实现之外
还重写hashCode和toString函数。 让我们看看如何实现这些功能。

类对象重新定义其继承的行为


为了处理数据,数据类需要对象,因此它自动为从Any超类继承的equals,hashCode和toString函数提供以下实现:

equals函数比较属性值


定义数据类时,如果链接指向同一对象,则其equals函数(因此==运算符)仍返回true。 但是,如果对象具有在构造函数中定义的相同属性值,则它也会返回true:

 val r1 = Recipe("Chicken Bhuna", false) val r2 = Recipe("Chicken Bhuna", false) println(r1.equals(r2)) true 

如果数据对象的属性包含相同的值,则认为它们相等。

对于相等的对象,返回相同的hashCode值


如果两个数据对象被认为相等(换句话说,它们具有相同的属性值),则hashCode函数为这些对象返回相同的值:

 val r1 = Recipe("Chicken Bhuna", false) val r2 = Recipe("Chicken Bhuna", false) println(r1.hashCode()) println(r2.hashCode()) 

241131113
241131113

toString返回所有属性的值


最后,toString函数不再返回类名,后跟数字,而是返回一个有用的字符串,其中包含在数据类的构造函数中定义的所有属性的值:

 val r1 = Recipe("Chicken Bhuna", false) println(r1.toString()) Recipe(title=Chicken Bhuna, isVegetarian=false) 

除了覆盖从Any超类继承的函数之外,数据类还提供其他工具,这些工具可提供对数据的更有效处理,例如,复制数据对象的能力。 让我们看看这些工具如何工作。

使用复制功能复制数据对象


如果需要通过更改数据对象的某些属性来创建数据对象的副本,但将其他属性保留为原始状态,请使用复制功能。 为此,将为要复制的对象调用该函数,并将具有新值的所有可变属性的名称传递给该函数。

假设您有一个名为r1的Recipe对象,它在如下代码中定义:

 val r1 = Recipe("Thai Curry", false) 

图片

如果要创建Recipe对象的副本,将isVegetarian属性的值替换为true,可以这样进行:

图片

本质上,这意味着“创建r1对象的副本,将其isVegetarian属性的值更改为true,然后将新对象分配给名为r2的变量。” 这将创建该对象的新副本,并且原始对象保持不变。

除了复制功能外,数据类还提供了一组用于将数据对象拆分为一组属性值的功能-此过程称为解构。 让我们看看这是如何完成的。

数据类定义componentN ...函数


定义数据类时,编译器会自动向该类添加一组函数,这些函数可用作访问对象属性值的替代机制。 这些函数在componentN函数的通用名称下是已知的,其中N是要提取的属性数(按声明顺序)。

若要查看componentN函数如何工作,假设您具有以下Recipe对象:

 val r = Recipe("Chicken Bhuna", false) 

如果要获取对象的第一个属性的值(标题属性),可以为此调用对象的component1()函数:

 val title = r.component1() 

component1()返回包含在数据类的构造函数中定义的第一个属性中的引用。

该函数与以下代码相同:

 val title = r.title 

具有该功能的代码更加通用。 为什么ComponentN函数在数据类中如此有用?

...旨在重组数据对象


通用componentN函数之所以有用,主要是因为它们提供了一种简单方便的方法来将数据对象拆分为属性值或对其进行破坏。

假设您要获取Recipe对象的属性值,并将其每个属性的值分配给一个单独的变量。 代替代码

 val title = r.title val vegetarian = r.isVegetarian 

通过顺序处理每个属性,可以使用以下代码:

 val (title, vegetarian) = r 

为第一个属性r分配标题,为第二个属性分配素食。

该代码的意思是“创建两个变量title和Vegetarian,并分配每个变量r属性之一的值。” 他和下一个片段一样

 val title = r.component1() val vegetarian = r.component2() 

但事实证明它更紧凑。

===运算符始终检查两个变量是否引用同一对象。

如果要检查两个变量是否引用同一个对象,而不管它们的类型如何,请使用===运算符而不是==。 当(且仅当)两个变量包含对一个实际对象的引用时,===运算符将结果设为true。 如果您有两个变量x和y,以及以下表达式:

 x === y 

如果结果为true,则您知道变量x和y必须引用同一对象。

与==运算符不同,===运算符的行为独立于equals函数。 不管类的类型如何,===运算符的行为始终相同。

现在,您已经学习了如何创建和使用数据类,为食谱代码创建一个项目。

创建食谱项目


为JVM创建一个新的Kotlin项目,并将其命名为“ Recipes”。 然后创建一个新的
Kotlin文件,名为Recipes.kt:选择src文件夹,打开“文件”菜单,然后选择命令
新建→Kotlin文件/类。 输入文件名“ Recipes”,然后在“种类”组中选择“文件”选项。

我们将一个名为Recipe的新数据类添加到项目中,并创建Recipe数据对象。 下面是代码。 更新您的Recipes.kt版本,并将其与我们的版本保持一致:

 data class Recipe(val title: String, val isVegetarian: Boolean) (  {} ,        .) fun main(args: Array<String>) { val r1 = Recipe("Thai Curry", false) val r2 = Recipe("Thai Curry", false) val r3 = r1.copy(title = "Chicken Bhuna") (  r1    title) println("r1 hash code: ${r1.hashCode()}") println("r2 hash code: ${r2.hashCode()}") println("r3 hash code: ${r3.hashCode()}") println("r1 toString: ${r1.toString()}") println("r1 == r2? ${r1 == r2}") println("r1 === r2? ${r1 === r2}") println("r1 == r3? ${r1 == r3}") val (title, vegetarian) = r1 ( r1) println("title is $title and vegetarian is $vegetarian") } 

运行代码时,以下文本将显示在IDE的输出窗口中:

 r1 hash code: -135497891 r2 hash code: -135497891 r3 hash code: 241131113 r1 toString: Recipe(title=Thai Curry, isVegetarian=false) r1 == r2? true r1 === r2? false r1 == r3? false title is Thai Curry and vegetarian is false 


»这本书的更多信息可以在出版商的网站上找到
» 目录
» 摘录

Khabrozhitel- Kotlin的优惠券可享受25%的折扣

支付纸质版本的书后,就会通过电子邮件发送电子书。

Source: https://habr.com/ru/post/zh-CN473614/


All Articles