表达式与语句是任何程序设计语言的基础,也是开发大型软件系统的最底层的“标准散件”,是开发人员掌握任何程序设计语言的基础。无论多么庞大的软件系统,都是由开发语言的表达式与语句搭建而成的,因此,不容忽视。也许你可能对这部分内容的概念较为熟悉,但在实际的程序设计中,却经常在方面犯错误,而且通常情况下,这些问题还比较隐蔽,极不易被发现,希望你能通过本章的学习可以避免在这些方面犯错误,并且提高所编写代码的性能。
1 表达式
在Java语言中表达式分为两种:一元操作符和二元操作符 。一元操作符和二元操作符如下表所示。
操作符 | 适用范围 | 结合规则 |
++, -- | 算数,字符 | 从右至左 |
+, - | 算数 | 从右至左 |
- | 整数 | 从右至左 |
! | 布尔 | 从右至左 |
(type) | 任意 | 从右至左 |
操作符 | 适用范围 | 结合规则 |
*, /, % | 算术 | 从左至右 |
+, - | 算术 | 从左至右 |
+ | 字符串 | 从左至右 |
<< | 整型数 | 从左至右 |
>> | 整型数 | 从左至右 |
>>> | 整型数 | 从左至右 |
<, >, <=, >= | 算术、字符 | 从左至右 |
instanceof | 对象x类型 | 从左至右 |
==, != | 元数据 | 从左至右 |
==, != | 对象 | 从左至右 |
&, ^, | | 整型数和布尔值 | 从左至右 |
&&, || | 布尔值 | 从左至右 |
1.1 括号规则
这里没有提到关于操作符运算优先级的知识,因为记住这些运算符的优先级是件很麻烦的事,而且稍有疏忽就有可能弄错,现在给你一个“放之四海皆准”的办法来解决这个令人头疼的问题——合理运用括号,表达你期望的运算优先级。例如下面的表达式:
x = ((a | b) && (a & c)) || (m < n)
我们只要合理地应用括号规则就可以避免使用默认优先级出现的错误。如果你觉得上面的表达式很容易应用默认优先级来达到你的期望结果,根本用不着使用括号加以区分,但是如果比这个表达式再复杂两倍或三倍的表达式,恐怕你就不会觉得是件容易的事了,因为较长且繁琐的表达式,很难让你一目了然,极有可能产生你所不期望的运算优先级。
1.2 简单规则
有些时候,我们为了书写方便,经常会使用一些较为复杂的复合表达式,例如:
boolean isTrue = (getObject() != null); userName = userManager.getUsers().getUserNames()[i];
这些语句在Java语言中都是正确的,而且这样可以节省你的编程时间,提高程序的编译效率,但是如果应用不当同样会产生很多问题,导致程序运算逻辑上的错误,例如:
x = i >= j && m < n && y + z <= d;
上面的表达式是较为复杂的,而且很难理解,即使是开发人员自己阅读恐怕都有些困难,其他人员就更难以了解其意图了,因此这样复杂的表达式应尽量避免使用。
1.3 单一意图规则
表达式应该尽量表达单一意图,也就是说,最好一个表达式只做一件事,例如,赋值表达式就只负责为一个变量赋值,一个运算表达式就只负责完成一个运算过程。这样就可以使程序员所表达的意图更为清晰,不会产生一些隐蔽的错误。否则就容易产生一些你所不期望的错误,而且降低程序的可读性,例如下面的语句:
int i = 0; if ((i=++j) == (x++)) { ... ... }
其目的是在对i赋值,x加1后再比较二者的值,这样书写没有任何语法上的错误,但是却使程序难以理解,降低了程序的可读性,如果存在逻辑上的错误也很难排除,正确解决的办法是将表达不同意图的表达式分开,例如上面的语句可以书写成:
int i = 0; i = ++j; x++; if (i == x) { ... }
通过对上面程序的分解,你可以看到修正后的代码,让人看起来思路更为清晰,逻辑也非常清楚。这样程序所表达的意图就一目了然,增强了程序的可读性,并且不易产生隐蔽的错误。
1.4 方法返回值比较规则
有时候为了使程序书写简洁,我们经常在判断语句中直接通过方法的返回值来作为条件判断的比较项,例如:假设方法obj.getAge()返回一个整数而且变量i也是一个整数,请看下面的语句。
if (obj.getAge() == i) { ... }
上面这个语句在语法上是没有任何问题的,但笔者曾经却遇到过这样一个奇怪的问题,即使当obj.getAge()方法的返回值与i中的数值相同,条件obj.getAge() == i 却为假,记得这是在JDK1.2中遇到的一个问题,耗费了笔者很长的时间才找到,现在的JDK版本已经解决了这个问题,但是笔者还是建议你尽量不要这样书写,下面的书写方式是值得提倡的:
int tmp = obj.getAge(); if (tmp == i) { ... }
1.5 字符串比较规则
字符串比较
字符串(String)是Java语言中的核心类,而且应用广泛。但是这里有一个极容易陷入的误区,当你比较两个字符串变量时,可能会写成下面的样子:
if (userName == myName) { ... }
在Java语言中不存在语法的问题,但是你却不一定能得到你所预期的结果。因为这种比较方式比较的是两个对象的引用,而不是比较它们所引用的对象。如果上面语句中的
userName = "张三";
myName = "李四";
这样表达式“userName == myName”的返回值就为假,这是因为这两个字符串变量的值不一样,而且它们的引用也不一样。假设二者的引用相同,那么返回值就为真,其比较结果显然是出乎我们意料的,因为这不是我们所期望的,引起这个问题的根本原因,就是你所采用的比较方法不对,应该采用下面的比较方式:
if (userName.equals(myName)) { ... }
String类中的equals()方法专门用来比较字符串的值,而不是比较字符串对象的引用,因此,采用上面的方法将返回你所期望的结果。这里需要提醒你一下,使用“==”比 较用equals()方法比较对象是否相等,更为快速,你应该在适当的前提下,尽可能的使用“==”而不使用equals()方法。
字符串驻留
上面我们讨论了字符串比较的知识要点,不妨再延伸一下,介绍字符串驻留的知识。在字符串类String中有一个叫做intern()的方法,这个方法用来创建一个唯一字符串集合的驻留池,例如:
userName = userName.intern();
就相当于将字符串添加到这个驻留池中,如果这个驻留池中原先并不存在这个值,并且返回对这个已驻留字符串的引用,这样已驻留的字符串就可以通过“==”彼此之间做比较操作了,采用这个方法比采用equals()方法更为廉价,而且运行速度更快,下面是一个字符串比较的例子:
// 字符串比较 public class CompareString { public static void main(String args[]) { String str = new String("张三"); // 没有做驻留处理 if (str == "张三") { System.out.println("相同"); } else { System.out.println("不同"); } // 做了驻留处理 str = str.intern(); if (str == "张三") { System.out.println("相同"); } else { System.out.println("不同"); } } }
在上面的例子中,第一个if 语句中str == "张三" 比较后的返回值为假,这是因为str 是对显式声明字符串“张三”的拷贝的引用,当“张三”自身是一个驻留字符串的时候,相应的引用也变得截然不同了。在上例中的第二个if 语句中str == "张三" 比较后的返回值为真,这是因为str 已经被驻留,并且对方法intern() 的返回值的引用与对显式声明字符串“张三”的引用相同,因为显式声明字符串正是将字符串内容保存在驻留字符串池中的过程。字符串中intern()方法的执行效率很高,因此在不必须要求采用equals() 方法做字符串比较时,你可以采用字符串驻留的方法,以提高系统的性能。