一、引言 Java是一种强类型的编程语言，Java中可以使用的数据类型分为基本数据类型（Primitive Types）和引用数据类型（Reference Types）两大类。这两类数据在内存管理、默认值、使用场景等方面存在显著差异。 在Java中，所有变量在使用前必须明确声明类型，声明类型后不可隐式转换变量的类型。这种严格性要求程序员在编写代码时明确数据类型，避免意外错误。Java编译器在编译阶段会进行类型匹配验证，可在代码运行前捕捉类型不匹配问题，显著减少运行时错误。不同基本类型之间的转换需显式强制转换。 Java正是通过编译时检查、显式类型声明和严格转换规则，构建了高可靠性的编程环境。这种设计不仅减少了运行时错误，还提升了代码安全性与可维护性，是Java成为企业级应用首选语言的核心原因之一。 二、基本数据类型 Java中定义了8种基本数据类型：byte、short、int、long、float、double、char、boolean。基本数据类型也被称为简单类型，它们可以分为4种： 整型：包括byte、short、int和long，主要用于表示有符号的整数。 浮点型：包括float和double，主要用于表示带小数位的浮点数。 字符型：char，主要用于表示字符集中的符号，比如字母、数字、标点符号等。 布尔型：boolean，主要用于表示true/false值，常用于逻辑判断。 Java虽然是面向对象的编程语言，但Java的基本数据类型并不是面向对象的，主要因为出于效率原因考虑，因为将基本数据类型设计成对象会降低性能，毕竟对象的结构更加复杂。 Java的基本数据类型具有明确的内存占用空间，也就使其具有明确的范围。这使得Java可以在不同的系统和设备上保持可移植性。比如，不管在哪种操作系统的特定平台上，int类型永远都是使用32个二进制位表示具体的整数。 1.整型（存储整数） 在Java中能够使用的整型类型有4种：byte、short、int和long。它们都能够存储一个有符号的（正号或负号）整数，不同的整型类型在内存中占用不同的二进制位数，使得它们能够表示的整数的范围不同，具体见下表： 类型 位数 取值范围 默认值 典型应用场景 byte 8 -128 ~ 127 0 文件处理、节省内存 short 16 -32768 ~ 32767 0 兼容旧代码、小范围整数 int 32 -2^31 ~ 2^31-1（约±21亿） 0 日常整数运算 long 64 -2^63 ~ 2^63-1 0L 时间戳、大数据量 byte类型是取值范围最小的整型，它使用8位（1字节）表示有符号的整数，只能在-128~127范围内的取整数值。byte特别适合处理来自网络和文件的数据流和其他基本类型不直接兼容的原始二进制数据。 byte类型变量的声明如下所示： byte b;// 将变量b的数据类型声明为byte byte c = 127;// 将变量c的数据类型声明为byte，并同时赋值127 short类型使用16位（2字节）表示有符号的整数，可以在-32768~32767范围内取整数值。相比其他整型类型，short类型的使用率较低。 short类型变量的声明如下所示： short s;// 将变量s的数据类型声明为short short t = 2025;// 将变量t的数据类型声明为short，并同时赋值2025 int类型是最常用的整型类型，它使用32位（4字节）表示有符号的整数，取值范围-2^31~2^31-1（约±21亿），基本可以涵盖日常整数运算的范围。在Java程序中，int类型常用于控制循环次数，或者做数组的索引。另外，由于byte类型和short类型会在表达式计算中自动被提升为int类型，所以当需要使用整型类型时，int类型通常是最佳选项。 int类型变量的声明如下所示： int i;// 将变量i的数据类型声明为int int j = 1745545182;// 将变量i的数据类型声明为int，并同时赋值1745545182 long类型是最大的整型类型，它使用64位（8字节）表示有符号的整数，取值范围-2^63~2^63-1。当需要处理的整数类型过大无法用int类型保存时，就可以使用long类型。long类型的中保存的数值如果超出int类型的范围，就需要在数值末尾增加大写“L”或小写“l”，由于小写“l”在部分字体显示时与数字1过于接近，所以通常建议使用大写“L”作为后缀。 long类型变量的声明如下所示： long l;// 将变量l的数据类型声明为long long m = 1745545182550L;// 将变量m的数据类型声明为long，并同时赋值1745545182550 2.浮点型（存储浮点数） 在Java中能够使用的浮点型类型有2种：float和double。它们用于存储在计算需要小数精度的表达式时使用的浮点数。Java实现了IEEE-754标准机的浮点类型和操作符。这两种不同的浮点型类型在内存中占用不同的二进制位数，使得它们能够表示的浮点数的精度范围不同，具体见下表： 类型 位数 取值范围 默认值 典型应用场景 float 32 1.4E-45~3.4028235E38（6-7位有效数） 0.0F 低精度浮点数运算 double 64 4.9E-324~1.7976931348623157E308（15位有效数） 0.0D 科学计算、默认的浮点型类型 float类型使用32位（4字节）存储单精度浮点数，通常在需要使用小数计算且精度要求不高的场合使用。类似long类型，float类型的数值在保存时需要使用大写“F”或小写“f”作为后缀。 float类型变量的声明如下所示： float f;// 将变量f的数据类型声明为float float g = 3.141593F;// 将变量g的数据类型声明为float，并同时赋值3.141593 注意使用float类型时，由于占用空间较小，使得计算效率较高，但如果操作的数值过大或者过小，就会损失浮点数的精度，导致计算结果偏差过大。 double类型使用64位（8字节）存储双精度浮点数，是Java中使用频率最高的浮点型类型，大部分Java中预定义的方法返回浮点数时，都使用的double类型。虽然double类型相比float类型占用空间更大，如果需要在多次运算中保持浮点数的精度，或者处理非常大的小数时，double类型更具优势。 double类型变量的声明如下所示： double d;// 将变量d的数据类型声明为double double e = 3.14159265358979;// 将变量e的数据类型声明为double，并同时赋值3.14159265358979 3.字符型（存储单个字符） 在Java中使用char类型保存单个字符。Java的设计初衷是允许程序员编写在世界范围内均可运行的程序，而Unicode为了解决传统的字符编码方案的局限，它为全世界每种语言中的每个字符设定了统一并且唯一的二进制编码，以满足跨语言、跨平台进行文本转换、处理的要求，所以Java对Unicode的支持就成为其语言设计的核心特性之一。Java的char类型是​16位无符号整数，取值范围为0~65535，可以直接存储单个字符、ASCII码或Unicode编码。 char类型变量的声明如下所示： char c;// 将变量c的数据类型声明为char char c1 = 'A';// 将变量c1的数据类型声明为char，并同时赋值直接字符'A' char c2 = 65; // 将变量c1的数据类型声明为char，并同时赋值直接字符'A'的ASCII码 char c3 = '\u0041';// 将变量c1的数据类型声明为char，并同时赋值直接字符'A'的Unicode编码 4.布尔型（存储逻辑值） 在Java中使用boolean类型保存逻辑值，它只能保存两个逻辑值true和false其中的一个，所有关系操作符和逻辑操作符都返回boolean类型的值。 boolean类型变量的声明如下所示： boolean b;// 将变量b的数据类型声明为boolean boolean b1 = true;// 将变量b1的数据类型声明为boolean，并同时赋值true boolean b2 = false;// 将变量b2的数据类型声明为boolean，并同时赋值false 5.基本数据类型的字面量 在Java中，​字面量是直接在源代码中表示的固定值，无需通过计算或转换即可使用。字面量主要用于初始化变量、参与表达式运算或定义常量，是程序中数据的基础表示形式。下面依次介绍几种基本数据类型的字面量。 (1).整型字面量 整型是最常用的数据类型，所有整数值都是整型字面量。除了现实世界常用的十进制整数值之外，Java还支持其他进制的整数值作为整型字面量。具体表示方式​如下： 进制 示例 说明 ​十进制​ 10、-5 日常以10位基数描述的整数 ​二进制 0b1010（表示十进制的10） 以0b或0B开头 ​八进制 012（表示十进制的10） 以0开头 ​十六进制 0xA（表示十进制的10） 以0x或0X开头 整型字面量在Java中默认用于创建int类型，当用整型字面量创建其他类型的整型类型时，需要注意： 将整型字面量赋值给byte类型或short类型时，由于这两种整型类型占用的空间比int类型少，如果整型字面量刚好位于byte类型或short类型的取值范围，就可以正常使用不会有问题。 将整型字面量赋值给long类型时，由于long类型占用的空间比int大，如果整型字面量刚好位于int类型的取值范围，可以省略整型字面量末尾的标识（大写“L”或小写“l”），如果整型字面量超出int类型的取值范围，就必须在整型字面量末尾使用标识（大写“L”或小写“l”）明确表示该整型字面量是long类型的。 (2).浮点型字面量 浮点型用于保存具有小数部分的十进制数值，在Java中可以使用标准记数法或者科学记数法表示浮点数。标准记数法是日常生活中最常见的记数法，由整数部分、小数部分和它们之间的小数点组成，如：3.14、0.333、12.27等。科学记数法也称指数记数法，是一种记录或标志数的科学表示法，在数学中，它把一个数表示成a与10的n次幂相乘的形式（1≤|a|<10，a为实数且不为分数形式，n为整数），常用于表示由于太大或太小而不方便用标准记数法表示的数。在Java中，a部分使用标准记数法表示，10的n次幂部分使用大写E（或小写e）后跟上一个十进制整数（表示10的幂），如314159E-5、5.67e11、-6.32E+23等。 浮点型字面量在Java中默认用于创建double类型，当用浮点型字面量创建float类型时，需要在浮点型字面量末尾添加大写F（或小写f）后缀。 虽然也可以在浮点型字面量末尾添加大写D（或小写d）后缀表示使用浮点型字面量创建double类型，但由于浮点型字面量默认就是double类型，所以这样做是多余的。 在Java中还有一种很少用的十六进制浮点型字面量，这种浮点型字面量使用与科学记数法类似的方式表示，不过a部分是一个十六进制数，其后使用大写P（或小写p），再接上一个二进制指数（表示2的幂），如0x12.2P2，0x12.2为十六进制数（转十进制数18.125），P后的2表示2的2次幂（4），所以0x12.2P2代表72.5（18.125乘以4）。 (3).布尔型字面量 布尔型用于保存逻辑值，逻辑值只有true和false两个，所以布尔型字面量只有true和false。在Java中，布尔型字面量只能赋值给声明为布尔型的变量。 在Java中，布尔型不能转换为其他基本数据类型，所以字面量true不能用1代替，字面量false也不能用0代替。 (4).字符型字面量 字符型用于保存单个字符，Java中采用Unicode字符集表示单个字符，所以字符型字面量大部分都可以转换成十六进制的非负整数，并且还可以使用部分操作符进行操作，只要操作结果还是一个十六进制的非负整数即可。在Java中，字符型字面量使用一对单引号包含的字符来表示，如'a'、'汉'、'한'等。对于一些不能直接输入的字符，还可以用转义字符（\）结合一个或多个指定字符组成转义序列进行表示，如'\n'、'\r'、'\t'等。Java还支持使用八进制或十六进制直接表示字符时，对于八进制表示法，可以在转义字符（\）后接三个数字的八进制数，如'\141'表示字符'a'；对于十六进制表示法，可以在转义字符（\）后接u，再接4位的十六进制数，如'\u0061'表示字符'a'，\u2c49'表示字符'汉'，'\ud55c'表示'한'。具体表示方式​如下： 转义序列 说明 描述 \ddd 八进制字符​ 表示一个八进制字符 \uxxxx​ ​十六进制字符​ 表示一个Unicode字符 \n​ 换行符​ 表示新的一行（换行） \r 回车符​ 表示回车（回到一行的开始） \t 制表符​ 表示一个制表符（通常用于对齐文本） \f 换页符​ 表示回车（回到一行的开始） \b 退格符​ 表示退格，将光标移到前一位置 \"​ 双引号​ 表示双引号字符（"）。在字符串中直接使用双引号时，需要用反斜杠进行转义 \' 单引号​ 表示单引号字符（'）。在字符串中直接使用单引号时，需要用反斜杠进行转义 \\ 反斜杠​ 表示反斜杠字符（\）。在字符串中直接使用反斜杠时，需要使用两个反斜杠（\\）来表示一个反斜杠(\) 注意，字符型字面量都使用一对单引号包含的字符来表示，千万不能将单引号写成双引号。在Java中，字符串字面量使用一对双引号包含的若干字符来表示，如："Java"、"编程语言"、"Hello World"等。字符型字面量可以替换字符串字面量中的单个字符，但需要注意的是Java中没有定义徐行的转义字符，所以字符串字面量的开头和结尾必须位于同一行中。另外，不同于其他语言中的字符串通常以字符数组实现，在Java中字符串是引用数据类型。 (5).字面量中的下划线 从Java 7开始，在字面量中引入了下划线（_），主要用于提升代码可读性。 分隔长数字：通过下划线将数字按逻辑分组，例如千分位，使数值更易读。 int decimal = 1_000_000; // 十进制千分位 long creditCard = 1234_5678_9012_3456L; // 十进制千分位 分隔其他进制：按字节或半字节分隔，增强二进制、八进制、十六进制数值的可读性。 int binary = 0b1010_1010; // 二进制 int octal = 0123_4567; // 八进制 int hex = 0xAB_CD_EF; // 十六进制 科学计数法与浮点数：分隔浮点数或科学计数法的整数/小数部分。 double pi = 3.141_592_653_589; // 在小数部分插入下划线 float earthRadius = 6_371.0F; // 在整数部分插入下划线 double avogadro = 6.022_140_857E23; // 科学计数法中插入下划线 在字面量中使用下划线要注意以下使用规则​： ​允许的位置​： 数字之间（整数、浮点数、二进制、十六进制等）。 可连续使用多个下划线（如5______2），但不推。 ​禁止的位置​ 数字开头或结尾（如_1000或1000_）。 小数点前后（如3_.14或3._14）。 进制前缀（如0x、0b）或类型后缀（如 L、F）附近（如 0x_52 或 123_L）。 科学计数法字母E前后之后（如 6.23E1_0）。 三、引用数据类型 在 Java 中，​引用数据类型是面向对象编程的核心，用于表示复杂数据结构。与基本数据类型直接存储数值不同，引用数据类型存储的是对象在堆内存中的地址引用，主要包括类、接口和数组三类。 引用数据类型变量本身存储在栈内存，保存对象的堆内存地址，对象实际数据存储在堆内存，通过引用间接访问。引用数据类型的默认值为null，使用未初始化的引用会抛出空指针异常NullPointerException。 Java中的引用数据类型与基本数据类型的具体区别如下： 特性 基本数据类型 引用数据类型 存储内容 实际数值 对象的内存地址 内存位置 栈内存 变量在栈，对象在堆 默认值 按具体类型区分 null 传递方式 值传递（复制数值） 引用传递（复制地址） 内存占用 固定大小 动态分配（取决于对象复杂度） 操作效率 高 较低（需间接访问堆数据） 垃圾回收 无 自动回收 四、数据类型转换 在Java中，数据类型转换是一个常见的操作，它允许程序员将某种类型的值赋给另一种类型的变量。Java支持两种数据类型类型转换的方式：自动类型转换和强制类型转换。 1.自动数据类型转换 自动数据类型转换也称为隐式转换，当数据类型转换时满足以下条件，那么Java就会将数据类型自动进行转换。 目标类型和源类型是兼容的 目标类型的占用空间大于源类型 当满足上述两个条件时，Java就会自动进行数据类型转换，这种转换被称为扩宽转换（Widening Conversion）。扩宽转换对所有的数值类型（整型和浮点型）都是相互兼容的，那么数值类型的数据是否能够进行扩宽转换，就只需要考虑数据类型的占用空间了。例如，int类型占用32位（4字节），long类型占用64位（8字节），那么将int类型的数据转换为long类型，long类型总是有足够的空间保存int类型的数据，所以Java就会自动进行数据类型转换。 对于基本数据类型，以下是常见的数据类型转换，箭头表示转换的方向： 整数类型：byte → short → int → long → float → double 浮点类型：float → double 字符类型：char → int（转换为Unicode码值） 注意，不存在数值类型到char或boolean类型的自动转换，不存在boolean类型到其他类型的自动转换。 扩宽转换还有一种形式，即混合操作提升，如果操作数中存在占用空间较大的数据类型，结果自动提升为该数据类型。例如，byte、short和char参与算术操作时自动提升为int。 下面的示例展示了Java中的自动数据类型转换。 public class AutoCasting { public static void main(String[] args) { // 1. 整数类型从小到大的自动转换 byte byteVal = 100; short shortVal = byteVal; // byte → short int intVal = shortVal; // short → int long longVal = intVal; // int → long float floatVal = longVal; // long → float（可能损失精度） double doubleVal = floatVal; // float → double System.out.println("byte → short → int → long → float → double:"); System.out.println("byteVal: " + byteVal + " → shortVal: " + shortVal); System.out.println("intVal: " + intVal + " → longVal: " + longVal); System.out.println("floatVal: " + floatVal + " → doubleVal: " + doubleVal); // 2. 字符类型自动转换为整数（Unicode码） char charVal = '汉'; int charToInt = charVal; // char → int（输出27721） System.out.println("\nchar '汉' → int: " + charToInt); // 3. 运算时自动提升到操作数中最大的类型 byte a = 10; short b = 20; int c = 30; long d = 40L; float e = 5.5f; double f = 6.6; // 示例1：byte + short → int int result1 = a + b; System.out.println("\nbyte + short → int: " + result1); // 输出30 // 示例2：int + long → long long result2 = c + d; System.out.println("int + long → long: " + result2); // 输出70 // 示例3：long + float → float float result3 = d + e; System.out.println("long + float → float: " + result3); // 输出45.5 // 示例4：float + double → double double result4 = e + f; System.out.println("float + double → double: " + result4); // 输出12.1 } } 程序执行后的结果如下： byte → short → int → long → float → double: byteVal: 100 → shortVal: 100 intVal: 100 → longVal: 100 floatVal: 100.0 → doubleVal: 100.0 char '汉' → int: 27721 byte + short → int: 30 int + long → long: 70 long + float → float: 45.5 float + double → double: 12.1 2.强制数据类型转换 强制数据类型转换也称为显式转换，因为这种数据类型转换需要使用类型转换操作符明确告诉Java需要进行该数据类型转换。强制数据类型转换的一般形式如下： (target-Data-Type)value 其中，括号中的target-Data-Type指定需要将括号后的value转换成哪种数据类型，value可以是保存了具体数据的变量，也可以是某个具体的值。 在整型类型中，如果需要将内存空间较大的数据类型转换为内容空间较小的数据类型，就需要使用强制数据类型转换，这种转换也被称为缩窄转换（Narrowing Conversion）。例如，int类型占用32位（4字节），long类型占用64位（8字节），那么将long类型的数据转换为int类型，int类型没有足够的空间保存long类型的数据，所以就必须进行缩窄转换。缩窄转换最常见的 后果就是数据溢出，因为原整型数据中的值过大，超出目标整型类型的取值范围，这时原整型数据将按照目标整型类型的取值范围为模减少。 另外，如果将浮点型类型强制转换为整型类型，Java总会对浮点型类型中的浮点数进行截尾，即舍弃浮点数的小数部分。 下面的示例展示了Java中的自动数据类型转换。 public class PrimitiveCasting { public static void main(String[] args) { // 1. 大范围整数转小范围（溢出） long population = 3_000_000_000L; int intPopulation = (int) population; // 结果为-1294967296（溢出） System.out.println("long转int（溢出）: " + intPopulation); // 2. 浮点数转整数（截尾） double price = 99.99; int intPrice = (int) price; // 结果为99 System.out.println("double转int（截尾）: " + intPrice); // 3. 字符与Unicode转换 int unicode = 27721; char restored = (char) unicode; // 结果为'汉' System.out.println("int: " + unicode + ", int转char: " + restored); } } 程序执行后的结果如下： long转int（溢出）: -1294967296 double转int（截尾）: 99 int: 27721, int转char: 汉 五、结语 Java基本数据类型及其转换机制是构建高效、健壮程序的基石。Java的8种基本数据类型（byte、short、int、long、float、double、char、boolean）以简洁的存储结构和明确的取值范围，为数值运算、逻辑判断和字符处理提供了高效支持。自动数据类型转换（如int→long）通过隐式操作提升避免了精度丢失，而强制数据类型转换（如double→int）则需谨慎处理截尾和溢出风险。从char的Unicode支持到long的大整数处理，Java的数据类型设计始终兼顾国际化与工程实践需求。而类型转换的严格规则（如boolean不可与其他类型互转）则体现了语言对逻辑严谨性的追求。理解基本数据类型及其转换机制，不仅是避免NullPointerException或精度丢失等常见问题的关键，更是优化程序性能、提升代码可维护性的核心技能。
一、引言 操作符在编程中的核心地位 操作符是程序中对数据进行计算、判断和转换的核心工具。操作符定义数据被处理方式，将原始输入的数据（操作数）转化为有意义的中间结果，为程序逻辑提供计算能力。在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将继续通过版本迭代拥抱现代化编程范式，巩固其作为“编程界常青树”的地位。