四、类和对象
在这一章中,我们将继续我们的 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
类,该类在其主构造函数中接受两个参数。如前所述,不需要getters
和setters
。
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
在前面的代码片段中:
- 我们声明一个名为
Student
的数据类。它的主构造函数中有三个参数:name
、classRoomNo
和studentId
。 anna
变量是具有以下属性的Student
的实例:name:Anna
、classRoomNo:5
和studentId:1
。- 变量
joseph
是通过复制anna
并更改两个属性—name
和studentId
而创建的。
目标
在我们深入讨论对象之前,让我们对 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)
}
- 在第一步和第二步中,我们用它们在 XML 布局中对应的视图初始化
turnTextView
和tableLayout
。 - 在
startNewGame()
中:- 我们重新初始化
turn
- 我们设置
turnTextView
来显示turn
的值 - 我们重置
gameBoard
的所有值 - 我们将
tableLayout
的所有单元格重置为一个空字符串
- 我们重新初始化
- 在
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 的这些特性使它成为最安全的语言之一。
版权属于:月萌API www.moonapi.com,转载请注明出处