自学内容网 自学内容网

Kotlin 类和属性(五)

1.1 封装行为和数据: 类和属性

  1. 与其他面向对象编程语言一样,Kotlin 也提供类的抽象
    • Kotlin 在这方面的概念您一定不会陌生,但会发现与其他面向对象编程语言相比
      1. Kotlin 可以用更少的代码完成许多常见任务
    • <1> Person类—简单的普通 Java 对象(plain old Java object–POJO)
      1. 到目前为止只包含一个属性: name
public class Person {                          // <1>
    private final String name;                 // <1>
    public Person(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
  1. 在 Java 中,这需要一些相当冗长的代码

    • 构造函数的主体完全是重复
      1. 因为它只是将参数赋值给具有相应名称的字段
    • 按照惯例,要访问name字段,Person 类还应提供一个 getter 函数
      1. 即getName, 该函数也只是返回字段的内容
    • 这种重复在 Java 中经常发生
      1. 在 Kotlin 中,这种逻辑的表达不需要这么多模板
  2. 在导读部分中,我们介绍 Java-to-Kotlin 转换器:

    • 这是一种能自动将 Java 代码替换为做相同事情的 Kotlin 代码的工具
    • 让我们来看看转换器的实际操作,将 Person 类转换为 Kotlin 代码
      1. 直接将上述Java代码拷贝并粘贴到Kotlin源文件中
    • <1> Kotlin 提供一种简洁的语法来声明类
      1. 尤其是只包含数据而不包含代码的类
class Person(val name : String)               // <1>
  1. 请注意,修饰符 public 在从 Java 转换到 Kotlin 的过程中消失
    • 在 Kotlin 中,public 是默认的,因此可以省略

1.1.1 将数据与类关联并使其可被访问: 属性

  1. 类的概念将数据和处理这些数据的代码封装成一个实体

    • 在 Java 中,数据存储在字段中,这些字段通常是私有
    • 如果需要让类的客户端访问这些数据
      1. 需要提供访问方法(accessor methods):一个getter和一个setter
    • setter还可以包含验证传递值发送更改通知等附加逻辑
  2. 在 Java 中,字段及其访问器(accessors)的组合通常被称为属性

    • 在 Kotlin 中,属性是一等语言特性,完全取代fields和accessor方法
    • 在类中声明属性的方式与声明变量的方式相同: 使用 val 和 var 关键字
      1. 声明为 val 的属性是只读的,而 var 属性是可变的,可以更改
    • <1> 例如, Person 类已包含一个只读的 name 属性
      1. 可以用一个可变的 isStudent 属性来扩展该类
    • <2> 只读属性–生成一个field和一个getter
    • <3> 可写属性–field、getter 和 setter
    • 基本上,当你声明一个属性时,你就声明相应的访问器(accessors)
      1. 对于只读属性一个 getter
      2. 对于可写属性一个 getter 和一个 setter
class Person(                             // <1>
    val name: String,                     // <2>
    var isStudent: Boolean                // <3>
)
  1. 默认情况下,这些访问器的实现非常简单

    • 创建一个字段来存储值,getter 返回该字段值,setter 更新其值
    • 但如果需要,可以声明一个自定义访问器
      1. 使用不同的逻辑计算或更新属性值
    • 上述简洁的 Person 类声明隐藏与原始 Java 代码相同的底层实现
      1. 这是一个带有私有字段的类,在构造函数中初始化
        • 并可通过相应的 getter 访问
    • 这意味着可以在 Java 和 Kotlin 中 以相同的方式使用这个类
      1. 无需考虑它是在哪里声明的, 使用方法看起来完全相同
  2. 下面是如何在 Java 代码中使用 Kotlin 的 Person 类

    • 创建一个名为 Bob 且是学生的的新 Person
    • 请注意, Person 在 Java 和 Kotlin 中的定义看起来是一样
      1. Kotlin 的 name 属性作为名为 getName 的 getter 方法暴露给 Java
    • getter 和 setter 的命名规则有一个例外:
      1. 如果属性名以 is 开头, getter 将不添加额外的前缀
        • 在 setter 名称中,is 将被 set 代替
    • <1> 在Java中调用 isStudent() 和 setStudent() 来访问isStudent属性
public class Demo {
    public static void main(String[] args) {
        Person person = new Person("Bob", true);
        System.out.println(person.getName());
        // Bob
        System.out.println(person.isStudent());       // <1>
        // true
        person.setStudent(false);                     // <1>
        // Graduation!
        System.out.println(person.isStudent());       // <1>
        // false
    }
}
// =========================================
Bob
true
false
  1. 将上述Java调用转换为Kotlin代码
    • <1> 调用构造函数时不使用 new 关键字
    • <2> 直接访问属性,但会调用 getter
      1. 现在,你不再显式调用 getter,而是直接引用属性
      2. 逻辑保持不变,但代码更加简洁
    • <3> 直接赋值,但会调用 setter
      1. 在 Java 中,可以使用 person.setStudent(false) 来表示毕业
      2. 而在 Kotlin 中,可以直接对属性进行赋值:person.isStudent=false
object Demo {
    @JvmStatic
    fun main(args: Array<String>) {
        val person = Person("Bob", true)   // <1>
        println(person.name)               // <2>
        // Bob
        println(person.isStudent)          // <2>
        // true
        person.isStudent = false           // <3>
        // Graduation!
        println(person.isStudent)
        // false
    }
}
  1. 您也可以对 Java 中定义的类使用 Kotlin 属性语法
    • 在 Kotlin 中, Java类中的Getters作为 val 属性访问
      1. 而getter-setter对可作为 var 属性访问
    • 例如,如果一个 Java 类定义名为 getName 和 setName 的方法
      1. 可以将其作为名为 name 的属性进行访问
    • 如果该类定义 isStudent 和 setStudent 方法
      1. 对应的 Kotlin 属性名称是 isStudent
class Person2 {
    private final String name;
    private Boolean isStudent;
    public Person2(String name, Boolean isStudent){
        this.name = name;
        this.isStudent = isStudent;
    }
    public String getName() {
        return name;
    }
    public Boolean isStudent() {
        return isStudent;
    }
    public void setStudent(Boolean isStudent) {
        this.isStudent = isStudent;
    }
}
==========================================
val person2 = Person2("Bob2", true)
println(person2.name)
// Bob
println(person2.isStudent)
// true
person2.isStudent = false
// Graduation!
println(person2.isStudent)
// false
  1. 在大多数情况下,属性会有一个相应的备选字段用来存储属性值
    • 如果属性值需要即时计算(例如,通过从其他属性派生)
      1. 您可以使用自定义 getter 来表达

1.1.2 计算属性,而不是存储其值: 自定义访问器

  1. 一种常见的情况是,属性是对象中其他属性的直接结果
    • 如果您有一个存储宽度和高度的Rectangle类
      1. 可以提供一个当宽度和高度相等时为 true 的isSquare属性
    • 由于这是一个属性,因此可以"随用随查",在访问时进行计算
      1. 无需将该信息存储为一个单独的字段
    • <1> 相反,您可以提供一个自定义的getter
      1. 每次访问该属性都会计算矩形的"方形度"
    • <2> 不必使用带大括号的完整语法,即使用表达式体语法定义getter
      1. 表达式体语法还允许省略属性类型由编译器为你推断类型
      2. 表达式体函数–传送门
    • <3> 无论您选择哪种语法,调用 isSquare 保持不变
class Rectangle(val height: Int, val width: Int){
    val isSquare: Boolean                 // <1>
        get() {                           // <1>
            return height == width
        }
}
================================== <2>
class Rectangle(val height: Int, val width: Int){
    val isSquare
        get() = height == width          // <2>
}
========================================== 
fun main() {
    val rectangle = Rectangle(41, 43)
    println(rectangle.isSquare)         // <3>
    // false
}
  1. 如果需要从 Java 访问该属性,则需要以前一样调用 isSquare 方法
    • 您可能会问,是用自定义 getter 声明一个属性好
      1. 还是在类中定义一个函数(在Kotlin中称为成员函数或方法)好
    • 这两种方法差不多:在实现或性能上没有区别,它们的区别只在于可读性
      1. 一般来说,如果要描述一个类的特性,应将其声明为属性
      2. 如果要描述类的行为,则应选择成员函数

1.1.3 Kotlin 源代码目录和包

  1. 随着程序越来越复杂,由越来越多的函数、类和其他语言结构组成

    • 不可避免地需要开始考虑如何组织源代码
      1. 以便保持项目的可维护性和可浏览性
    • 让我们来看看 Kotlin 项目通常是如何组织的
  2. Kotlin 使用包的概念来组织类(类似于您熟悉的 Java)

    • 每个 Kotlin 文件的开头都可以有一个包语句
      1. 文件中定义的所有声明(类、函数和属性)都将放在该包
    • <1> 下表显示一个源文件示例,其中显示包声明语句的语法
      1. 如果在同一软件包中,可以直接使用其他文件中定义的声明
      2. 如果在不同软件包中,则需要导入
        • 这需要在包声明的下方使用"import"关键字来导入所需的包
package geometry.shapes                                  // <1>
class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() = height == width
}
fun createUnitSquare(): Rectangle {
    return Rectangle(1, 1)
}
  1. Kotlin 并不区分导入类还是导入函数
    • 它允许使用 import 关键字导入任何类型的声明
      1. 但是不允许直接导入包, 试图通过包名来访问其下定义的声明
        title
    • <1> 如果您正在使用geometry.examples包编写项目,那么只需通过名称导入
      1. 就可以使用geometry.shapes包中的类Rectangle和函数createUnitSquare
package geometry.examples                   // <1>
import geometry.shapes.Rectangle            // <1>
import geometry.shapes.createUnitSquare     // <1>
fun main() {
    println(Rectangle(3, 4).isSquare)
    // false
    println(createUnitSquare().isSquare)
    // true
}
  1. 您还可以导入特定包中定义的所有声明,方法是在包名后写".*"

    • 请注意,这种星形导入(也称为通配符导入)使包中定义的所有内容都可见
      1. 不仅包括类,还包括顶层函数和属性
    • 比如使用import geometry.shapes.*代替显式导入可以使代码正确编译
  2. 在 Java 中,您需要将类放入与包结构相匹配的文件和目录结构

    • 例如,如果您有一个名为 shapes 的包,其中包含多个类
      1. 那么您需要将每个类放入一个名称匹配的单独文件
        • 并将这些文件存储在一个名为 shapes 的目录
    • 下面的列表显示geometry包及其子包在 Java 中的组织结构
      1. 假设 createUnitSquare 函数位于名为 RectangleUtil 的单独文件中
        目录层次结构遵循包层次结构
  3. 在 Kotlin 中,您可以将多个类放在同一个文件中,并为该文件选择任意名称

    • Kotlin也没有对源文件在磁盘上的布局施加任何限制
      1. 您可以使用任何目录结构来组织文件
    • 例如,您可以在 shapes.kt 文件中定义包 geometry.shapes 的所有内容
      1. 将该文件放在 geometry 文件夹
      2. 无需创建单独的 shapes 文件夹
        title
  4. 不过,在大多数情况下,按照 Java 的目录布局

    • 根据包结构将源文件组织到目录仍然是一种好的做法
    • 在 Kotlin 与 Java 混合使用的项目中,坚持使用这种结构尤为重要
      1. 因为这样做可以逐步迁移代码,而不会带来任何意外
    • 不过,你也不应该犹豫是否要将多个类放到同一个文件
      1. 尤其是当这些类很小的时候(在 Kotlin 中,它们通常都很小)

原文地址:https://blog.csdn.net/qq_36154755/article/details/142309834

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!