赋值运算符是java中使用频率最高的一类运算符,几乎每一行代码都离不开它。很多初学者觉得赋值无非就是一个等号,没什么好讲的,但实际上java的赋值运算符包含基本赋值和扩展赋值两大类,其中扩展赋值运算符还隐藏着自动类型转换的机制,面试中也经常拿short s = 1; s = s + 1和s += 1的区别来考察候选人对类型提升的理解。这篇文章就来把java赋值运算符的知识点彻底梳理清楚。

一、基本赋值运算符的原理
java中最基本的赋值运算符就是等号=,它的作用是把右侧的值赋给左侧的变量。这里有一个非常关键的前提:左侧必须是一个变量,而不能是常量或表达式。比如int a = 10;是合法的,而10 = a;就会直接编译报错,因为字面量10不是一个可以被赋值的存储单元。
赋值运算符在java中并不是一个“动作”,它其实是一个表达式,有自己的返回值。赋值表达式的值就是赋值完成后的值。这意味着a = 10这个式子本身的值就是10,所以下面的写法在java中是完全合法的:
int a; int b; int c; // 连续赋值,从右往左执行 a = b = c = 100; System.out.println(a + "," + b + "," + c); // 输出 100,100,100
连续赋值的执行顺序是从右到左:先执行c = 100,这个表达式的值是100,再把这个值赋给b,最后赋给a。这个特性在实际开发中不太建议滥用,因为可读性差,但在阅读别人代码或者面试笔试题中经常会遇到。
还有一个容易混淆的点:=是赋值,==才是判断相等。在if条件里写if (a = 5)这种代码,java会直接编译报错,因为java不像C语言那样把非零值当真,if条件必须是boolean类型,这在一定程度上避免了C语言中经典的赋值bug。
二、扩展赋值运算符有哪些
java在基本赋值运算符的基础上,提供了一组扩展赋值运算符,它们是算术运算符或位运算符与等号的组合形式,作用是让“先运算再赋值”的写法更简洁。完整列表如下:
| 运算符 | 示例 | 等价形式(含义上) |
|---|---|---|
| += | a += b | a = a + b |
| -= | a -= b | a = a - b |
| *= | a *= b | a = a * b |
| /= | a /= b | a = a / b |
| %= | a %= b | a = a % b |
| &= | a &= b | a = a & b |
| |= | a |= b | a = a | b |
| ^= | a ^= b | a = a ^ b |
| <<= | a <<= 2 | a = a << 2 |
| >>= | a >>= 2 | a = a >> 2 |
| >>>= | a >>>= 2 | a = a >>> 2 |
这些运算符的常规用法很简单,比如累加操作sum += i就是最常见的写法。下面是一个综合示例:
int a = 10; a += 5; // a = 15 a -= 3; // a = 12 a *= 2; // a = 24 a /= 5; // a = 4,整数除法舍弃小数 a %= 3; // a = 1 a <<= 2; // a = 4,二进制左移两位 System.out.println(a);
三、扩展赋值运算符与普通写法的本质区别
这是java赋值运算符中最经典的知识点。+=并不完全等价于先加后赋值的展开形式,它们的区别在于隐含的类型转换机制。看下面这段代码:
short s = 1; // 写法一:编译报错 s = s + 1; // 写法二:编译通过 s += 1;
写法一报错的原因是自动类型提升:s是short类型,s + 1中的常量1默认是int类型,short参与算术运算时会先提升为int,运算结果就是int类型。把int赋值给short属于大范围转小范围,必须强制转换,所以编译器报错。写法二能通过,是因为java规范规定复合赋值运算符内部包含了一次隐式的强制类型转换,s += 1实际上等价于s = (short)(s + 1),编译器替你做了强转。
这个隐式强转虽然方便,但也带来了潜在的精度丢失风险。比如byte类型的变量执行b += 200;,运算结果230超出了byte的范围-128到127,强转后会发生溢出,得到的结果是-26。这种问题不会报任何错误,只会默默产生错误数据,所以在处理小范围整数类型时要格外小心。
四、实际开发中的注意事项
第一点要注意除法和取模的边界情况。使用/=时如果右侧是整数类型且值为0,会抛出ArithmeticException异常;如果操作数是浮点数,除以0会得到Infinity而不会报错。用%=对负数取模时,结果的符号与被除数一致,这一点很多人容易记混。
int x = 10; int y = 0; // x /= y; 会抛出 java.lang.ArithmeticException: / by zero double d = 10.0; d /= 0; // 不报错,结果为 Infinity System.out.println(-7 % 3); // 输出 -1,符号跟被除数一致
第二点是复合赋值运算符右侧是完整表达式时,左侧变量只会被求值一次。比如arr[i++] += 10;这种写法,i的自增只会发生一次,如果展开成arr[i++] = arr[i++] + 10;则i会自增两次,行为完全不同。这也说明复合赋值在语义上更接近原子化的操作,代码意图更清晰。
第三点,浮点数使用+=做累加时要警惕精度问题。比如用double循环累加0.1一百次,结果不是精确的10.0,而是10.000000000000002。对精度敏感的场景,比如金额计算,应该使用BigDecimal,并且构造BigDecimal时传入字符串而不是double,否则精度问题依旧存在。
double total = 0;
for (int i = 0; i < 100; i++) {
total += 0.1;
}
System.out.println(total); // 10.000000000000002总的来说,java的赋值运算符分为基本赋值=和一系列复合赋值运算符,核心要掌握三点:赋值表达式本身有返回值,支持连续赋值;复合赋值运算符自带隐式强转,这是它与普通展开式的本质区别;实际开发中要注意小类型溢出、整数除零和浮点精度这些隐患。把这些细节搞清楚,写出的代码才会更可靠。