Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. Kotlin
Code

Kotlin从头开始:高级属性和类

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Kotlin From Scratch.
Kotlin From Scratch: Classes and Objects
Kotlin From Scratch: Abstract Classes, Interfaces, Inheritance, and Type Alias

Chinese (Simplified) (中文(简体)) translation by Zhang Xiang Liang (you can also view the original English article)

Kotlin  是一种编译为Java字节码的现代编程语言。它是免费的,  开源,并有望使编码为Android更有趣。

在  前面的文章中,您了解了Kotlin类和对象。在本教程中,我们将继续学习更多关于属性的内容,并通过探索以下内容来深入了解Kotlin中的高级类型:

  • 后期初始化的属性
  • 内联属性 
  • 扩展属性
  • 数据,枚举,嵌套和密封类

1.后期初始化属性

我们可以在Kotlin中声明一个非空属性作为后期初始化。这意味着非空属性在声明时不会被初始化,并且值实际的初始化不会通过任何构造函数发生 - 相反,它将通过方法或依赖注入来延迟初始化。

我们来看一个例子来理解这个独特的属性修饰符。

在上面的代码中,我们声明了一个可变的nullable repository属性,它是类型RepositoryPresenter- 在类中- 然后我们在声明过程中将此属性初始化为null。 我们 在Presenter类中有一个initRepository()方法,稍后用实际的Repository实例重新初始化这个属性。请注意,该属性也可以使用像Dagger这样的依赖注入器来赋值。    

现在,我们要调用此repository 属性的方法或属性  ,我们必须执行空检查或使用安全调用操作符。为什么? 因为该repository属性是可空类型(Repository?)。(如果您需要进一步了解Kotlin的可空性,请访问Nullability,Loops和Conditions)

为了避免每次我们需要调用属性方法时进行空检查,我们可以使用lateinit修饰符标记该属性  - 这意味着我们已经声明该属性(这是另一个类的实例)为后期初始化的(意思是属性稍后将被初始化)。  

现在,只要我们等待该属性被赋予一个值,我们就可以安全地访问该属性的方法而不进行任何空的检查。属性初始化可以通过setter方法或通过依赖注入来实现。 

请注意,如果我们在初始化之前尝试访问属性的方法,我们将得到一个  kotlin.UninitializedPropertyAccessException 而不是一个  NullPointerException。在这种情况下,异常消息将是“lateinit属性存储库尚未初始化”。 

当使用以下命令延迟属性初始化时,还要注意以下限制lateinit:

  • 它必须是可变的(用var声明)。
  • 属性类型不能是基本类型,例如,  IntDoubleFloat,等。
  • 该属性不能有自定义的getter或setter。

2.  内联属性

在  高级函数中,我引入了inline高阶函数的修饰符 - 这有助于优化任何接受lambda作为参数的高阶函数。

在Kotlin中,我们也可以inline在属性上使用这个修饰符。使用此修饰符将优化对该属性的访问。

我们来看一个实际的例子。

在上面的代码中,我们有一个普通的属性,  nickName,没有  inline修饰符。如果我们反编译代码片段,使用Show Kotlin Bytecode功能(如果您使用的是IntelliJ IDEA或Android Studio,请使用  Tools  > Kotlin > Show Kotlin Bytecode),我们将看到以下Java代码:

在上面生成的Java代码中(为了简洁,删除了生成代码的某些元素),您可以看到在该main()方法内部,编译器创建了一个Student名为getNickName() 方法的对象,然后打印其返回值。  

现在我们来指定该属性  inline ,并比较生成的字节码。

我们只需在变量varval修饰符之前插入inline  修饰符:  。 以下是为这个内联属性生成的字节码:

再次删除了一些代码,但要注意的关键是该main()方法。编译器复制属性get()函数体,并将其粘贴到调用点(这种机制类似于内联函数)。 

我们的代码已经过优化,因为不需要创建对象并调用属性getter方法。但是,正如在内联函数中所讨论的那样,我们会有比以前更大的字节码,所以谨慎使用。 

还要注意,这种机制可以用于没有后台字段的属性(记住,当你想修改或使用字段数据时,后台字段只是属性使用的字段)。 

3.  扩展属性 

在  高级函数中,  我还讨论了扩展函数 - 这些扩展函数使我们能够扩展具有新功能的类,而不必从该类继承。Kotlin还为属性提供了一种类似的机制,称为  扩展属性。 

在  高级函数后,  我们 用接收器String类型定义了一个扩展函数uppercaseFirstLetter() 。 在这里,我们已将其转换为顶级扩展属性。请注意,您必须在您的属性上定义一个getter方法才能使其工作。 

因此,借助关于扩展属性的新知识,您将知道如果您希望某个类应该有一个不可用的属性,则可以自由创建该类的扩展属性。 

4.  数据类

让我们从一个典型的Java类或POJO(Plain Old Java Object)开始。 

正如你所看到的,我们需要明确的代码的类属性访问器:getter和setter,以及  hashcode,  equalstoString 方法(虽然IntelliJ IDEA的,Android的工作室,或AutoValue库可以帮助我们来生成)。  我们在一个典型的Java项目的数据层中主要看到这种样板代码。(为了简洁起见,我删除了字段访问器和构造器)。

很酷的是,Kotlin团队为我们提供了data修改器,以避免编写这些样板文件。

现在让我们在Kotlin中写上前面的代码。

真棒!我们只是data 在class 关键字之前指定  修饰符  来创建数据类,就像我们在BlogPost 上面的Kotlin类中所做的一样  。 现在  equals,  hashcode,  toString,  copy,和多组分方法将引擎盖为我们下创建的。请注意,数据类可以扩展其他类(这是Kotlin 1.1的新功能)。

equals 方法

该方法比较两个对象是否相等,如果相等则返回true,否则返回false。换句话说,它比较两个类实例是否包含相同的数据。 

在Kotlin中,使用相等运算符==将equals 在幕后调用该  方法。

hashCode 方法 

此方法返回在用于快速存储,并存储在基于散列的集合数据结构的数据的检索,例如一个整数值  HashMap 和  HashSet 集合类型。

toString 方法

这个方法返回一个String 对象的  表示。

通过调用类实例,我们得到一个返回给我们的字符串对象 - Kotlin为我们调用toString() 对象  。但是如果我们不把data 关键字放进去  ,看看我们的对象字符串表示是什么: 

更少的信息!

copy 方法

这个方法允许我们创建一个具有所有相同属性值的对象的新实例。换句话说,它创建了对象的副本。 

Kotlin中关于copy方法的一件很酷的事情就是在复制期间改变属性的能力。 

如果您是Java编码器,则此方法与clone() 您已熟悉的方法类似  。但是Kotlin  copy 方法具有更强大的功能。 

破坏性声明

在  Person 类中,由于data 关键字放置在类中,因此编译器会自动为我们生成两个方法  。这两个方法以“component”为前缀,后跟一个数字后缀:  component1(),  component2()。 每种方法都表示该类型的各个属性。请注意,后缀对应于在主构造函数中声明的属性的顺序。

因此,在我们的示例中,调用  component1() 将返回名字,并且调用  component2() 将返回最后的名字。

尽管使用这种风格调用属性很难理解和阅读,所以明确调用属性名称要好得多。但是,这些隐式创建的属性确实有一个非常有用的用途:它们让我们执行一个解构声明,其中我们可以将每个组件分配给一个局部变量。

我们在这里所做的是直接将Person类型的第一个和第二个属性(firstName 和  lastName)   分别赋值给变量firstName 和  变量  lastName。我还在Packages和Basic Functions  的最后一部分  讨论了称为解构声明的机制   。 

5.  嵌套类

在 文章 更有趣的函数,我告诉你,Kotlin支持本地函数或嵌套函数 - 这是在另一个函数内声明的函数。那么,Kotlin也同样支持嵌套类 - 在另一个类中创建的类。

我们甚至可以调用嵌套类的公共函数,如下所示 - Kotlin中的static 嵌套类相当于Java中的  嵌套类。请注意,嵌套类不能存储对其外部类的引用。 

我们也可以自由地将嵌套类设置为私有 - 这意味着我们只能NestedClass 在该范围内  创建一个实例  OuterClass。 

内部类

内部类,在另一方面,可以参考它在声明的外部类。要创建一个内部类,我们把该  inner 关键字前  class 在嵌套类的关键字。

在这里,我们引用  OuterClass 从  InnerClass 使用   this@OuterClass。

6.  枚举类

枚举类型声明由标识符表示的一组常量。这种特殊的类是由关键字enum之前指定的class关键字创建的。 

要根据名称检索枚举值(就像在Java中一样),我们这样做:

或者我们可以使用Kotlin enumValueOf()辅助方法以通用方式访问常量:

另外,我们可以像这样获得所有的值(例如Java枚举类型):

最后,我们可以使用Kotlin enumValues()辅助方法以通用方式获取所有枚举条目:

这将返回一个包含枚举条目的数组。  

枚举构造函数

就像一个普通的类一样,enum类型可以有自己的构造函数,其属性与每个枚举常量相关联。

在Country枚举类型的主构造函数中,我们callingCodes为每个枚举常量定义了不可变属性。在每个常量中,我们向构造函数传递了一个参数。 

然后我们可以像这样访问常量属性:

7.密封类

Kotlin中的密封类是一个抽象类(你从来不打算从它创建对象),其他类可以扩展。这些子类是在密封的类体中定义的 - 在同一个文件中。因为所有这些子类都是在密封的类体内定义的,所以我们可以通过查看文件来了解所有可能的子类 因为所有这些子类都是在密封的类体内定义的,所以我们可以通过查看文件来了解所有可能的子类。 

我们来看一个实际的例子。

为了将类声明为密封类,我们在类声明头中的sealed修饰符之前插入修饰符class- 在我们的例子中,我们将该Shape类声明为sealed。一个密封的类没有它的子类是不完整的 - 就像一个典型的抽象类 - 所以我们必须在同一个文件中声明各个子类(在这种情况下是shape.kt)。请注意,您不能从另一个文件中定义密封类的子类。  请注意,您不能从另一个文件中定义密封类的子类。 

在上面的代码中,我们指定的Shape类只能通过类来扩展  Circle,  Triangle和  Rectangle

Kotlin的密封课程有以下附加规则:

  • 我们可以将修饰符添加  abstract 到密封类,但这是多余的,因为密封类默认是抽象的。
  • 密封的类不能有  open或final修饰符。 
  • 我们也可以自由地将数据类和对象声明为一个密封类的子类(它们仍然需要在同一个文件中声明)。 
  • 密封类不允许使用公共构造函数 - 它们的构造函数默认是私有的。 

扩展密封类的子类的类可以放在同一个文件或其他文件中。密封的类子类必须使用open 修饰符标记  (在下一篇文章中,您将在Kotlin中学习更多关于继承的知识)。 

一个密封的类和它的子类在when表达式中非常方便。例如:

这里编译器很聪明,可以确保我们涵盖了所有可能的  when情况。这意味着没有必要添加该else 条款。 

如果我们要这样做:

代码不会编译,因为我们没有包含所有可能的情况。我们会遇到以下错误:

因此,我们可以包括is Rectangle案例或包含else条款来完成when表达式。

结论

在本教程中,您了解了更多关于Kotlin的课程。我们介绍了关于类属性的以下内容:

  • 后期初始化
  • 内联属性 
  • 扩展属性

此外,您还了解了一些很酷和高级的类,例如数据,枚举,嵌套和密封类。在Kotlin From Scratch系列的下一个教程中,您将介绍Kotlin中的接口和继承。再见!

要了解有关Kotlin语言的更多信息,我建议访问  Kotlin文档。或者在Envato Tuts上查看我们的其他一些Android应用程序开发帖子!

Envato qr branded
关注我们的公众号
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.