本章概要
- 移位运算符
- 三员运算符
- 字符串运算符
- 常见陷阱
- 类型转换
- 截断和舍入
- 类型提升
- Java 没有 sizeof
- 总结
移位运算符
移位运算符面向的运算对象也是二进制的“位”。它们只能用于处理整数类型(基本类型的一种)。左移位运算符 <<
能将其左边的运算对象向左移动右侧指定的位数(在低位补 0)。右移位运算符 >>
则相反。右移位运算符有“正”、“负”值:若值为正,则在高位插入 0;若值为负,则在高位插入 1。Java 也添加了一种“不分正负”的右移位运算符(>>>),它使用了“零扩展”(zero extension):无论正负,都在高位插入 0。这一运算符是 C/C++ 没有的。
如果移动 char、byte 或 short,则会在移动发生之前将其提升为 int,结果为 int。仅使用右值(rvalue)的 5 个低阶位。这可以防止我们移动超过 int 范围的位数。若对一个 long 值进行处理,最后得到的结果也是 long。
移位可以与等号 <<=
或 >>=
或 >>>=
组合使用。左值被替换为其移位运算后的值。但是,问题来了,当无符号右移与赋值相结合时,若将其与 byte 或 short 一起使用的话,则结果错误。取而代之的是,它们被提升为 int 型并右移,但在重新赋值时被截断。在这种情况下,结果为 -1。下面是代码示例:
// operators/URShift.java
// 测试无符号右移
public class URShift {
public static void main(String[] args) {
int i = -1;
System.out.println(Integer.toBinaryString(i));
i >>>= 10;
System.out.println(Integer.toBinaryString(i));
long l = -1;
System.out.println(Long.toBinaryString(l));
l >>>= 10;
System.out.println(Long.toBinaryString(l));
short s = -1;
System.out.println(Integer.toBinaryString(s));
s >>>= 10;
System.out.println(Integer.toBinaryString(s));
byte b = -1;
System.out.println(Integer.toBinaryString(b));
b >>>= 10;
System.out.println(Integer.toBinaryString(b));
b = -1;
System.out.println(Integer.toBinaryString(b));
System.out.println(Integer.toBinaryString(b>>>10));
}
}
输出结果:
11111111111111111111111111111111
1111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
1111111111111111111111
在上例中,结果并未重新赋值给变量 b ,而是直接打印出来,因此一切正常。下面是一个涉及所有位运算符的代码示例:
// operators/BitManipulation.java
// 使用位运算符
import java.util.*;
public class BitManipulation {
public static void main(String[] args) {
Random rand = new Random(47);
int i = rand.nextInt();
int j = rand.nextInt();
printBinaryInt("-1", -1);
printBinaryInt("+1", +1);
int maxpos = 2147483647;
printBinaryInt("maxpos", maxpos);
int maxneg = -2147483648;
printBinaryInt("maxneg", maxneg);
printBinaryInt("i", i);
printBinaryInt("~i", ~i);
printBinaryInt("-i", -i);
printBinaryInt("j", j);
printBinaryInt("i & j", i & j);
printBinaryInt("i | j", i | j);
printBinaryInt("i ^ j", i ^ j);
printBinaryInt("i << 5", i << 5);
printBinaryInt("i >> 5", i >> 5);
printBinaryInt("(~i) >> 5", (~i) >> 5);
printBinaryInt("i >>> 5", i >>> 5);
printBinaryInt("(~i) >>> 5", (~i) >>> 5);
long l = rand.nextLong();
long m = rand.nextLong();
printBinaryLong("-1L", -1L);
printBinaryLong("+1L", +1L);
long ll = 9223372036854775807L;
printBinaryLong("maxpos", ll);
long lln = -9223372036854775808L;
printBinaryLong("maxneg", lln);
printBinaryLong("l", l);
printBinaryLong("~l", ~l);
printBinaryLong("-l", -l);
printBinaryLong("m", m);
printBinaryLong("l & m", l & m);
printBinaryLong("l | m", l | m);
printBinaryLong("l ^ m", l ^ m);
printBinaryLong("l << 5", l << 5);
printBinaryLong("l >> 5", l >> 5);
printBinaryLong("(~l) >> 5", (~l) >> 5);
printBinaryLong("l >>> 5", l >>> 5);
printBinaryLong("(~l) >>> 5", (~l) >>> 5);
}
static void printBinaryInt(String s, int i) {
System.out.println(
s + ", int: " + i + ", binary:\n " +
Integer.toBinaryString(i));
}
static void printBinaryLong(String s, long l) {
System.out.println(
s + ", long: " + l + ", binary:\n " +
Long.toBinaryString(l));
}
}
输出结果(前 32 行):
-1, int: -1, binary:
11111111111111111111111111111111
+1, int: 1, binary:
1
maxpos, int: 2147483647, binary:
1111111111111111111111111111111
maxneg, int: -2147483648, binary:
10000000000000000000000000000000
i, int: -1172028779, binary:
10111010001001000100001010010101
~i, int: 1172028778, binary:
1000101110110111011110101101010
-i, int: 1172028779, binary:
1000101110110111011110101101011
j, int: 1717241110, binary:
1100110010110110000010100010110
i & j, int: 570425364, binary:
100010000000000000000000010100
i | j, int: -25213033, binary:
11111110011111110100011110010111
i ^ j, int: -595638397, binary:
11011100011111110100011110000011
i << 5, int: 1149784736, binary:
1000100100010000101001010100000
i >> 5, int: -36625900, binary:
11111101110100010010001000010100
(~i) >> 5, int: 36625899, binary:
10001011101101110111101011
i >>> 5, int: 97591828, binary:
101110100010010001000010100
(~i) >>> 5, int: 36625899, binary:
10001011101101110111101011
...
结尾的两个方法 printBinaryInt()
和 printBinaryLong()
分别操作一个 int 和 long 值,并转换为二进制格式输出,同时附有简要的文字说明。除了演示 int 和 long 的所有位运算符的效果之外,本示例还显示 int 和 long 的最小值、最大值、+1 和 -1 值,以便我们了解它们的形式。注意高位代表符号:0 表示正,1 表示负。上面显示了 int 部分的输出。以上数字的二进制表示形式是带符号的补码(2’s complement)。
三元运算符
三元运算符,也称为条件运算符。这种运算符比较罕见,因为它有三个运算对象。但它确实属于运算符的一种,因为它最终也会生成一个值。这与本章后一节要讲述的普通 if-else 语句是不同的。下面是它的表达式格式:
布尔表达式 ? 值 1 : 值 2
若表达式计算为 true,则返回结果 值 1 ;如果表达式的计算为 false,则返回结果 值 2。
当然,也可以换用普通的 if-else 语句(在后面介绍),但三元运算符更加简洁。作为三元运算符的创造者, C 自诩为一门简练的语言。三元运算符的引入多半就是为了高效编程,但假若我们打算频繁使用它的话,还是先多作一些思量: 它易于产生可读性差的代码。与 if-else 不同的是,三元运算符是有返回结果的。请看下面的代码示例:
// operators/TernaryIfElse.java
public class TernaryIfElse {
static int ternary(int i) {
return i < 10 ? i * 100 : i * 10;
}
static int standardIfElse(int i) {
if (i < 10) {
return i * 100;
} else {
return i * 10;
}
}
public static void main(String[] args) {
System.out.println(ternary(9));
System.out.println(ternary(10));
System.out.println(standardIfElse(9));
System.out.println(standardIfElse(10));
}
}
输出结果:
900
100
900
100
可以看出,ternary()
中的代码更简短。然而,standardIfElse() 中的代码更易理解且不要求更多的录入。所以我们在挑选三元运算符时,请务必权衡一下利弊。
字符串运算符
这个运算符在 Java 里有一项特殊用途:连接字符串。这点已在前面展示过了。尽管与 +
的传统意义不符,但如此使用也还是比较自然的。这一功能看起来还不错,于是在 C++ 里引入了“运算符重载”机制,以便 C++ 程序员为几乎所有运算符增加特殊的含义。但遗憾得是,与 C++ 的一些限制结合以后,它变得复杂。这要求程序员在设计自己的类时必须对此有周全的考虑。虽然在 Java 中实现运算符重载机制并非难事(如 C# 所展示的,它具有简单的运算符重载),但因该特性过于复杂,因此 Java 并未实现它。
我们注意到运用 String +
时有一些有趣的现象。若表达式以一个 String 类型开头(编译器会自动将双引号 ""
标注的的字符序列转换为字符串),那么后续所有运算对象都必须是字符串。代码示例:
// operators/StringOperators.java
public class StringOperators {
public static void main(String[] args) {
int x = 0, y = 1, z = 2;
String s = "x, y, z ";
System.out.println(s + x + y + z);
// 将 x 转换为字符串
System.out.println(x + " " + s);
s += "(summed) = ";
// 级联操作
System.out.println(s + (x + y + z));
// Integer.toString()方法的简写:
System.out.println("" + x);
}
}
输出结果:
注意:上例中第 1 输出语句的执行结果是 012
而并非 3
,这是因为编译器将其分别转换为其字符串形式然后与字符串变量 s 连接。在第 2 条输出语句中,编译器将开头的变量转换为了字符串,由此可以看出,这种转换与数据的位置无关,只要当中有一条数据是字符串类型,其他非字符串数据都将被转换为字符串形式并连接。最后一条输出语句,我们可以看出 +=
运算符可以拼接其右侧的字符串连接结果并重赋值给自身变量 s
。括号 ()
可以控制表达式的计算顺序,以便在显示 int 之前对其进行实际求和。
请注意主方法中的最后一个例子:我们经常会看到一个空字符串 ""
跟着一个基本类型的数据。这样可以隐式地将其转换为字符串,以代替繁琐的显式调用方法(如这里可以使用 Integer.toString())。
常见陷阱
使用运算符时很容易犯的一个错误是,在还没搞清楚表达式的计算方式时就试图忽略括号 ()
。在 Java 中也一样。 在 C++ 中你甚至可能犯这样极端的错误.代码示例:
while(x = y) {
// ...
}
显然,程序员原意是测试等价性 ==
,而非赋值 =
。若变量 y 非 0 的话,在 C/C++ 中,这样的赋值操作总会返回 true
。于是,上面的代码示例将会无限循环。而在 Java 中,这样的表达式结果并不会转化为一个布尔值。 而编译器会试图把这个 int 型数据转换为预期应接收的布尔类型。最后,我们将会在试图运行前收到编译期错误。因此,Java 天生避免了这种陷阱发生的可能。
唯一有种情况例外:当变量 x
和 y
都是布尔值,例如 x=y
是一个逻辑表达式。除此之外,之前的那个例子,很大可能是错误。
在 C/C++ 里,类似的一个问题还有使用按位“与” &
和“或” |
运算,而非逻辑“与” &&
和“或” ||
。就象 =
和 ==
一样,键入一个字符当然要比键入两个简单。在 Java 中,编译器同样可防止这一点,因为它不允许我们强行使用另一种并不符的类型。
类型转换
“类型转换”(Casting)的作用是“与一个模型匹配”。在适当的时候,Java 会将一种数据类型自动转换成另一种。例如,假设我们为 float 变量赋值一个整数值,计算机会将 int 自动转换成 float。我们可以在程序未自动转换时显式、强制地使此类型发生转换。
要执行强制转换,需要将所需的数据类型放在任何值左侧的括号内,如下所示:
// operators/Casting.java
public class Casting {
public static void main(String[] args) {
int i = 200;
long lng = (long) i;
lng = i; // 没有必要的类型提升
long lng2 = (long) 200;
lng2 = 200;
// 类型收缩
i = (int) lng2; // Cast required
}
}
诚然,你可以这样地去转换一个数值类型的变量。但是上例这种做法是多余的:因为编译器会在必要时自动提升 int 型数据为 long 型。
当然,为了程序逻辑清晰或提醒自己留意,我们也可以显式地类型转换。在其他情况下,类型转换只有在代码编译时才显出其重要性。在 C/C++ 中,类型转换有时会让人头痛。在 Java 里,类型转换则是一种比较安全的操作。但是,若将数据类型进行“向下转换”(Narrowing Conversion)的操作(将容量较大的数据类型转换成容量较小的类型),可能会发生信息丢失的危险。此时,编译器会强迫我们进行转型,好比在提醒我们:该操作可能危险,若你坚持让我这么做,那么对不起,请明确需要转换的类型。 对于“向上转换”(Widening conversion),则不必进行显式的类型转换,因为较大类型的数据肯定能容纳较小类型的数据,不会造成任何信息的丢失。
除了布尔类型的数据,Java 允许任何基本类型的数据转换为另一种基本类型的数据。此外,类是不能进行类型转换的。为了将一个类转换为另一个类型,需要使用特殊的方法(后面将会学习到如何在父子类之间进行向上/向下转型,例如,“橡树”可以转换为“树”,反之亦然。而对于“岩石”是无法转换为“树”的)。
截断和舍入
在执行“向下转换”时,必须注意数据的截断和舍入问题。若从浮点值转换为整型值,Java 会做什么呢?例如:浮点数 29.7 被转换为整型值,结果会是 29 还是 30 呢?下面是代码示例:
// 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);
}
}
输出结果:
因此,答案是,从 float 和 double 转换为整数值时,小数位将被截断。若你想对结果进行四舍五入,可以使用 java.lang.Math
的 round()
方法:
// operators/RoundingNumbers.java
// float 和 double 类型数据的四舍五入
public class RoundingNumbers {
public static void main(String[] args) {
double above = 0.7, below = 0.4;
float fabove = 0.7f, fbelow = 0.4f;
System.out.println("Math.round(above): " + Math.round(above));
System.out.println("Math.round(below): " + Math.round(below));
System.out.println("Math.round(fabove): " + Math.round(fabove));
System.out.println("Math.round(fbelow): " + Math.round(fbelow));
}
}
输出结果:
因为 round()
方法是 java.lang
的一部分,所以我们无需通过 import
就可以使用。
类型提升
你会发现,如果我们对小于 int 的基本数据类型(即 char、byte 或 short)执行任何算术或按位操作,这些值会在执行操作之前类型提升为 int,并且结果值的类型为 int。若想重新使用较小的类型,必须使用强制转换(由于重新分配回一个较小的类型,结果可能会丢失精度)。通常,表达式中最大的数据类型是决定表达式结果的数据类型。float 型和 double 型相乘,结果是 double 型的;int 和 long 相加,结果是 long 型。
Java没有sizeof
在 C/C++ 中,经常需要用到 sizeof()
方法来获取数据项被分配的字节大小。C/C++ 中使用 sizeof()
最有说服力的原因是为了移植性,不同数据在不同机器上可能有不同的大小,所以在进行大小敏感的运算时,程序员必须对这些类型有多大做到心中有数。例如,一台计算机可用 32 位来保存整数,而另一台只用 16 位保存。显然,在第一台机器中,程序可保存更大的值。所以,移植是令 C/C++ 程序员颇为头痛的一个问题。
Java 不需要 sizeof()
方法来满足这种需求,因为所有类型的大小在不同平台上是相同的。我们不必考虑这个层次的移植问题 —— Java 本身就是一种“与平台无关”的语言。
总结
上述示例分别向我们展示了哪些基本类型能被用于特定的运算符。基本上,下面的代码示例是对上述所有示例的重复,只不过概括了所有的基本类型。这个文件能被正确地编译,因为我已经把编译不通过的那部分用注释 //
过滤了。代码示例:
// operators/AllOps.java
// 测试所有基本类型的运算符操作
// 看看哪些是能被 Java 编译器接受的
public class AllOps {
// 布尔值的接收测试:
void f(boolean b) {
}
void boolTest(boolean x, boolean y) {
// 算数运算符:
//- x = x * y;
//- x = x / y;
//- x = x % y;
//- x = x + y;
//- x = x - y;
//- x++;
//- x--;
//- x = +y;
//- x = -y;
// 关系运算符和逻辑运算符:
//- f(x > y);
//- f(x >= y);
//- f(x < y);
//- f(x <= y);
f(x == y);
f(x != y);
f(!y);
x = x && y;
x = x || y;
// 按位运算符:
//- x = ~y;
x = x & y;
x = x | y;
x = x ^ y;
//- x = x << 1;
//- x = x >> 1;
//- x = x >>> 1;
// 联合赋值:
//- x += y;
//- x -= y;
//- x *= y;
//- x /= y;
//- x %= y;
//- x <<= 1;
//- x >>= 1;
//- x >>>= 1;
x &= y;
x ^= y;
x |= y;
// 类型转换:
//- char c = (char)x;
//- byte b = (byte)x;
//- short s = (short)x;
//- int i = (int)x;
//- long l = (long)x;
//- float f = (float)x;
//- double d = (double)x;
}
void charTest(char x, char y) {
// 算数运算符:
x = (char) (x * y);
x = (char) (x / y);
x = (char) (x % y);
x = (char) (x + y);
x = (char) (x - y);
x++;
x--;
x = (char) +y;
x = (char) -y;
// 关系和逻辑运算符:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//- f(!x);
//- f(x && y);
//- f(x || y);
// 按位运算符:
x = (char) ~y;
x = (char) (x & y);
x = (char) (x | y);
x = (char) (x ^ y);
x = (char) (x << 1);
x = (char) (x >> 1);
x = (char) (x >>> 1);
// 联合赋值:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
x <<= 1;
x >>= 1;
x >>>= 1;
x &= y;
x ^= y;
x |= y;
// 类型转换
//- boolean bl = (boolean)x;
byte b = (byte) x;
short s = (short) x;
int i = (int) x;
long l = (long) x;
float f = (float) x;
double d = (double) x;
}
void byteTest(byte x, byte y) {
// 算数运算符:
x = (byte) (x * y);
x = (byte) (x / y);
x = (byte) (x % y);
x = (byte) (x + y);
x = (byte) (x - y);
x++;
x--;
x = (byte) +y;
x = (byte) -y;
// 关系和逻辑运算符:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//- f(!x);
//- f(x && y);
//- f(x || y);
// 按位运算符:
x = (byte) ~y;
x = (byte) (x & y);
x = (byte) (x | y);
x = (byte) (x ^ y);
x = (byte) (x << 1);
x = (byte) (x >> 1);
x = (byte) (x >>> 1);
// 联合赋值:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
x <<= 1;
x >>= 1;
x >>>= 1;
x &= y;
x ^= y;
x |= y;
// 类型转换:
//- boolean bl = (boolean)x;
char c = (char) x;
short s = (short) x;
int i = (int) x;
long l = (long) x;
float f = (float) x;
double d = (double) x;
}
void shortTest(short x, short y) {
// 算术运算符:
x = (short) (x * y);
x = (short) (x / y);
x = (short) (x % y);
x = (short) (x + y);
x = (short) (x - y);
x++;
x--;
x = (short) +y;
x = (short) -y;
// 关系和逻辑运算符:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//- f(!x);
//- f(x && y);
//- f(x || y);
// 按位运算符:
x = (short) ~y;
x = (short) (x & y);
x = (short) (x | y);
x = (short) (x ^ y);
x = (short) (x << 1);
x = (short) (x >> 1);
x = (short) (x >>> 1);
// Compound assignment:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
x <<= 1;
x >>= 1;
x >>>= 1;
x &= y;
x ^= y;
x |= y;
// 类型转换:
//- boolean bl = (boolean)x;
char c = (char) x;
byte b = (byte) x;
int i = (int) x;
long l = (long) x;
float f = (float) x;
double d = (double) x;
}
void intTest(int x, int y) {
// 算术运算符:
x = x * y;
x = x / y;
x = x % y;
x = x + y;
x = x - y;
x++;
x--;
x = +y;
x = -y;
// 关系和逻辑运算符:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//- f(!x);
//- f(x && y);
//- f(x || y);
// 按位运算符:
x = ~y;
x = x & y;
x = x | y;
x = x ^ y;
x = x << 1;
x = x >> 1;
x = x >>> 1;
// 联合赋值:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
x <<= 1;
x >>= 1;
x >>>= 1;
x &= y;
x ^= y;
x |= y;
// 类型转换:
//- boolean bl = (boolean)x;
char c = (char) x;
byte b = (byte) x;
short s = (short) x;
long l = (long) x;
float f = (float) x;
double d = (double) x;
}
void longTest(long x, long y) {
// 算数运算符:
x = x * y;
x = x / y;
x = x % y;
x = x + y;
x = x - y;
x++;
x--;
x = +y;
x = -y;
// 关系和逻辑运算符:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//- f(!x);
//- f(x && y);
//- f(x || y);
// 按位运算符:
x = ~y;
x = x & y;
x = x | y;
x = x ^ y;
x = x << 1;
x = x >> 1;
x = x >>> 1;
// 联合赋值:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
x <<= 1;
x >>= 1;
x >>>= 1;
x &= y;
x ^= y;
x |= y;
// 类型转换:
//- boolean bl = (boolean)x;
char c = (char) x;
byte b = (byte) x;
short s = (short) x;
int i = (int) x;
float f = (float) x;
double d = (double) x;
}
void floatTest(float x, float y) {
// 算数运算符:
x = x * y;
x = x / y;
x = x % y;
x = x + y;
x = x - y;
x++;
x--;
x = +y;
x = -y;
// 关系和逻辑运算符:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//- f(!x);
//- f(x && y);
//- f(x || y);
// 按位运算符:
//- x = ~y;
//- x = x & y;
//- x = x | y;
//- x = x ^ y;
//- x = x << 1;
//- x = x >> 1;
//- x = x >>> 1;
// 联合赋值:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
//- x <<= 1;
//- x >>= 1;
//- x >>>= 1;
//- x &= y;
//- x ^= y;
//- x |= y;
// 类型转换:
//- boolean bl = (boolean)x;
char c = (char) x;
byte b = (byte) x;
short s = (short) x;
int i = (int) x;
long l = (long) x;
double d = (double) x;
}
void doubleTest(double x, double y) {
// 算术运算符:
x = x * y;
x = x / y;
x = x % y;
x = x + y;
x = x - y;
x++;
x--;
x = +y;
x = -y;
// 关系和逻辑运算符:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//- f(!x);
//- f(x && y);
//- f(x || y);
// 按位运算符:
//- x = ~y;
//- x = x & y;
//- x = x | y;
//- x = x ^ y;
//- x = x << 1;
//- x = x >> 1;
//- x = x >>> 1;
// 联合赋值:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
//- x <<= 1;
//- x >>= 1;
//- x >>>= 1;
//- x &= y;
//- x ^= y;
//- x |= y;
// 类型转换:
//- boolean bl = (boolean)x;
char c = (char) x;
byte b = (byte) x;
short s = (short) x;
int i = (int) x;
long l = (long) x;
float f = (float) x;
}
}
注意 :boolean 类型的运算是受限的。你能为其赋值 true
或 false
,也可测试它的值是否是 true
或 false
。但你不能对其作加减等其他运算。
在 char,byte 和 short 类型中,我们可以看到算术运算符的“类型转换”效果。我们必须要显式强制类型转换才能将结果重新赋值为原始类型。对于 int 类型的运算则不用转换,因为默认就是 int 型。虽然我们不用再停下来思考这一切是否安全,但是两个大的 int 型整数相乘时,结果有可能超出 int 型的范围,这种情况下结果会发生溢出。下面的代码示例:
// operators/Overflow.java
// 厉害了!内存溢出
public class Overflow {
public static void main(String[] args) {
int big = Integer.MAX_VALUE;
System.out.println("big = " + big);
int bigger = big * 4;
System.out.println("bigger = " + bigger);
}
}
输出结果:
big = 2147483647
bigger = -4
编译器没有报错或警告,运行时一切看起来都无异常。诚然,Java 是优秀的,但是还不足够优秀。
对于 char,byte 或者 short,混合赋值并不需要类型转换。即使为它们执行转型操作,也会获得与直接算术运算相同的结果。另外,省略类型转换可以使代码显得更加简练。总之,除 boolean 以外,其他任何两种基本类型间都可进行类型转换。当我们进行向下转换类型时,需要注意结果的范围是否溢出,否则我们就很可能在不知不觉中丢失精度。