Kotlin 类和属性(五)

导读大纲

    • [1.1 封装行为和数据: 类和属性](#1.1 封装行为和数据: 类和属性)
      • [1.1.1 将数据与类关联并使其可被访问: 属性](#1.1.1 将数据与类关联并使其可被访问: 属性)
      • [1.1.2 计算属性,而不是存储其值: 自定义访问器](#1.1.2 计算属性,而不是存储其值: 自定义访问器)
      • [1.1.3 Kotlin 源代码目录和包](#1.1.3 Kotlin 源代码目录和包)

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

  1. 与其他面向对象编程语言一样,Kotlin 也提供类的抽象
    • Kotlin 在这方面的概念您一定不会陌生,但会发现与其他面向对象编程语言相比
      1. Kotlin 可以用更少的代码完成许多常见任务
    • <1> Person类---简单的普通 Java 对象(plain old Java object--POJO)
      1. 到目前为止只包含一个属性: name
java 复制代码
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. 尤其是只包含数据而不包含代码的类
kotlin 复制代码
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
kotlin 复制代码
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属性
kotlin 复制代码
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
kotlin 复制代码
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
kotlin 复制代码
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 保持不变
kotlin 复制代码
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"关键字来导入所需的包
kotlin 复制代码
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. 但是不允许直接导入包 , 试图通过包名来访问其下定义的声明
    • <1> 如果您正在使用geometry.examples包编写项目,那么只需通过名称导入
      1. 就可以使用geometry.shapes包中的类Rectangle和函数createUnitSquare
kotlin 复制代码
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 文件夹
  4. 不过,在大多数情况 下,按照 Java 的目录布局

    • 根据包结构将源文件组织到目录仍然是一种好的做法
    • 在 Kotlin 与 Java 混合使用的项目 中,坚持使用这种结构尤为重要
      1. 因为这样做可以逐步迁移代码 ,而不会带来任何意外
    • 不过,你也不应该犹豫是否要将多个类放到同一个文件
      1. 尤其是当这些类很小 的时候(在 Kotlin 中,它们通常都很小)
相关推荐
zquwei9 分钟前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
TT哇16 分钟前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
火烧屁屁啦38 分钟前
【JavaEE进阶】初始Spring Web MVC
java·spring·java-ee
飞飞-躺着更舒服42 分钟前
【QT】实现电子飞行显示器(改进版)
开发语言·qt
w_31234541 小时前
自定义一个maven骨架 | 最佳实践
java·maven·intellij-idea
岁岁岁平安1 小时前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA1 小时前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
Q_19284999061 小时前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
张国荣家的弟弟1 小时前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S2 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring