文章目录
第四章 运算符
1. 开始使用
加法 + 、减法 - 、乘法 * 、除法 / 以及赋值 = 在任何编程语言中的工作方式都是类似的。
2. 优先级
运算符的优先级决定了存在多个运算符时一个表达式各部分的运算顺序。Java 对运算顺序作出了特别 的规定。其中,最简单的规则就是乘法和除法在加法和减法之前完成。程序员经常都会忘记其他优先级规则,所以应该用括号明确规定运算顺序。
—PS:加括号表明,代码逻辑更清晰
3. 赋值
运算符的赋值是由符号 = 完成的。它代表着获取 = 右边的值并赋给左边的变量。右边可以是任何常量、变量或者可产生一个返回值的表达式。但左边必须是一个明确的、已命名的变量。
基本类型的赋值都是直接的,而不像对象,赋予的只是其内存的引用。
// operators/Assignment.java // Assignment with objects is a bit tricky
class Tank {
int level;
}
public class Assignment {
public static void main(String[] args) {
Tank t1 = new Tank();
Tank t2 = new Tank();
t1.level = 9;
t2.level = 47;
System.out.println("1: t1.level: " + t1.level + ", t2.level: " + t2.level); t1 = t2; System.out.println("2: t1.level: " + t1.level + ", t2.level: " + t2.level); t1.level = 27; System.out.println("3: t1.level: " + t1.level + ", t2.level: " + t2.level);
}
}
输出结果:
1: t1.level: 9, t2.level: 47
2: t1.level: 47, t2.level: 47
3: t1.level: 27, t2.level: 27
在 Java 中,由于赋予的只是对象的引用,改变 t1 也就改变了 t2。 这是因为 t1 和 t2 此时指向的是堆中同一个对象。(t1 原始对象的引用在 t2 赋值给其时丢失,它引用的对象会在垃圾回收时被清理)。
这种现象通常称为别名(aliasing),这是 Java 处理对象的一种基本方式。
—PS:对象赋值的只是引用,后续改变针对的是同一个对象
4. 算术运算符
Java 的基本算术运算符与其他大多编程语言是相同的。其中包括加号 + 、减号 - 、除号 / 、乘号 * 以及取模 % (从整数除法中获得余数)。整数除法会直接砍掉小数,而不是进位。
—PS:3/5=0 3%5=3
Java 也用一种与 C++ 相同的简写形式同时进行运算和赋值操作,由运算符后跟等号表示,并且与语言中的所有运算符一致(只要有意义)。可用 x += 4 来表示:将 x 的值加上4的结果再赋值给 x。
5. 递增和递减
对于前递增和前递减(如 ++a 或 --a ),会先执行递增/减运算,再返回值。
而对于后递增和后递减(如 a++ 或 a-- ),会先返回值,再执行递增/减运算。
// operators/AutoInc.java
// 演示 ++ 和 -- 运算符
public class AutoInc {
public static void main(String[] args) {
int i = 1;
System.out.println("i: " + i); System.out.println("++i: " + ++i); // 前递增
System.out.println("i++: " + i++); // 后递增
System.out.println("i: " + i);
System.out.println("--i: " + --i); // 前递减
System.out.println("i--: " + i--); // 后递减
System.out.println("i: " + i);
}
}
输出结果:
i: 1
++i: 2
i++: 2
i: 3
--i: 2
i--: 2
i: 1
—PS:记住一种情况就行了,另一种反推
6. 关系运算符
关系运算符会通过产生一个布尔(boolean)结果来表示操作数之间的关系。如果关系为真,则结果为true,如果关系为假,则结果为 false。关系运算符包括小于 < ,大于 > ,小于或等于 <= ,大于或等于 >= ,等于 == 和不等于 != 。 == 和 != 可用于所有基本类型,但其他运算符不能用于基本类型 boolean,因为布尔值只能表示 true 或 false,所以比较它们之间的“大于”或“小于”没有意义。
那么怎么比较两个对象的内容是否相同呢?你必须使用所有对象(不包括基本类型)中都存在的 equals() 方法。
// operators/EqualsMethod2.java
// 默认的 equals() 方法没有比较内容
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
equals() 的默认行为是比较对象的引用而非具体内容。大多数 Java 库类通过覆写 equals() 方法比较对象的内容而不是其引用。
—PS:没有重写的 equals() 方法不是好方法
7. 逻辑运算符
每个逻辑运算符 && (AND)、 || (OR)和 ! (非)根据参数的逻辑关系生成布尔值 true 或 false 。
逻辑运算符支持一种称为“短路”(short-circuiting)的现象。整个表达式会在运算到可以明确结果时就停止并返回结果,这意味着该逻辑表达式的后半部分不会被执行到。
—PS:多个 && 运算,遇到第一个为 false 时就会停止,后面不会运算。
多个 || 运算,遇到第一个为 true 时就会停止,后面不会运算。
8. 字面值常量
对于 Long 型数值,结尾使用大写 L 或小写 l 皆可(不推荐使用 l ,因为容易与阿拉伯数值 1 混淆)。大写 F 或小写 f 表示 float 浮点数。大写 D 或小写 d 表示 double 双精度。
下划线
Java 7 中有一个深思熟虑的补充:我们可以在数字字面量中包含下划线 _ ,以使结果更清晰。这对于大数值的分组特别有用。代码示例:
public class Underscores {
public static void main(String[] args) {
double d = 341_435_936.445_667;
System.out.println(d);
int bin = 0b0010_1111_1010_1111_1010_1111_1010_1111;
System.out.println(Integer.toBinaryString(bin));
System.out.printf("%x%n", bin); // [1]
long hex = 0x7f_e9_b7_aa;
System.out.printf("%x%n", hex);
}
}
输出结果:
3.41435936445667E8
101111101011111010111110101111
2fafafaf
7fe9b7aa
下面是合理使用的规则:
-
仅限单 _ ,不能多条相连。
-
数值开头和结尾不允许出现 _ 。
-
F 、 D 和 L 的前后禁止出现 _ 。
-
二进制前导 b 和 十六进制 x 前后禁止出现 _ 。
9. 位运算符
位运算符允许我们操作一个整型数字中的单个二进制位。位运算符会对两个整数对应的位执行布尔代数,从而产生结果。
10. 移位运算符
移位运算符面向的运算对象也是二进制的“位”。它们只能用于处理整数类型(基本类型的一种)。左 移位运算符 << 能将其左边的运算对象向左移动右侧指定的位数(在低位补 0)。右移位运算符 >> 则相反。右移位运算符有“正”、“负”值:若值为正,则在高位插入 0;若值为负,则在高位插入 1。
—PS:这么长时间开发没有用到过
11. 三元运算符
三元运算符,也称为条件运算符。
它的表达式格式:
布尔表达式 ? 值 1 : 值 2
若表达式计算为 true,则返回结果 值 1 ;如果表达式的计算为 false,则返回结果 值 2。
—PS:如果计算太复杂了,还不如 if-else 好使
12. 字符串运算符
若表达式以一个 String 类型开头(编译器会自动将双引号 “” 标注的的字符序列转换为字符串),那么后续所有运算对象都必须是字符串。
我们经常会看到一个空字符串 “” 跟着一个基本类型的数据。这样可以隐式地将其转换为字符串,以代替繁琐的显式调用方法(如这里可以使用 Integer.toString())。
—PS:取巧的写法,System.out.println(“” + 10);
13. 常见陷阱
while(x = y) {
// ...
}
显然,程序员原意是测试等价性 == ,而非赋值 = 。若变量 y 非 0 的话,在 C/C++ 中,这样的赋值操作总会返回 true 。于是,上面的代码示例将会无限循环。而在 Java 中,这样的表达式结果并不会转化为一个布尔值。 而编译器会试图把这个 int 型数据转换为预期应接收的布尔类型。最后,我们将会在试图运行前收到编译期错误。因此,Java 天生避免了这种陷阱发生的可能。
—PS:不用担心,这样写的时候 Java 就会报错
14. 类型转换
“类型转换”(Casting)的作用是“与一个模型匹配”。在适当的时候,Java 会将一种数据类型自动转换成另一种。
当然,为了程序逻辑清晰或提醒自己留意,我们也可以显式地类型转换。但是,若将数据类型进行“向下转换”(Narrowing Conversion)的操作(将容量较大的数据类型转换成容量较小的类型),可能会发生信息丢失的危险。此时,编译器会强迫我们进行转型,好比在提醒我们:该操作可能危险,若你坚持让我这么做,那么对不起,请明确需要转换的类型。 对于“向上转换”(Widening conversion),则不必进行显式的类型转换,因为较大类型的数据肯定能容纳较小类型的数据,不会造成任何信息的丢失。
—PS:大桶水往小桶里面倒的时候可能会丢失,反之则不然
除了布尔类型的数据,Java 允许任何基本类型的数据转换为另一种基本类型的数据。此外,类是不能进行类型转换的。为了将一个类转换为另一个类型,需要使用特殊的方法(后面将会学习到如何在父子类之间进行向上/向下转型,例如,“橡树”可以转换为“树”,反之亦然。而对于“岩石”是无法转换为“树”的)。
—PS:这是父子类、继承的事了。
执行“向下转换”时,必须注意数据的截断和舍入问题。
// operators/CastingNumbers.java
// 尝试转换 float 和 double 型数据为整型数据
public class CastingNumbers {
public static void main(String[] args) {
double above = 0.7, below = 0.4;
float fabove = 0.7f, fbelow = 0.4f;
System.out.println("(int)above: " + (int)above);
System.out.println("(int)below: " + (int)below);
System.out.println("(int)fabove: " + (int)fabove);
System.out.println("(int)fbelow: " + (int)fbelow);
}
}
输出结果:
(int)above: 0
(int)below: 0
(int)fabove: 0
(int)fbelow: 0
因此,答案是,从 float 和 double 转换为整数值时,小数位将被截断。
对小于 int 的基本数据类型(即 char、byte 或 short)执行任何算术或按位操作, 这些值会在执行操作之前类型提升为 int,并且结果值的类型为 int。若想重新使用较小的类型,必须 使用强制转换(由于重新分配回一个较小的类型,结果可能会丢失精度)。
—PS:就是特殊
通常,表达式中最大的数 据类型是决定表达式结果的数据类型。float 型和 double 型相乘,结果是 double 型的;int 和 long 相加,结果是 long 型。
—PS:Now you are talking