
3.4 状态和控制
3.4.1 变量声明
以下是在上一节中编写的代码:


先从第一行代码开始:

如果与之前声明字段的代码对比,会注意到它们有一部分十分相似:

上面两行代码声明了四个字段,并为其中三个字段提供了值。
之前的章节中曾经讲过,字段相当于为一个对象提供了一个容器,或者说提供了一个可以控制的状态。实际上,计算机本身就是一个存储并控制若干状态的设备,若干状态存储在内存中,CPU通过存取内存中的数据完成状态的切换。而程序就是用来描述状态是如何被控制的。如果状态不够用,就需要在程序中添加额外的状态,只不过在Java中,这一类额外的状态是在方法中添加的。与针对类的字段对应,我们称这类状态为变量(Variable)。
如果读者理解上面的描述有一定困难,那么只需要知道声明了一个名为entity、类型为net.minecraft.entity.Entity的变量就可以了。
实际上,Java允许编写代码时省略赋值部分,而仅声明一个变量:

一个变量也可以先声明再进行赋值,比如:

这与Entity entity=event.getEntity();这一行代码是等价的。
此外,多个类型相同的变量可以合并声明,如:

这与以下的代码是等价的:

3.4.2 条件语句
在前面的章节中已经看到了一种简单的语句,它由一个表达式加上分号组成,这里看到的语句被称为条件语句(Conditional Statement)。
条件语句的形式如下:

conditionalExpression是一个表达式,其值的类型应为boolean,就是只可能是true或false的布尔类型。当表达式的值为true时,执行括号里的所有语句,若为false,则把这些语句全部跳过。
当大括号里的语句只有一句时,大括号可以省略,换言之,下面三种写法是等价的:

还有一种条件语句,在相应的表达式为true时执行第一个括号里的所有语句,否则执行第二个括号里的所有语句,它含有关键词else,具体形式如下:

再比如下面这段代码:

在正常情况下,一个方法里的语句都是按顺序执行的,但是条件表达式的出现,在部分情况下,使一个方法里的部分语句被跳过,而不再被执行。我们称条件语句调整了程序的控制流(Control Flow)。
正是因为大括号内的语句只有在特殊的情况下才会执行,因此它们的地位和大括号外的语句不同,因此很多Java代码中,大括号内的语句出现时,它们的前面有若干空格,以便看清程序的结构。我们称这些空格为缩进(Indent)。实际上在为一个类添加字段和方法时,添加的字段和方法也存在缩进。作为可读性较强的程序的源代码,不同层级之间的缩进空格数应该一致,而且应该是某个整数的倍数,比如一个类的所有方法的缩进应该一致,大括号里的每一条语句的缩进也应该一致,后者的缩进空格数应该是前者的两倍。对于这个整数,不同的项目会采取不同的数值,有的是2,有的是4,有的是8,或者其他数字,对于本书,一层缩进增加的空格数是4。
大括号里可以有其他语句,当然也可以有条件语句,一种常见的方式是在else下面的语句中添加条件语句,比如下面这段代码:

else处的大括号可以省略,因此可以写成下面的形式:

这种把else if放在一起的写法,在代码中也是相当常见的,比如下面这段代码:


3.4.3 使用new运算符直接构造对象
先从条件语句的内部开始分析。

声明了一个String类型的实例,调用了entity的getName方法获得了实体的名字(对玩家来说这就是玩家的游戏名),并使用了加号将这一名字和其他字符串拼接在了一起,形成了一个新的字符串。
然后使用这个字符串进行以下操作。

这里使用了new运算符。new运算符是一种用于直接构造某种类型的实例的方式,在Java代码中十分常见。
new运算符的组成形式为:
● new及随后的空格。
● 想要直接使用new运算符生成实例的类名。
● 一对小括号及其中的参数,可以有零个、一个或多个。
传入new运算符的参数是由相应的类本身决定的。打开TextComponentString类,会看到以下一段代码:

这段代码除了定义了一个名为text的字段,还声明了一个方法,但是只要把之前编写过的方法稍加比较,就会发现不同之处:

实际上,这里定义的是一个构造方法(Constructor Method)。与普通的方法不同的是,构造方法的方法名称和类名一致,同时没有声明返回值。这个构造方法的声明中有一个名为String的参数,因此它决定了当使用new运算符构造一个TextComponentString时,需要传入一个参数,而这个参数的类型也应该为String。
最后使用了entity对象的sendMessage方法将这条消息显示在聊天栏。这是一个在Mod开发中相对常用的方法。

3.4.4 对象类型的判断
我们希望在一个实体加入世界时判断它是不是玩家,如果是就向其客户端聊天栏发送消息。代表玩家的类是net.minecraft.entity.player.EntityPlayer。换言之,希望检查的是这个Entity类的实例是否是EntityPlayer类的实例,要使用的是代码中出现的instanceof运算符:

上面的表达式会在entity变量是EntityPlayer类的实例时返回true,否则为false。
现在运行客户端,然后新建并进入一个世界,读者应该能看到聊天栏出现这样的内容:

Player997便是启动客户端时Minecraft为作者设置的玩家名称,它可以是Player0和Player999之间的名字中的任何一个。
不过有一点似乎比较奇怪:为什么这条消息出现了两次?难道这个玩家刚刚进了两次世界?事实当然不是这样的。实际上是因为玩家在进入世界时,这个事件分别在客户端线程和服务端线程各触发了一次,所以事件总共触发了两次,因此产生了两条输出。至于客户端线程和服务端线程到底是什么,为什么开启一个客户端也会有服务端线程存在等各种问题,将放到后面的章节来讲述。