一、引言 操作符在编程中的核心地位 操作符是程序中对数据进行计算、判断和转换的核心工具。操作符定义数据被处理方式，将原始输入的数据（操作数）转化为有意义的中间结果，为程序逻辑提供计算能力。在Java编程中，操作符是实现数据运算和赋值的关键。对于初学者来说，理解和掌握各种操作符的用法和特性是非常重要的。 Java操作符的设计哲学与特性 Java操作符的设计哲学以简约性、强类型安全和封装性为核心，通过严格规范操作符行为与优先级，确保代码的可预测性和可维护性。理解Java操作符的特性，有助于开发者规避常见陷阱（如整数除法截断、别名问题），并充分利用位运算等特性优化性能关键代码。 二、Java操作符分类与详解 Java中的操作符，按照操作数被处理的方式，可以分为算术操作符、赋值操作符、位操作符、关系操作符、逻辑操作符以及三元操作符。不管哪种操作符，都可以通过一个或多个操作数来生成新的值，有的操作符还可以修改其操作数的值。另外，几乎所有的操作符（除开=、==、!=）都只能操作基本数据类型的操作数。 1.算术操作符 Java的算数操作符与其他大部分编程语言相同，包括加法（+）、减法（-）、乘法（*）、除法（/）、求模（%）和自增（++）、自减（--），具体见下表： 操作符 名称 示例 功能描述 + 加法操作符 a + b 加法 - 计算操作符两侧的值的和 - 减法操作符 a - b 减法 - 计算操作符左侧的值减去右侧的值的差 * 乘法操作符 a * b 乘法 - 计算操作符两侧的值的积 / 除法操作符 a / b 除法 - 计算操作符左侧的值除以右侧的值的商 % 求模操作符 a % b 求模 - 计算操作符左侧的值除以右侧的值的余数 ++ 自增操作符 a++或++a 操作数的值增加1 -- 自减操作符 a--或--a 操作数的值增减1 下面的示例展示了在四则运算中，部分算数操作符（+、-、*、/、%）的用法。 import java.util.Random; public class MathOps { public static void main(String[] args) { // 创建Random对象，为后续四则运算生成随机数: Random rand = new Random(27); int i, a, b; // 生成随机整数a和b并进行四则运算: a = rand.nextInt(100) + 1; System.out.println("a : " + a); b = rand.nextInt(100) + 1; System.out.println("b : " + b); i = a + b; System.out.println("a + b : " + i); i = a - b; System.out.println("a - b : " + i); i = a / b; System.out.println("a / b : " + i); i = a * b; System.out.println("a * b : " + i); i = a % b; System.out.println("a % b : " + i); a %= b; System.out.println("a %= b : " + a); // 生成随机浮点数s和n并进行四则运算: double u, s, n; s = rand.nextDouble(); System.out.println("s : " + s); n = rand.nextDouble(); System.out.println("n : " + n); u = s + n; System.out.println("s + n : " + u); u = s - n; System.out.println("s - n : " + u); u = s * n; System.out.println("s * n : " + u); u = s / n; System.out.println("s / n : " + u); s %= n; System.out.println("s % n : " + s); } } 程序执行后的结果如下： a : 31 b : 53 a + b : 84 a - b : -22 a / b : 0 a * b : 1643 a % b : 31 a %= b : 31 s : 0.7146368532850442 n : 0.5047406370101457 s + n : 1.21937749029519 s - n : 0.20989621627489852 s * n : 0.36070626055801924 s / n : 1.4158496480850609 s % n : 0.20989621627489852 上面的程序首先创建了一个Random对象，后续进行算术运算的整数和浮点数都通过调用Random对象的nextInt()方法和nextDouble()方法生成。但执行多次程序会发现每次生成的随机数都一样（所以运算结果也一样）。这是因为在创建Random对象，传递了随机种子（示例中是整数27），相同的随机种子会保证按照相同的序列生成随机数。如果去除创建Random对象的随机种子，那么Java会使用操作系统的当前时间作为随机种子。另外，Random对象除了nextInt()方法和nextDouble()方法之外，还有用于生成其他基本数据类型（除了char类型）的方法，方法名为next单词在前，首字母大写的基本数据类型名称在后。 一元加(+)和一元减（-）与二元加减法使用了相同的符号，Java会根据+、-操作符前后的操作数自行判断运算方式。一元加（+）会将比int类型小的数据类型（byte或者short）的操作数转换为int类型，一元减（-）会反转数据的正负值。 下面的示例展示了自增（++）、自减（--）运算符的用法： 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 自增（++）、自减（--）操作符都有两种使用方式，一种是放在操作数之前，称为前缀式自增或前缀式自减，如++i或--i，另一种是放在操作数之后，称为后缀式自增或后缀式自减，如i++或i--。 自增（++）、自减（--）操作符对操作数的影响无论前缀式还是后缀式，都一样。假如i是一个int类型的数据，i++和++i，对i值的影响都等价于i = i + 1，同理，i--和--i，对i值的影响都等价于i = i - 1。 那么自增（++）、自减（--）运算符作为前缀和后缀到底有什么区别呢？其实它们的区别主要在于整个表达式的值。前缀式自增或前缀式自减会先对操作数进行自增或自减操作，然后将操作后的结果作为整个表达式的值，后缀式自增或后缀式自减会先把操作数作为整个表达式的值，然后对操作数进行自增或自减的操作。这样，就可以上面程序的运行结果了。int类型的变量i初始保存整数1，经过两次自增（++i和i++）之后值变为3，再经过两次自减（--i和i--）之后值变为1。两次自增的操作返回表达式++i和i++的值虽然相同，但其中i值是不同的；同理，两次自减操作返回表达式--i和i--虽然相同，但其中的i值也是不同的。 2.赋值操作符 赋值操作符（=）用于进行赋值操作。赋值操作符（=）的操作过程就是，取=右边的数据（通常简称右值），将其保存到=左边的变量（通常简称左值）。需要注意的是，在赋值操作过程中，右值可以是其他变量、常量或者是表达式，但左值只能是命名的变量。比如，int i = 0;表示把整数0保存到整型变量i中，10 = a这种写法就是错误的。 赋值操作符（=）操作的右值是基本数据类型时比较好理解，因为基本数据类型的数据是直接保存在左值的变量中，例如int a = 0; int b = a;中b = a就是直接将a中保存的0保存到b中。后续操作a或者b都不影响另一个变量中的数据，大多数情况下这也是我们希望的。 但赋值操作符（=）操作的右值如果是引用数据类型，就需要注意了，因为引用数据类型的数据保存在左值中的是对该对象的引用。例如Object a = new Object();Object b = a;中b = a实际上是将a指向的对象的引用，复制了一个相同的引用到b。后续操作a或者b都会影响另一个变量的数据，因为它们实际上都指向同一个对象。下面这个示例将演示这种情况： class Dog { int age; } public class Assignment { public static void main(String[] args) { Dog dog001 = new Dog(); dog001.age = 3; Dog dog002 = new Dog(); dog002.age = 5; System.out.println("1:dog001.age=" + dog001.age + ",dog002.age=" + dog002.age); dog001 = dog002; System.out.println("2:dog001.age=" + dog001.age + ",dog002.age=" + dog002.age); dog001.age = 7; System.out.println("3:dog001.age=" + dog001.age + ",dog002.age=" + dog002.age); } } 程序执行后的结果如下： 1:dog001.age=3,dog002.age=5 2:dog001.age=5,dog002.age=5 3:dog001.age=7,dog002.age=7 Dog类只有一个成员变量age保存整型数据。它的两个实例dog001和dog002都在main()方法中实例化出来，每个实例的age属性都保存了一个不同的整型数据。然后dog001 = dog002;将dog002赋值给了dog001，接着修改了dog001的age。由于赋值操作符在dog001 = dog002;中操作的是引用数据类型，它实际上让dog001和dog002指向了一个相同的Dog实例，即原来的dog002，而原来的dog001实例因为不再被引用而被垃圾回收机制清理掉。这种情况在编程中通常成为“别名”，也是Java操作对象的一种基本方式。如果想避免对象出现别名，可以将dog001 = dog002;替换为dog001.age = dog002.age;，这样就可以保持dog001和dog002两个实例独立，避免别名将dog001和dog002指向同一个Dog的实例。 注意上面避免别名的方式中，直接操作了对象的属性，这种操作方式不符合Java面向对象的封装特性：隐藏对象的属性和实现细节，仅对外提供公有的访问方式。 赋值操作符（=）还可以和其他部分操作符合并使用，这种合并使用的操作符被称为复合赋值操作符。它将算术操作、位操作与赋值操作结合为单一操作符。 算术复合赋值操作符 通用形式为“算数操作符=”，具体见下表： 操作符 名称 示例 说明 += 加法赋值 a += 5 等价于 a = a + 5 -= 减法赋值 a -= 3 等价于 a = a - 3 *= 乘法赋值 a *= 2 等价于 a = a * 2 /= 除法赋值 a /= 4 等价于 a = a / 4 %= 取模赋值 a %= 5 等价于 a = a % 5 位运算复合赋值操作符 通用形式为“算数操作符=”，具体见下表： 操作符 名称 示例 说明 &= 按位与赋值 a += 5 等价于 a = a + 5 |= 按位或赋值 a |= b 等价于 a = a | b ^= 按位异或赋值 a ^= b 等价于 a = a ^ b <<= 左移赋值 a <<= 2 等价于 a = a << 2 >>= 带符号右移赋值 a >>= 1 等价于 a = a >> 1 >>>= 无符号右移赋值 a >>>= 1 等价于 a = a >>> 1 Java的复合赋值操作符通过简化表达式和隐式优化，成为提升代码效率的核心工具，涵盖算术操作和位操作，支持自动类型转换，但需警惕数据溢出和优先级陷阱。 另外+和+=还有一种特殊的用法，它们可以对Java中的字符串String对象进行连接操作。但当字符串String对象与其他数据类型进行连接时，一定要注意数据类型转换，下面这个示例将演示这种情况： public class StringOperation { public static void main(String[] args) { int a = 0, b = 1, c = 2; String s = "a, b, c "; System.out.println(s + a + b + c); // 变量a, b, c转换为字符串 System.out.println(a + " " + s); // 变量a转换为字符串 s += "(summed) = "; // 字符串s连接新的字符串 System.out.println(s + (a + b + c));// 变量a, b, c中的整型求和 System.out.println("" + a);// 变量a转换为字符串，Integer.toString()的简化版 } } 程序执行后的结果如下： a, b, c 012 0 a, b, c a, b, c (summed) = 3 0 注意第一个输出内容中的012，实际上是s和a，b，c中的0，1，2被转化的字符串进行了连接操作。第二个输出内容中的0也是a中的数值被转换为字符串后与s进行了连接操作。因此使用字符串连接操作符时，其他数据类型是否转换为字符串并不取决于先后顺序。第三个输出内容通过()改变了a + b + c的优先级，所以优先进行了求和才被转换为字符串。第四个输出内容使用了"" + a的形式进行输出，实际上这是一种针对基本数据类型进行转换的方式，它等效于调用a的基本数据类型对应的包装类的toString()方法。 3.位操作符 前面在符合赋值操作符中见到的位操作符主要用于操作二进制数据。位操作符源自C语言，由于C语言面向底层的特性，它经常需要直接操作底层硬件寄存器中的二进制位。Java也用位操作符实现了C语言中类似的底层操作。通过对整数类型（byte、short、int、long）的二进制位进行运算，可实现高性能计算、内存优化等场景需求。 Java支持的位操作符按照对二进制位的操作方式主要分为按位操作和移位操作，具体见下表： 操作符 名称 分类 示例 功能描述 & 按位与 按位 a & b 两个对应位均为1时结果为1，否则为0 | 按位或 按位 a | b 任意一个对应位为1时结果为1 ^ 按位异或 按位 a ^ b 对应位不同时结果为1，相同时为0 ~ 按位非 按位 ~a 每一位取反（0变1，1变0） << 左移 移位 a << n 所有位左移n位，低位补0，相当于乘以2ⁿ >> 带符号右移 移位 a >> n 所有位右移n位，高位补符号位（正数补0，负数补1），相当于除以2ⁿ并向下取整 >>> 无符号右移 移位 a >>> n 所有位右移n位，高位补0 上表中只有按位非操作符（~）是一元操作符，它只对其右侧的操作数进行按位取反操作，所以它不能跟赋值操作符结合成复合赋值操作符使用。其他的位操作符都是二元操作符，可以跟赋值操作符结合成复合赋值操作符。按位与（&）、按位或（|）使用了和逻辑与（&&）、逻辑或（||）相同的字符，使用时一定要注意区分。 字符类型（char）由于可以转换成整型，也可以使用位操作符操作。 布尔类型（boolean）在被位操作符操作时，也需要注意，只能对布尔值进行按位与（&）、按位或（|）和按位异或（^）操作，不能对其进行按位非（~）操作，需要对布尔值取反只能使用逻辑非（!）。另外，对布尔值进行按位与（&）、按位或（|）操作时，效果和逻辑与（&&）、逻辑或（||）相同，但按位与（&）、按位或（|）不会“短路”。 下面这个示例将演示所有位操作符的操作情况： import java.util.Random; public class BitOperation { public static void main(String[] args) { Random rand = new Random(27); printBinary("-1", -1); printBinary("+1", +1); int maxInt = 2147483647; printBinary("maxInt", maxInt); int minInt = -2147483648; printBinary("minInt", minInt); int i = rand.nextInt(); printBinary("i", i); printBinary("~i", ~i); printBinary("-i", -i); printBinary("i << 5", i << 5); printBinary("i >> 5", i >> 5); printBinary("(~i) >> 5", (~i) >> 5); printBinary("i >>> 5", i >>> 5); printBinary("(~i) >>> 5", (~i) >>> 5); int j = rand.nextInt(); printBinary("j", j); printBinary("i & j", i & j); printBinary("i | j", i | j); printBinary("i ^ j", i ^ j); printBinary("-1L", -1L); printBinary("+1L", +1L); long maxLong = 9223372036854775807L; printBinary("maxLong", maxLong); long minLong = -9223372036854775808L; printBinary("minLong", minLong); long l = rand.nextLong(); printBinary("l", l); printBinary("~l", ~l); printBinary("-l", -l); printBinary("l << 5", l << 5); printBinary("l >> 5", l >> 5); printBinary("(~l) >> 5", (~l) >> 5); printBinary("l >>> 5", l >>> 5); printBinary("(~l) >>> 5", (~l) >>> 5); long m = rand.nextLong(); printBinary("m", m); printBinary("l & m", l & m); printBinary("l | m", l | m); printBinary("l ^ m", l ^ m); } static void printBinary(String s, int i) { String BinaryStr = Integer.toBinaryString(i); while(BinaryStr.length() < 32){ BinaryStr = "0"+BinaryStr; } System.out.println(s + ", int: " + i + ", binary:\n " + BinaryStr); } static void printBinary(String s, long l) { String BinaryStr = Long.toBinaryString(l); while(BinaryStr.length() < 64){ BinaryStr = "0"+BinaryStr; } System.out.println(s + ", long: " + l + ", binary:\n " + BinaryStr); } } 程序执行后的结果如下： -1, int: -1, binary: 11111111111111111111111111111111 +1, int: 1, binary: 00000000000000000000000000000001 maxInt, int: 2147483647, binary: 01111111111111111111111111111111 minInt, int: -2147483648, binary: 10000000000000000000000000000000 i, int: -1152021836, binary: 10111011010101011000101010110100 ~i, int: 1152021835, binary: 01000100101010100111010101001011 -i, int: 1152021836, binary: 01000100101010100111010101001100 i << 5, int: 1790006912, binary: 01101010101100010101011010000000 i >> 5, int: -36000683, binary: 11111101110110101010110001010101 (~i) >> 5, int: 36000682, binary: 00000010001001010101001110101010 i >>> 5, int: 98217045, binary: 00000101110110101010110001010101 (~i) >>> 5, int: 36000682, binary: 00000010001001010101001110101010 j, int: 1761585305, binary: 01101000111111111010100010011001 i & j, int: 676694160, binary: 00101000010101011000100010010000 i | j, int: -67130691, binary: 11111011111111111010101010111101 i ^ j, int: -743824851, binary: 11010011101010100010001000101101 ……此处省略长整型（long）数据的操作结果 在上面的示例代码中，BitOperation类中，main()方法之外定义了两个无返回值的静态方法printBinary()，它们的方法名相同，但通过第二个参数的数据类型（int或者long）进行方法重载，将传入的int类型参数或者long类型参数通过其包装类的toBinaryString()方法转换成字符串，由于toBinaryString()方法在转换字符串时会忽略数值的二进制位高位中的前导0，所以方法中通过一个while循环对转换之后字符串补充高位0。这个示例对int和long类型的数值使用所有的位操作符进行了操作并展示了结果操作结果，此外，还展示了int和long的最大值、最小值、正1值和负1值的二进制形式。 注意所有整型数据都用二进制形式的最高位表示符号：0表示正数，1表示负数。 4.关系与逻辑操作符 关系与逻辑操作符返回的操作结果都是布尔值。 关系操作符会根据操作符左右两侧的操作数的值之间的关系（大小或相等），返回布尔值（true/false）。如果关系为真，关系表达式会返回true；如果关系不为真，关系表达式会返回false。关系操作符是控制程序逻辑的核心工具，常用于条件判断和循环控制。 关系操作符包括小于（<）、大于（>）、小于等于（<=）、大于等于（>=）、相等于（==）和不相等于（!=），具体见下表： 操作符 名称 示例 功能描述 < 小于操作符 a < b 检查左侧操作数的值是否小于右侧操作数的值 > 大于操作符 a > b 检查左侧操作数的值是否大于右侧操作数的值 <= 小于等于操作符 a <= b 检查左侧操作数的值是否小于等于右侧操作数的值 >= 大于等于操作符 a >= b 检查左侧操作数的值是否大于等于右侧操作数的值 == 相等于操作符 a == b 检查左侧操作数的值是否相等于右侧操作数的值 != 不相等于操作符 a != b 检查左侧操作数的值是否不相等于右侧操作数的值 下面这个示例将演示所有关系操作符的操作情况： import java.util.Random; public class RelationalOperation { public static void main(String[] args) { Random rand = new Random(27); int a = rand.nextInt(); int b = rand.nextInt(); System.out.println("a: " + a); System.out.println("b: " + b); System.out.println("a < b : " + (a < b)); System.out.println("a > b : " + (a > b)); System.out.println("b <= a : " + (b <= a)); System.out.println("b >= a : " + (b >= a)); System.out.println("a == b : " + (a == b)); System.out.println("a != b : " + (a != b)); } } 程序执行后的结果如下： a: -1152021836 b: 1761585305 a < b : true a > b : false b <= a : false b >= a : true a == b : false a != b : true 小于（<）、大于（>）、小于等于（<=）和大于等于（>=）适用于除布尔类型（boolean）之外的基本数据类型，因为布尔类型只有true和false两个值，对这两个值比较大小没有意义；相等于（==）和不相等于（!=）适用于所有基本数据类型。 相等于（==）和不相等于（!=）还适用于所有引用数据类型，但在操作引用数据类型时，它们的返回结果可能会引起困惑。 下面这个示例将演示相等于（==）操作符对引用数据类型的操作情况： public class EqualsOperation { static void show(String desc, Integer n1, Integer n2) { System.out.println(desc + ":"); System.out.printf("%d==%d的返回值：%b，换用equals()时的返回值：%b%n", n1, n2, n1 == n2, n1.equals(n2)); } public static void testEquals(int value) { // [1] 自动转换为Integer对象 Integer i1 = value; Integer i2 = value; show("[1]自动转换", i1, i2); // [2] 使用Integer的构造方法实例化Integer对象 Integer r1 = new Integer(value); Integer r2 = new Integer(value); show("[2]构造方法", r1, r2); // [3] 使用Integer.valueOf()方法创建Integer对象 Integer v1 = Integer.valueOf(value); Integer v2 = Integer.valueOf(value); show("[3]Integer.valueOf()", v1, v2); // [4] 基本数据类型int int x = value; int y = value; // x.equals(y); // 基本数据类型不能使用equals()比较 show("[4]基本数据类型int", x, y); } public static void main(String[] args) { testEquals(127); testEquals(128); } } 程序执行后的结果如下： [1]自动转换: 127==127的返回值：true，换用equals()时的返回值：true [2]构造方法: 127==127的返回值：false，换用equals()时的返回值：true [3]Integer.valueOf(): 127==127的返回值：true，换用equals()时的返回值：true [4]基本数据类型int: 127==127的返回值：true，换用equals()时的返回值：true [1]自动转换: 128==128的返回值：false，换用equals()时的返回值：true [2]构造方法: 128==128的返回值：false，换用equals()时的返回值：true [3]Integer.valueOf(): 128==128的返回值：false，换用equals()时的返回值：true [4]基本数据类型int: 128==128的返回值：false，换用equals()时的返回值：true 在上面的示例代码中，EqualsOperation类中的main()方法调用testEquals()方法，testEquals()方法将传入的整型数据按照不同的方式转换为Integer对象，再将转换后的两个Integer对象通过参数传递给show()方法，在show()方法中分别使用相等于（==）操作符和equals()方法对两个Integer对象进行比较并输出结果。 如果要查看不相等于（!=）操作符，可以将show()方法中的相等于（==）操作符替换为不相等于（!=）操作符，并将n1.equals(n2)修改为!n1.equals(n2)。 需要注意testEquals()方法中将接受的整型参数转换为Integer对象的几种不同方式： [1] 自动转换为Integer对象，本质上是通过调用Integer.valueOf()方法创建Integer对象。 [2] 使用Integer的构造方法实例化Integer对象，这种方式在Java 9之后的版本中已经不推荐使用。它是以前使用包装类进行装箱操作的主要方式。 [3] 使用Integer.valueOf()方法创建Integer对象，从Java 9开始推荐的使用包装类进行装箱操作的方式，它的效率优于方式[2]，但代码较[1]复杂。 [4] 基本数据类型int也可以直接当做参数传递给show()，虽然show()方法接受的参数类型是Integer。 在程序执行后的结果中，127的比较结果相对容易理解，传递给show()方法的4种参数都产生了预期的结果。方式[2]中的相等于（==）操作符返回了false是因为虽然两个Integer对象保存的数据都是127，但两个Integer对象是各自通过构造方法创建的，所以它们通过相等于（==）操作符比较的引用地址是不同的。但128的比较结果就容易让人疑惑，转换为Integer对象之后，所有保存了128的对象通过相等于（==）操作符返回的结果都是false，所有通过equals()方法比较返回的结果都是true。这是因为在使用Integer对象时，Java会通过享元模式来缓存数值范围在-128到+127内的对象，因此在这个范围内无论使用多少次方式[1]和方式[3]来创建Integer对象，只要数值相同，它们引用的都是同一个Integer对象。而超出这个数值范围，即使数值相同，Java也会实例化新的Integer对象。所以，通常都会建议：使用操作符操作基本数据类型，使用equals()方法操作引用数据类型。 另外使用关系操作符操作浮点数时，也会遇到一些返回结果超出预期的情况，下面这个示例将演示相等于（==）操作符对浮点数的操作情况： public class EqualsDouble { static void show(String desc, Double n1, Double n2) { System.out.println(desc + ":"); System.out.printf("%e==%e的返回值：%b，换用equals()时的返回值：%b%n", n1, n2, n1 == n2, n1.equals(n2)); } public static void testEquals(double value1, double value2) { // [1] 自动转换为Double对象 Double d1 = value1; Double d2 = value2; show("[1]自动转换", d1, d2); // [2] 使用Double的构造方法实例化Double对象 Double r1 = new Double(value1); Double r2 = new Double(value2); show("[2]构造方法", r1, r2); // [3] 使用Double.valueOf()方法创建Integer对象 Double v1 = Double.valueOf(value1); Double v2 = Double.valueOf(value2); show("[3]Double.valueOf()", v1, v2); // [4] 基本数据类型double double x = value1; double y = value2; // x.equals(y); // 基本数据类型不能使用equals()比较 show("[4]基本数据类型int", x, y); } public static void main(String[] args) { testEquals(0, Double.MIN_VALUE); testEquals(Double.MAX_VALUE, Double.MAX_VALUE - Double.MIN_VALUE * 1_000_000); } } 程序执行后的结果如下： [1]自动转换: 0.000000e+00==4.900000e-324的返回值：false，换用equals()时的返回值：false [2]构造方法: 0.000000e+00==4.900000e-324的返回值：false，换用equals()时的返回值：false [3]Integer.valueOf(): 0.000000e+00==4.900000e-324的返回值：false，换用equals()时的返回值：false [4]基本数据类型int: 0.000000e+00==4.900000e-324的返回值：false，换用equals()时的返回值：false [1]自动转换: 1.797693e+308==1.797693e+308的返回值：false，换用equals()时的返回值：true [2]构造方法: 1.797693e+308==1.797693e+308的返回值：false，换用equals()时的返回值：true [3]Integer.valueOf(): 1.797693e+308==1.797693e+308的返回值：false，换用equals()时的返回值：true [4]基本数据类型int: 1.797693e+308==1.797693e+308的返回值：false，换用equals()时的返回值：true 在上面的示例代码中，沿用EqualsOperation类的代码，只是将需要比较的数据类型换成了Double对象。代码中的Double.MIN_VALUE和Double.MAX_VALUE分别表示Double对象可以表示的最小浮点数和最大浮点数，为了方便查看输出结果，在格式化输出内容时使用了e%指定数值使用科学计数法方式输出。第一次比较0和最小浮点数，两个数值明显不同，所以返回结果false还在预想之中，但第二次比较最大浮点数，和最大浮点数减去一百万倍的最小浮点数，从数学角度看，这两个数值明显不同，但equals()方法都返回了true值。这是由浮点数在计算机中的特性决定的，当一个非常大的数值减去一个相对较小的数值时，这个非常大的数值不会发生明显变化，这就是编程语言中的“舍入误差”，这种误差产生的原因主要是因为计算机不能用足够的二进制位数精确的表示一个非常大的数值的微小变化。另外，相等于（==）操作符返回的结果都是false，是因为相等于（==）操作符比较的是Double对象的引用地址。 基于上面的两个示例，在进行关系操作时，通常都会建议：使用操作符操作基本数据类型，使用equals()方法操作引用数据类型。但是需要注意的是，并不是每个引用数据类型都重写了equals()方法。大部分标准库会重写equals()方法来比较对象的内容而非对象的引用。 逻辑操作符用于根据操作数的逻辑关系，进行组合或修改，返回新的布尔值。逻辑操作符是构建复杂条件判断的基础。 逻辑操作符包括逻辑与（&&）、逻辑或（||）和逻辑非（!），具体见下表： 操作符 名称 示例 功能描述 && 逻辑与操作符 a && b 当且仅当左右两个操作数都为true，才返回true || 逻辑或于操作符 a || b 左右两个操作数任一为true，就返回true ! 逻辑非操作符 !a 用来反转操作数的逻辑状态，将true反转为false，将false反转为true 下面这个示例将演示关系操作符和逻辑操作符的操作情况： import java.util.Random; public class LogicOperation { public static void main(String[] args) { Random rand = new Random(27); int a = rand.nextInt(); int b = rand.nextInt(); System.out.println("a: " + a); System.out.println("b: " + b); System.out.println("a < b : " + (a < b)); System.out.println("a > b : " + (a > b)); System.out.println("b <= a : " + (b <= a)); System.out.println("b >= a : " + (b >= a)); System.out.println("a == b : " + (a == b)); System.out.println("a != b : " + (a != b)); // Java中不允许将int类型当作boolean类型使用，以下注释的代码是错误的: // System.out.println("a && b : " + (a && b)); // System.out.println("a || b : " + (a || b)); // System.out.println("!a : " + !a); System.out.println("(a < 10) && (b < 10) : " + ((a < 10) && (b < 10))); System.out.println("(a < 10) || (b < 10) : " + ((a < 10) || (b < 10))); } } 程序执行后的结果如下： a: -1152021836 b: 1761585305 a < b : true a > b : false b <= a : false b >= a : true a == b : false a != b : true (a < 10) && (b < 10) : false (a < 10) || (b < 10) : true 在上面的示例代码中，注释的代码内容是错误的用法，因为在Java中，逻辑与（&&）、逻辑或（||）和逻辑非（!）只能用于操作boolean类型的数据，所以即使将示例代码中的int类型替换为其他除boolean之外的任何基本数据类型，代码都可以正常运行。示例代码的最后两个输出内容，先由关系操作符比较变量a、b和10的大小关系，再由逻辑操作符根据返回的布尔值结果进行逻辑操作，最终返回逻辑操作的布尔值结果。 逻辑与（&&）和逻辑或（||）在进行逻辑操作时，还具有“短路”的特性，这种特性导致它们在进行逻辑操作是，一旦表达式的部分操作结果能够确定整个表达式的返回值，那么表达式剩余的部分就不会继续操作。比如，condition1 && condition2，如果condition1返回了false，无论condition2返回的布尔值是什么，整个表达式的值都已经由逻辑与（&&）左侧的false确定了整个表达式的返回值是false，那么condition2就不会被执行。同样，condition1 || condition2，如果condition1返回了true，无论condition2返回的布尔值是什么，整个表达式的值都已经由逻辑或（||）左侧的true确定了整个表达式的返回值是true，那么condition2就不会被执行。 下面这个示例将演示逻辑与（&&）和逻辑或（||）的短路操作情况： public class LogicShortCircuit { static boolean condition1(int val) { System.out.println("condition1(" + val + ")"); System.out.println("返回的结果是: " + (val < 1)); return val < 1; } static boolean condition2(int val) { System.out.println("condition2(" + val + ")"); System.out.println("返回的结果是: " + (val < 2)); return val < 2; } static boolean condition3(int val) { System.out.println("condition3(" + val + ")"); System.out.println("返回的结果是: " + (val < 3)); return val < 3; } public static void main(String[] args) { boolean b1 = condition1(0) && condition2(2) && condition3(2); System.out.println("表达式condition1(0) && condition2(2) && condition3(2)"); System.out.println("返回的结果是: " + b1); boolean b2 = condition1(0) || condition2(2) || condition3(2); System.out.println("表达式condition1(0) || condition2(2) || condition3(2)"); System.out.println("返回的结果是: " + b2); } } 程序执行后的结果如下： condition1(0) 返回的结果是: true condition2(2) 返回的结果是: false 表达式condition1(0) && condition2(2) && condition3(2) 返回的结果是: false condition1(0) 返回的结果是: true 表达式condition1(0) || condition2(2) || condition3(2) 返回的结果是: true 在上面的示例代码中，condition1()、condition2()和condition3()分别将接受到的整型参数同0，1和2进行小于比较，并返回结果。表达式condition1(0) && condition2(2) && condition3(2)在计算到condition2(2)时得到了false，就将false作为整个表达式的返回值并结束操作，所以condition3(2)被短路未执行；同理，表达式condition1(0) || condition2(2) || condition3(2)在计算到condition1(0)时得到了true，就将true作为整个表达式的返回值并结束操作，所以condition2(2)和condition3(2)被短路未执行。Java的逻辑短路特性可以快速返回逻辑操作结果，从某种程度上看，还可以减少系统资源开销。但是需要注意避免在可能逻辑短路的表达式中对数据进行操作。 5.三元操作符 三元操作符比较特殊，它有三个操作数，不过它确实是一个操作符，因为它会根据它的三个操作数返回一个操作结果。三元表达式的形式如下： expression ? value0 : value1 如果expression的操作结果是布尔值true，value0就会返回操作结果，这个操作结果会作为整个三元表达式的操作结果；反之，如果expression的操作结果是布尔值false，value1就会返回操作结果，这个操作结果会作为整个三元表达式的操作结果。所以，三元操作符也常被称为条件操作符。但三元操作符与程序流程控制中的if…else…语句仍要进行区分，因为三元操作符会返回操作结果。 下面这个示例将演示三元操作符的操作情况： public class TernaryOperation { static int ternary(int i) { return (i < 10) ? i * 10 : i / 2; } static int standardIfElse(int i) { if (i < 10) return i * 10; else return i / 2; } 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)); } } 程序执行后的结果如下： 90 5 90 5 在上面的示例代码中，ternary()方法接受int类型参数，将接收的参数值通过三元操作符处理并返回三元表达式的返回值，standardIfElse()方法接受int类型参数，通过if…else…语句判断接收的参数值是否小于10，再决定返回哪一个计算结果。这两个方法相比，三元操作符使得代码更加简洁，if…else…语句更容易理解，各有优势，但三元操作符更适合需要从两个值选择一个赋值给变量的操作场景。 6.操作符的优先级和结合性 当一个表达式中出现了多个操作符时，操作符的优先级会决定表达式的操作顺序。操作符的结合性则决定了操作数的操作方向。 优先级 操作符名称 操作符说明 结合性 1 () 分隔符 从左向右 2 ! +（一元正） -（一元负） ~ ++ -- 一元操作符 从右向左 3 * / % 算术操作符 从左向右 4 +（加法） -（减法） 算术操作符 从左向右 5 << >> >>> 移位操作符 从左向右 6 < <= >= > instanceof 关系操作符 从左向右 7 == != 关系操作符 从左向右 8 & 按位操作符 从左向右 9 ^ 按位操作符 从左向右 10 | 按位操作符 从左向右 11 && 逻辑操作符 从左向右 12 || 逻辑操作符 从左向右 13 ?: 条件操作符 从右向左 14 = += *= /= %= &= ^= <<= >>= >>>= 赋值操作符 从右向左 在编写复杂的表达式时，如果不确定操作符的优先级，可以使用括号来明确指定操作的顺序。例如，表达式 a + b * c 中，乘法操作 b * c 会先于加法操作执行，因为乘法操作符的优先级高于加法操作符。如果想要先执行加法操作，则应该写成 (a + b) * c。在实际编程中，合理利用括号来避免优先级引起的错误是一个好习惯。 在使用按位操作符和移位操作符时，需要特别注意它们的优先级，因为它们可能会影响到数值的正负和大小。此外，逻辑操作符 （&&）和（||）具有短路特性，即在确定整个逻辑表达式的结果后，可能不会操作所有的操作数。
一、安装Java开发工具包（JDK） 1.Java与JDK的关系 要学习Java编程语言，需要在操作系统中安装JDK。可以降Java语言和JDK之间的关系理解为： Java语言定义了语法规则和功能特性，而JDK提供将这些规则转化为实际程序的工具集合。 JDK中主要包含： 编译器（javac）：将Java源代码（.java文件）编译为字节码（.class文件），这是Java跨平台的基础。 运行时环境（JRE）：包含JVM（Java虚拟机）和核心类库，负责执行编译后的字节码。 调试工具（jdb） 和文档生成器（javadoc） 等：支持开发全流程。 所以没有JDK，Java语言编写代码就无法编译和执行；没有Java语言的规范，JDK就失去存在意义。 以下是Java历代主要版本与JDK版本的对应关系： Java版本 发布时间 对应JDK版本 备注 Java1.0 1995.05.23 JDK1.0 首个正式版，提出“一次编写，到处运行”理念 Java1.1 1997.02.18 JDK1.1 引入内部类、JDBC等特性，JavaOne会议规模创纪录 Java1.2 1998.12.04 JDK1.2 划分为J2SE、J2EE、J2ME三大平台，奠定企业级应用基础(2005年6月正式取消，统一更名为Java SE、Java EE、Java ME) Java1.3 2000.05.08 JDK1.3 新增JNDI和动态代理，提升企业集成能力 Java1.4 2002.02.13 JDK1.4 引入断言（Assertions）、正则表达式和NIO Java5.0 2004.09.30 JDK1.8 更名为Java SE 5.0，支持泛型、枚举、自动装箱等 Java6 2006.12.11 JDK1.6 改进JVM性能，支持脚本语言 Java7 2011.07.28 JDK1.7 新增字符串Switch、二进制字面量等语法糖 Java8 2014.03.18 JDK8 里程碑版本，引入Lambda表达式、Stream API（长期支持版LTS） Java9 2017.09.21 JDK9 模块化系统（JPMS），每半年发布一次版本 Java10 2018.03.20 JDK10 Java11 2018.09.25 JDK11 移除Java EE模块，支持HTTP Client（LTS） Java12 2019.03.19 JDK12 Java13 2019.09.17 JDK13 Java14 2020.03.17 JDK14 Java15 2020.09.16 JDK15 Java16 2021.03.16 JDK16 Java17 2021.09.14 JDK17 密封类（Sealed Classes）、模式匹配等（LTS） Java18 2022.03.22 JDK18 Java19 2022.09.20 JDK19 Java20 2023.03.21 JDK20 Java21 2023.09.19 JDK21 虚拟线程（Loom）、分代ZGC（LTS） Java22 2024.03.19 JDK22 Java23 2024.09.17 JDK23 Java24 2025.03.18 JDK24 Java 24 是自 Java 21 后的第三个非长期支持版本 2.选择并安装JDK 在Oracle官网下载JDK，有很多版本可供选择，建议选择Oracle官方的长期支持版，如：JDK8、JDK11、JDK17、JDK21，这里选用JDK21作为示例，因为Spring官方推荐的安装环境需要JDK17或JDK21。 另外如果是下载JDK8及其以前的版本，还需要注意对应操作系统是32位或者64位，如下： 自2023年9月通过JEP 449提案，JDK21正式废弃Windows 32位x86平台的构建支持，但仍允许通过特定配置编译32位版本。本文以 Window11 64位系统为例，如下： 下载完成jdk-21_windows-x64_bin.exe或者jdk-21_windows-x64_bin.msi文件，双击运行进行安装，中间无需调整安装选项。安装完成后可以在命令行输入命令 java -version 查看系统中安装的JDK版本，如下： 二、配置环境变量（JAVA_HOME） 从JDK11开始，安装后默认无需手动配置 Path 变量即可使用基础命令，但 JAVA_HOME 仍需按需设置。对于开发场景，建议始终补充 JAVA_HOME 以规避兼容性问题。 1.JDK安装完成后，从Windows开始菜单打开 设置 ，进入 系统 中的 系统信息 ，点击 高级系统设置 ； 2.选择 高级 选项卡，点击 环境变量 ； 然后就会出现 环境变量 设置界面，如下： 3.在系统变量中设置3个变量： JAVA_HOME 、 PATH 、 CLASSPATH ，若已存在则点击 编辑 ，不存在则点击 新建 。 注意：使用JDK5以上的版本，不用设置CLASSPATH环境变量，也可以正常编译和运行Java程序。 变量设置的相关值如下： 变量名：JAVA_HOME 变量值：C:\Program Files\Java\jdk-21 // 要根据自己的实际安装路径配置 变量名：CLASSPATH 变量值：.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar; // 注意前面有个英文句号"." 变量名：Path 变量值：%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin; // JDK9开始取消了jre目录 注意：从Windows10开始，PATH变量是按不同路径分隔的条目，需要将 %JAVA_HOME%\bin 和 %JAVA_HOME%\jre\bin 逐条添加，如下： 三、测试JDK 配置好系统环境变量之后就可以使用JDK提供的命令编译、运行Java程序。这里运行一个简单的Java程序对安装好JDK后的开发环境进行测试。 1.编写HelloWorld.java文件 在系统主用户目录下新建HelloWorld.java文件（可以新建文件后使用记事本进行编辑），代码如下： public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World"); } } 2.编译HelloWorld.java文件 在命令行中进入HelloWorld.java文件所在的目录，使用javac命令编译java文件。 javac HelloWorld.java HelloWorld.java文件经过javac编译后会得到HelloWorld.class文件，如下： 3.运行HelloWorld 在命令行中进入HelloWorld.class文件所在的目录，使用java命令运行HelloWorld程序。 java HelloWorld // 注意不要带.class文件后缀名 运行成功就会在命令行中输出“Hello World”，如下： 四、选择流行的Java开发工具软件 Java程序的编写、编译、运行肯定不能只依赖记事本、命令行，子曰：“工欲善其事，必先利其器”，目前市场上有许多流行的IDE（IDE，Integrated Development Environment，集成开发环境）软件可以选择，下面介绍几款常用的IDE软件。 1.Eclipse Eclipse是一款开源、跨平台的IDE，最初由IBM于2001年贡献给开源社区，现由Eclipse基金会维护。它以插件化架构为核心，支持通过扩展插件适应多种编程语言和开发场景，是开发者构建复杂软件系统的首选工具。下载地址：https://www.eclipse.org/downloads/packages/ 下载地址中的Eclipse IDE for Java Developers和Eclipse IDE for Enterprise Java and Web Developers都可以开发Java程序，但两者有不同的定位和使用场景，如下： Eclipse IDE for Java Developers 定位：轻量级基础开发工具，适合 个人开发者或小型项目。 适用场景： 纯 Java SE 应用开发（如桌面程序、命令行工具）。 无需 Web 或企业级功能（如 JSP、数据库连接）。 开发工具链需求简单（仅需基础 Java IDE、Git、Maven/Gradle）。 Eclipse IDE for Enterprise Java and Web Developers 定位：企业级全栈开发工具，适合 Java EE、Web 应用及分布式系统。 适用场景： 开发企业级 Java EE 应用（如使用 JPA、JSF、EJB）。 Web 开发（HTML、JSP、Servlet、JavaScript）。 集成数据库工具、应用服务器（如 Tomcat、GlassFish）。 为了方便后续学习Java EE、Web应用的开发，可以选择Eclipse IDE for Enterprise Java and Web Developers下载安装，如下： 2.JetBrains的IDEA JetBrains的IntelliJ IDEA是一款以智能化和高效著称的IDE，专注于Java/Kotlin开发，同时通过插件支持多种编程语言和框架。下载地址：https://www.jetbrains.com/idea/download/ 下载地址中的IntelliJ IDEA Ultimate（旗舰版）和IntelliJ IDEA Community（社区版）都可以开发Java程序，但两者有不同的定位和使用场景，如下： IntelliJ IDEA Ultimate（旗舰版） 适用场景：企业级开发（如 Spring、微服务、数据库集成）、全栈开发（支持前端框架如 React/Vue）。 核心功能：深度支持 Java EE、Spring Boot、数据库工具、Docker/Kubernetes 集成等。 授权模式：付费订阅（提供 30 天试用，学生可免费申请授权）。 IntelliJ IDEA Community（社区版） 适用场景：学习、基础 Java/Kotlin 开发（如 Android 应用、小型项目）。 功能限制：缺少企业级框架支持（如 Spring）、数据库工具等高级功能。 授权模式：免费开源。 为了方便后续学习Java EE、Web应用的开发，可以选择IntelliJ IDEA Ultimate（旗舰版）下载安装，如下： 3.Visual Studio Code Visual Studio Code（简称VS Code）是一款由微软开发的免费开源、跨平台的现代化代码编辑器，自2015年发布以来已成为全球开发者首选的开发工具之一。下载地址：https://code.visualstudio.com/ 下载地址中的Visual Studio Code需要通过插件Extension Pack for Java才能支持Java程序开发，如下： 4.三种IDE的对比 特性 IntelliJ IDEA Eclipse VS Code 启动速度 中等 较慢 快 智能代码辅助 强（原生） 弱（依赖插件） 中（需插件） 企业级支持 完善（旗舰版） 需手动配置 需扩展配置 资源占用 较高 中等 低
一、Java语言的起源与设计理念 Java由詹姆斯·高斯林（James Gosling）领导的团队于1991年在Sun Microsystems启动开发，最初用于嵌入式家电设备（如电视机顶盒）的编程。1995年正式发布后，凭借“一次编写，到处运行”（WORA）的跨平台特性迅速成为互联网时代的核心语言。其设计初衷是简化C++的复杂性，摒弃多继承、指针等易错概念，同时强化面向对象特性和安全性。Oracle于2010年收购Sun后，持续推动Java的技术迭代与生态扩展。 二、Java的核心特性 跨平台性与JVM机制 Java程序通过编译器生成字节码（.class文件），由Java虚拟机（JVM）解释执行。JVM为不同操作系统提供统一运行环境，实现“代码一次编写，多平台运行”。例如，Windows编译的字节码可直接在Linux或MacOS的JVM上运行，极大降低开发成本。 纯粹的面向对象设计 Java强制使用类与对象结构，支持封装、继承、多态三大特性： 封装：通过私有变量（private）与公共方法（getter/setter）保护数据完整性。 继承：子类复用父类代码（如Animal类派生出Dog/Cat类）。 多态：接口（如Shape）允许多种实现（Circle/Rectangle），增强代码灵活性。 内存管理与安全性 垃圾回收（GC）：自动回收无用对象内存，避免内存泄漏。 安全机制：字节码验证、安全管理器（Security Manager）及沙箱模型限制恶意代码权限，防止系统攻击。 高性能与并发支持 即时编译（JIT）：将高频代码编译为机器码，提升执行效率。 多线程：内置Thread类与synchronized关键字简化并发编程，适用于服务器端高并发场景。 丰富的API与生态系统 提供超过4500个核心类库，涵盖网络（java.net）、IO流（java.io）、集合框架（java.util）等模块。Spring、Hibernate等开源框架进一步扩展了企业级开发能力。 三、Java的发展历程与版本迭代 1995-2000年：Java 1.0发布，引入Applet（网页小程序）；Java 2平台划分为J2SE（标准版）、J2EE（企业版）和J2ME（微型版）。 2004年：Java 5引入泛型、自动装箱/拆箱、注解（Annotation）等，提升代码简洁性。 2014年：Java 8推出Lambda表达式与Stream API，支持函数式编程，简化集合操作。 2021年：Java 17成为长期支持（LTS）版本，新增密封类（sealed class）、模式匹配等特性，强化代码可维护性。 四、Java的应用场景 企业级应用开发 占全球企业级市场60%以上份额，Spring框架与J2EE规范支撑银行系统（如支付清算）、ERP等复杂业务。 移动开发 Android平台以Java为核心开发语言，支付宝、微信等应用均依赖Java处理后台逻辑。 大数据与云计算 Hadoop、Spark等大数据框架基于Java构建；云原生技术（如Kubernetes）依赖Java的高可扩展性。 Web开发 通过Servlet、JSP和Spring Boot构建动态网站，典型案例包括LinkedIn、eBay等。 嵌入式与物联网（IoT） 早期Oak语言设计为智能家电赋能，如今在车载系统、智能家居中仍有应用。 五、未来趋势与挑战 云原生与微服务：Quarkus、Micronaut等轻量级框架优化Java在容器化环境中的启动速度与内存占用。 新语言特性：Project Loom引入虚拟线程（Virtual Threads），提升高并发性能；Project Valhalla探索值类型（Value Types）以优化内存使用。 竞争与替代：Kotlin（Android官方推荐语言）、Go等新兴语言对Java构成挑战，但其庞大的开发者社区与成熟生态仍具不可替代性。 结语 Java凭借跨平台、高性能、强生态等优势，历经30年发展仍稳居TIOBE指数前三。从嵌入式系统到云计算，其“一次编写，到处运行”的理念持续推动技术创新。未来，Java将继续通过版本迭代拥抱现代化编程范式，巩固其作为“编程界常青树”的地位。
如果您看到这篇文章,表示您的 blog 已经安装成功.