第三章 操作符
3.1 更简单的打印语句
Java SE5新增静态导入(static import)概念,在第六章会创建一个小类库简化打印语句的编写。现在直接使用它:
import java.util.*; import static net.mindview.util.Print.*; public class HelloDate() { public static void main(String[] args) { print("Hello, it's: "); print(new Date()); } }
要使用这个类库,必须下载代码包,然后解压代码目录树,并在CLASSPATH环境变量中添加该代码目录树的根目录。
3.4 赋值
赋值语句的左边必须是一个明确的、已命名的变量。例如:
a = 4;
基本类型存储了实际值,而不是指向对象的引用。所以基本类型的a=b,a的内容就变成了b的内容,接者修改a不会影响b。
但是假如将一个对象赋值给另一个对象,实际上是将“引用”复制过去了。所以对象使用c=d,那么c和d都指向原本只有d指向的对象。
import static net.mindview.util.Print.*; class Tank { int i; } public class Assignment { public static void main(String[] args) { Tank t1 = new Tank(); Tank t2 = new Tank(); t1.i = 9; t2.i = 47; print("1:"+t1.i+" "+t2.i); t1 = t2; print("2:"+t1.i+" "+t2.i); t1.i = 20; print("3:"+t1.i+" "+t2.i); } } /* 输出: 1:9 47 2:47 47 3:20 20 */
t1 = t2; 执行后,此时t1、t2实际上都变成了 Tank t2 = new Tank(); 这条语句创建的对象的引用,而 Tank t1 = new Tank(); 语句创建的对象的引用原来是t1,现在却丢失了。
这种现象称作“别名现象”,是Java操作对象的一种基本方式。
3.4.1 方法调用中的别名问题
将一个对象传递给一个方法,也存在别名问题:
import static net.mindview.util.Print.*; class Letter { char c; } public class PassObject { static void f(Letter y) { y.c = 'z'; } public static void main(String[] args) { Letter x = new Letter(); x.c = 'a'; print("1: x.c: "+x.c); f(x); print("2: x.c: "+x.c); } } /* 输出: 1: x.c: a 2: x.c: z */
方法f()并不是在它的作用域内复制参数Letter y的一个副本,而是传递了一个引用。
3.7 关系操作符
3.7.1 测试对象的等价性
关系操作符==和!=也适用于所有对象,看下面这个例子:
public class Equivalence { public static void main(Striing[] args) { Integer n1 = new Integer(47); Integer n2 = new Integer(47); System.out.println(n1 == n2); System.out.println(n1 != n2); } } /* 输出: false true */
尽管两个对象的内容相同,但对象的引用却不同。而==和!=比较的就是对象的引用。所以输出先是false再是true。
要想比较对象实际内容是否相同,可以用特殊方法equals(),它适用于所有对象,但不适用于基本类型。
public class Equivalence { public static void main(Striing[] args) { Integer n1 = new Integer(47); Integer n2 = new Integer(47); System.out.println(n1.equals(n2)); } } /* 输出: true */
这次结果是相等,但是如果比较的是自己创建的类的对象:
class Value { int i; } public class EqualsMethod2 { public static void main(String[] args) { Value v1 = new Value(); Value v2 = new Value(); v1.i = v2.i = 100; System.out.println(v1.equals(v2)); } } /* 输出: false */
这时结果又变成false了,这是因为equals()默认比较引用,查看equals()方法源码:
public boolean equals(Object obj) { return (this == obj); }
对象都拥有标识(内存地址)和状态(数据),同时“==”比较两个对象的的内存地址,所以说使用Object的equals()方法是比较两个对象的内存地址是否相等,即若object1.equals(object2)为true,则表示equals1和equals2实际上是引用同一个对象。
3.8 逻辑操作符
与在C和C++中不同的是:Java中不能将一个非布尔值当做布尔值在逻辑表达式中使用:
public clss Bool { public static void main(String[] args) { Random rand = new Random(47); int i = rand.nextInt(100); int j = rand.nextInt(100); System.out.println("i && j is "+ (i && j)); //非法 System.out.println("i || j is "+ (i || j)); //非法 System.out.println("!i is "+ !i); //非法 } }
所以像while(1)这种写法也是不允许的。
3.9.1 指数计数法
例如: float expFloat = 1.39e-43f; “e”也可以大写: float expFloat = 1.39E-43f;
注意:这里的基数“e”不是自然对数的基数,而表示“10的幂次”,即1.39e-43f的含义是1.39×10-43。
对于语句 float f4 = 1e-43f; ,编译器通常会把指数作为double处理,所以要加上这个尾随的f,否则会编译出错,要求把double类型转换成float。
3.12 三元操作符
格式:boolean-exp ? value0 : value1
如果boolean-exp(布尔表达式)结果为true就计算value0,结果为false就计算value1,计算结果作为操作符产生的最终的值。
3.13 字符串操作符+和+=
//: operators/StringOperators.java import static net.mindview.util.Print.*; public class StringOperators { public static void main(String[] args) { int x = 0, y = 1, z = 2; String s = "x, y, z "; print(s + x + y + z); print(x + " " + s); // Converts x to a String s += "(summed) = "; // Concatenation operator print(s + (x + y + z)); print("" + x); // Shorthand for Integer.toString() } } /* Output: x, y, z 012 0 x, y, z x, y, z (summed) = 3 0 *///:~
3.14 使用操作符常见错误
C和C++中写 while(x = y) { //... } ,若y是非零值,结果一定是true,变成死循环。Java会直接抛出错误,除非x和y都是布尔值。
Java中要注意区分按位与“&”、按位或“|”和逻辑与“&&”、逻辑或“||”的区别。
3.15 类型转换
1.隐式类型转换
双目运算符组成的表达式,一般要求两边类型一致。若不一致,Java会自动转换为取值范围较大的类型。
2.显式类型转换
隐式类型转换只能“扩展转换”,要想“窄化转换”(即高类型向低类型转换),就必须采用显式类型转换。
注意:布尔型不能类型转换。“类”数据类型也不能类型转换,而是采取特殊方法。
3.15.1 截尾和舍入
执行窄化转换时,必须注意截尾和舍入问题。
将float或double转型成int型,总是对数字进行截尾。要想得到舍入的结果,就要用java.lang.Math中的round()方法。