你可能对面向对象编程不陌生而且非常熟悉类抽象。Kotlin在这方面的的概念,你可能会非常熟悉。但是将会发现可以使用更少的代码来实现许多常见的任务。我们将会在第四章详细的讨论类。
首先,让我们来看看JavaBean的Person类,尽管它只有一个属性,name

/* Java */
public class Person {
    private final String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

在Java中,构造器主体包含的代码通常是完全重复的:它把参数值赋给对应名字的字段。在Kotlin中,这个逻辑无需如此多的样板代码来表达。
 在1.5.6章节,我们介绍了Java-Kotlin转换器:一个能够自动用同等高效的Kotlin代码替代Java代码的工具。让我们来看看转换器的工作原理,并转化Person为Kotlin代码:


class Person(val name: String)

看好了,它是不是你想象的那样?如果你有用过其他的现代JVM语言,你可能看过相似的东西。这种类型的类通常被叫做值对象。许多语言提供了一个精简的语法声明它们。相对于Java版本来说,用Kotlin风格写成的如此简单的类更加容易处理。
注意,代码从Java转换为Kotlin的过程中修饰器public不见了。在Kotlin中public是默认的可见性,因此,你也可以忽略它。

2.2.1 属性

如你所确信的那样,类的思想是封装数据以及用于将数据转换为单一实体的代码的。在Java中,数据存储在通常是私有的字段中。如果你需要让类的客户端访问数据,你可以提供访问方法:获取函数,可能还有设置函数。你已经在Person类中看到一个这样的例子。设置函数也可以包含额外的逻辑来验证传递的值,发送更改的通知等等。
 在Java中,字段和它的访问器组合通常被称作属性。许多的框架大量使用这个概念。在Kotlin中,属性是一个能完全替代字段和访问器函数的一等语言特性。你可以像什么变量那样在类中声明一个属性:使用valvar关键词。一个声明为val的属性是只读的。相反的,var属性是易变的和可被改变的:


class Person(
    val name: String,        // 1 只读属性生成和琐碎获取器
    var isMarried: Boolean   // 2 可写属性:一个字段,一个获取函数和一个设置函数
)

基本上,当你声明一个属性时,你要声明对应的访问器(只读属性需要获取器,可写属性获取器两者)。默认情况下,访问器的实现都是琐碎的:一个的创建是为值,获取函数和设置函数返回和更新字段值。但是,如果你想的话,你可以声明一个使用不同的逻辑来计算或更新属性值的自定义访问器。
 先前的Person的简洁声明隐藏了跟原始Java代码相同的本质实现:它是一个有着被构造函数初始化并且能够通过相应的获取函数进行访问的私有属性的类。这意味着你能够从Java和Kotlin以同样的方式使用这这个类,跟在哪里声明它无关。用法看上去是一样的。以下是你如何能够通过Java代码使用Person类(的示例):


/* Java */
>>> Person person = new Person("Bob", true);
>>> System.out.println(person.getName());
Bob
>>> System.out.println(person.isMarried());
true

注意,当Person类在Java和Kotlin中有定义时,结果看起来是相同的。Kotlin的name属性在Java中暴露为一个叫做getName()的获取函数。对于布尔值属性来说,使用到了一个特殊的获取函数命名规则:属性名以is开头,获取函数没有添加额外的前缀。因此,在Java中你可以调用isMarried()
如果你把之前的代码转换成Kotlin代码,你将会得到以下结果:


>>> val person = Person("Bob", true)   // 1 
>>> println(person.name)  // 2 
Bob
>>> println(person.isMarried)  // 2 
true

// 1 你可以不使用new关键词来调用构造函数
// 2 你可以直接访问属性,但是会调用获取函数

现在,你可以直接引用属性而不是调用获取函数。逻辑保持不变,但是代码变得更精简了。易变属性的设置器以同样的方式工作:在Java中,你使用person.setMarried(false)来表示离婚,而在Kotlin中,你可以写成 person.isMarried = false

TIP  Properties of Java classes
 你也可以为Java中的类使用Kotlin属性语法。Java类中的获取函数可以作为Kotlin中的val属性进行访问,同时,获取器/设置器对可以作为var属性进行访问。举个例子,如果一个Java类定义了叫做getName()setName()的方法,你可以把它作为一个叫做name的属性进行访问。如果它定义了isMarried()setMarried()方法,Kotlin中对应的属性名将会是isMarried。
 大多数情况下,属性有一个用来存储属性值的对应的字段。但是如果属性值能够在忙碌状态下被计算出来,举个例子,通过其他属性,你也可以使用自定义的获取函数来表达它。

自定义的获取器

这个章节向你展示了如何写一个自定义的属性访问器的实现。假定你声明一个能说出它是否为正方形的矩形类。你不需要独立的字段来存储信息,因为你能够不停地检查高度和宽度是否相等:


class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() {    // 属性的获取函数声明
            return height == width
        }
}

isSquare属性不需要一个字段来存储它的值。它只有一个自定义的已经提供实现的获取函数。每一次属性被访问时,它的值都会被重新计算。
 注意,你不需要使用大括号中的完整语法。你也可以写成get() = height ==width。这个属性的调用保持不变:


>>> val rectangle = Rectangle(41, 43)
>>> println(rectangle.isSquare)
false

如果你需要从Java访问这个属性,你可以像往常那样调用isSquare()
  你可能会问声明一个没有形式参数的函数或者没有自定义访问器的属性是否会更好。这两个选项是相似的:(它们的)实现或者执行是没有差别的,它们只是在可读性方面有所不同。通常的,如果你描述一个类的特征(属性),你应该把它声明为一个(类的)属性。
  在第4章,我们将会展示更多的使用类和属性的例子。同时我们也会着眼于显式声明构造器的语法。如果你此时此刻已经迫不及待了,你可以一直使用Java-to-Kotlin代码转换器。在我们继续讨论其他语言特性时之前,现在先让我们来简要的检查Kotlin代码在磁盘中是是如何组织的。

2.2.3 Kotlin源代码布局:目录和包结构

你(已经)知道Java是如何将所有的类组织成一个个的包的。Kotlin也有包这个概念。而且跟Java中的非常相似。每一个Kotlin文件可以在(文件)开头有一个package声明。所有定义在文件中的声明(类、函数和属性)都会被放置在这个包里面。如果(它们)在同一个包里面,其他文件中定义的声明也能够被直接使用。如果不在同一个包,它们需要被导入。跟Java一样,导入声明放在文件的开头,同样是使用import关键字。
 这有一个源文件示例来展示包声明和导入语法:


package geometry.shapes    // 1 包声明

import java.util.Random    // 2 导入标准的Java类库

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() = height == width
}

fun createRandomRectangle(): Rectangle {
    val random = Random()
    return Rectangle(random.nextInt(), random.nextInt())
}

Kotlin并没有在导入类和函数之间做区别。它允许你使用import关键字导入任何类型的声明。你可以通过名字导入顶层的函数:


package geometry.example

import geometry.shapes.createRandomRectangle     // 1 通过名字来导入函数

fun main(args: Array<String>) {
    println(createRandomRectangle().isSquare)    // 2 很少概率会打印true
}

你也可以通过在报名后面添加.*来导入定义在某一个的包里所有的声明。注意这个通配符导入不仅会是得包内所有的类可见,也会使得顶层函数可见。在前一个示例中,写上import geometry.shapes.*而不是显式的导入也能让代码正确编译。
  在Java中,你把你的类放到一个匹配包结构的文件或目录结构下。举个例子,如果你有一个叫做shapes的包,这个包有多个类。你需要 每一个类放到一个文件名匹配,同时目存放在一个叫做shapes的目录中的独立文件里。图2.2展示了geometry包和它的子包在Java中是如何组织的。(我们)假设createRandomRectangle函数位于一个单独的文件RectangleUtil中。

2-2
图2.2 Java中,目录层级跟包结构层级一样的。
  在Kotlin中,你可以把多个类放到同一个文件中并且可以为文件选择任意的名字。Kotlin并不会对在源文件在磁盘中的布局强加任何的限制。你可以使用任意的目录结构来组织你的文件。举个例子,你可以在shapes.kt文件中定义geometry.shapes包所有的内容并把这个文件放在geometry文件夹中但并不需要创建一个单独的shapes文件夹(见图2.3)。

2.3
 图 2.3 你的包层级并不需要跟随目录的层级

 然而,在大部分情况下,跟随Java目录布局和根据包结果将源文件组成到目录中依然是一个很好的实践。在JavaKotlin混合代码中保持这样的一个结构是相当重要的。因为这样做能让你平滑的实现代码的迁移而不会导致其他的疑惑。但是你应该毫不犹豫的将多个类拉到同一个文件中,尤其是类很小的时候(在Kotlin中,类经常是很小的)。
 现在,你知道程序是如何组织的了。让我们继续学习基本的概念以及Kotlin中的控制结构吧!

results matching ""

    No results matching ""