四、类和对象

在这一章中,我们将继续我们的 TicTacToe 游戏,同时学习 Kotlin 中的类和对象。

到本章结束时,我们将拥有:

  • 了解了 Kotlin 中的类和对象
  • 研究了游戏的部分逻辑

类的结构

就像 Java 一样,Kotlin 中的类是使用class关键字声明的。类的基本结构包括:

  • class关键词
  • 类别的名称
  • 朗读者
  • 用花括号括起来的类的主体

标头可以由主构造函数、父类(如果适用)和要实现的接口(如果适用)组成。

Of all four parts, only the first two are compulsory. If the class has no body, you can skip the curly braces.

构造器

就像在 Java 中一样,一个类可以有多个构造函数,但是在 Kotlin 中,主构造函数可以作为类头的一部分添加。

例如,让我们向HelloKotlin类添加一个构造函数:

import kotlinx.android.synthetic.main.activity_main.*

class HelloKotlin constructor(message: String) {

    fun displayKotlinMessage(view: View) {
        Snackbar.make(view, "Hello Kotlin!!",
        Snackbar.LENGTH_LONG).setAction("Action", null).show()
    }
}

在前面的代码中,HelloKotlin类有一个主构造函数,它接受一个名为message的字符串。

由于构造函数没有任何修饰符,我们可以完全去掉constructor关键字:

class HelloKotlin (message: String) {

    fun displayKotlinMessage(view: View) {
        Snackbar.make(view, "Hello Kotlin!!", 
        Snackbar.LENGTH_LONG).setAction("Action", null).show()
    }

}

在 Kotlin 中,辅助构造函数必须调用主构造函数。让我们看看代码:

class HelloKotlin (message: String) {

    constructor(): this("Hello Kotlin!!")

    fun displayKotlinMessage(view: View) {
        Snackbar.make(view, "Hello Kotlin!!", 
        Snackbar.LENGTH_LONG).setAction("Action", null).show()
    }
}

关于二级构造函数需要注意的几点:

  • 它不接受任何参数。
  • 它使用默认消息调用主构造函数。
  • 它没有使用花括号。这是因为它没有主体,因此不需要花括号。如果我们添加一个主体,我们将被要求使用花括号。

如果displayKotlinMessage()方法想利用构造函数中传递的message参数怎么办?

有两种方法可以解决这个问题。您可以在HelloKotlin中创建一个字段,并用传递的message参数初始化它:

class HelloKotlin (message: String) {

    private var msg = message

    constructor(): this("Hello Kotlin!!")

    fun displayKotlinMessage(view: View) {
        Snackbar.make(view, msg, 
        Snackbar.LENGTH_LONG).setAction("Action", null).show()
    }
}

您也可以将适当的关键字添加到message参数中,使其成为该类的一个字段:

class HelloKotlin (private var message: String) {

    constructor(): this("Hello Kotlin!!")

    fun displayKotlinMessage(view: View) {
        Snackbar.make(view, message, 
        Snackbar.LENGTH_LONG).setAction("Action", null).show()
    }
}

让我们带着我们所做的改变兜兜风。在MainActivity类的onCreate()方法中,让我们替换HelloKotlin初始化:

HelloKotlin().displayKotlinMessage(view)

我们将用一个传递消息的初始化来替换它:

HelloKotlin("Get ready for a fun game of Tic Tac Toe").displayKotlinMessage(view)

当我们单击浮动操作按钮时,传递的消息显示在底部。

数据类

在构建应用时,大多数时候我们需要的类的唯一功能是存储数据。在 Java 中,我们通常为此使用一个 POJO。在 Kotlin,有一个特殊的类叫做数据类

假设我们想为我们的 TicTacToe 游戏保留一个记分牌。我们将如何存储每个游戏会话的数据?

在 Java 中,我们将创建一个 POJO,它将存储关于游戏会话的数据(游戏结束时的棋盘和该游戏的赢家):

public class Game {

    private char[][] gameBoard;
    private char winner;

    public Game(char[][] gameBoard, char winner) {
        setGameBoard(gameBoard);
        setWinner(winner);
    }

    public char[][] getGameBoard() {
        return gameBoard;
    }

    public void setGameBoard(char[][] gameBoard) {
        this.gameBoard = gameBoard;
    }

    public char getWinner() {
        return winner;
    }

    public void setWinner(char winner) {
        this.winner = winner;
    }
}

在 Kotlin,这被大大简化为:

data class Game(var gameBoard: Array<CharArray>, var winner: Char)

前一行代码与前 26 行 Java 代码做了同样的事情。它声明了一个Game类,该类在其主构造函数中接受两个参数。如前所述,不需要getterssetters

Kotlin 中的数据类还附带了许多其他方法:

  • equals() / hashCode()
  • toString()
  • copy()

如果你曾经写过任何 Java 代码,你应该熟悉equals()hashCode()toString()。让我们继续讨论copy()

当您想要创建一个对象的副本,但它的部分数据被更改时,copy()方法就派上了用场,例如:

data class Student(var name: String, var classRoomNo: Int, var studentId: Int) // 1

var anna = Student("Anna", 5, 1) // 2
var joseph = anna.copy("Joseph", studentId = 2) // 3

在前面的代码片段中:

  1. 我们声明一个名为Student的数据类。它的主构造函数中有三个参数:nameclassRoomNostudentId
  2. anna变量是具有以下属性的Student的实例:name:AnnaclassRoomNo:5studentId:1
  3. 变量joseph是通过复制anna并更改两个属性— namestudentId而创建的。

目标

在我们深入讨论对象之前,让我们对 TicTacToe 游戏做一些补充。让我们初始化我们的视图。在MainActivity类的onCreate()方法中添加以下代码行:

turnTextView = findViewById(R.id.turnTextView) as TextView // 1

tableLayout = findViewById(R.id.table_layout) as TableLayout // 2

startNewGame(true)

将以下方法添加到MainActivity类中:

private fun startNewGame(setClickListener: Boolean) {
    turn = 'X'
    turnTextView?.text = 
    String.format(resources.getString(R.string.turn), turn)
    for (i in 0 until gameBoard.size) {
        for (j in 0 until gameBoard[i].size) {
            gameBoard[i][j] = ' '
            val cell = (tableLayout?.getChildAt(i) as 
            TableRow).getChildAt(j) as TextView
            cell.text = ""
            if (setClickListener) {
            }
        }
    }
}

private fun cellClickListener(row: Int, column: Int) {
    gameBoard[row][column] = turn
    ((tableLayout?.getChildAt(row) as TableRow).getChildAt(column) as TextView).text = turn.toString()
    turn = if ('X' == turn) 'O' else 'X'
    turnTextView?.text = String.format(resources.getString(R.string.turn), turn)
}
  1. 在第一步和第二步中,我们用它们在 XML 布局中对应的视图初始化turnTextViewtableLayout
  2. startNewGame()中:
    • 我们重新初始化turn
    • 我们设置turnTextView来显示turn的值
    • 我们重置gameBoard的所有值
    • 我们将tableLayout的所有单元格重置为一个空字符串
  3. cellClickListener()中:
    • 我们根据传递给cellClickListener()的参数将turn的值设置为gameBoard的特定元素
    • 我们还将tableLayout上相应单元格的值更改为turn
    • 我们将turn的值更改为下一个玩家,这取决于turn的前一个值
    • 我们将turnTextView上显示的值更改为turn的新值

我们需要在每次点击任何一个单元格时调用cellClickListener()。为此,我们需要为它们中的每一个添加一个点击监听器。在安卓系统中,我们使用View.OnClickListener。由于View.OnClickListener是一个接口,我们通常创建一个实现其方法的类,并将该类设置为我们的点击监听器。

Java 和 Kotlin 都有一种简化的方法。在 Java 中,你可以通过使用一个匿名内部类来绕过它。匿名内部类允许您同时声明和创建类的实例:

// Java Anonymous Inner Class
cell.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {

    }
});

在前面的代码中,我们声明并创建了一个实现View.OnClickListener接口的类的实例。

在 Kotlin 中,这是使用对象表达式完成的。

将以下代码行放入startNewGame()方法中if (setClickListener)语句的正文中:

cell.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View?) {
        cellClickListener(i, j)
    }
})

Kotlin allows us to further simplify the previous lines of code. We'll discuss this in Chapter 6, Functions and Lambdas, when we talk about Lambdas.

构建并运行。现在,当您点击任何一个单元格时,其中的文本将变为turnTextView的文本,并且turnTextView的值也将变为下一个玩家的值:

摘要

在本章中,我们学习了类、数据类和对象表达式,同时初始化了我们的视图,并为我们的游戏应用添加了额外的逻辑。

在下一章中,我们将深入探讨类型检查和空值安全的主题,以及为什么 Kotlin 的这些特性使它成为最安全的语言之一。