跳到内容

第 1 卷 / 第 3 章 / 第 1 课

3.1 表达式、赋值与求值顺序 ​

前置知识:第 2 章变量、类型与转换

预计时间:25 分钟

练习环境:JDK 17+ 与 Bash;首个示例写入临时目录

完成标志:能区分表达式与语句,并用括号写出清楚的求值意图

表达式、值与语句 ​

java
public class Checkout {
    public static void main(String[] args) {
        int apples = 5;
        double price = 3.5;
        double total = apples * price;
        System.out.println(total);
    }
}

先建立临时目录;运行到 mkdir out 后,把上面的代码保存为当前目录中的 Checkout.java,再编译运行:

bash
chapter3_workdir=$(mktemp -d)
readonly chapter3_workdir
cd "$chapter3_workdir"
mkdir out
javac -d out Checkout.java
java -cp out Checkout

输出是 17.5。

下面的表达式有类型,正常求值后会得到值。另一些表达式不产生值,例如调用返回 void 的 println(...);求值也可能因为异常而突然结束。先看有值的例子:

代码类型或作用
5int 字面量表达式
apples变量访问表达式,类型为 int
apples * price数值表达式,经提升后类型为 double
apples > 3关系表达式,类型为 boolean
total = apples * price赋值表达式,类型和值来自赋值后的左侧变量

double total = ...; 整体是局部变量声明语句,不是表达式。total = ...; 则是赋值表达式作为表达式语句使用,计算出的赋值结果被丢弃。Java 的“赋值本身也是表达式”解释了链式赋值为何可以编译:

java
int left;
int right;
left = right = 10;

链式写法不适合所有场景;类型不同或意图不清时分行更好。

操作数按从左到右求值 ​

Java 对操作数求值顺序有明确规定。先看:

java
public class EvaluationOrder {
    static int mark(String label, int value) {
        System.out.println(label);
        return value;
    }

    public static void main(String[] args) {
        int result = mark("left", 2) + mark("right", 3);
        System.out.println(result);
    }
}

输出顺序是 left、right,结果为 5。优先级决定表达式如何分组,不会把右操作数提前到左操作数之前求值。

复杂表达式中混入赋值、++ 或方法副作用仍然难读。语言规则能告诉你结果,却不能让糟糕结构变清楚。把副作用拆成独立语句。

优先级、结合性与括号 ​

java
int result = 10 + 3 * 2; // 16

乘法优先级高,所以分组等价于 10 + (3 * 2)。

java
int difference = 20 - 5 - 3; // 12

减法同级且左结合,因此等价于 (20 - 5) - 3。赋值通常右结合,这使 left = right = 10 等价于 left = (right = 10)。

括号应表达业务分组,而不是机械包住所有记号:

java
boolean eligible = isMember && (spent >= minimumSpend);
double finalPrice = eligible ? total * discountRate : total;

不要依赖一张不完整的优先级口诀处理生产代码。涉及三种以上操作符,或读者需要停顿才能理解时,用命名中间变量和括号拆开。

字符串连接也是 + ​

如果任一相关操作数触发字符串连接,+ 会把值转成文本并连接。它仍按左结合分组:

java
System.out.println("total=" + 1 + 2);   // total=12
System.out.println("total=" + (1 + 2)); // total=3

日志或循环中大量连接可能产生额外对象,后面会学习参数化日志和 StringBuilder。本章先把括号写对。

复合赋值不只是缩写 ​

java
int count = 10;
count += 3;

对简单变量,它看起来像 count = count + 3。但复合赋值还包含隐式转换,并且左侧只求值一次:

java
byte level = 127;
level += 1;
System.out.println(level); // -128

level = level + 1 不能直接编译,因为 level + 1 是 int。level += 1 会把结果隐式转换回 byte,所以也可能静默窄化。

对数组元素、字段或带副作用的左侧表达式,两种写法的求值次数也可能不同。不要把复合赋值简单替换成“读一次、算一次、写一次”以外的随意展开。

条件操作符 ?: ​

java
String result = score >= 60 ? "pass" : "retry";

先求条件,只求两个分支中的一个。它适合选择一个短值,不适合塞入多层业务流程:

java
String label = value == null
        ? "missing"
        : value.isBlank() ? "blank" : "present"; // 能编译,但阅读成本高

改成命名方法或 if 通常更清楚。两个分支的类型还会影响整个条件表达式的类型,涉及 null、装箱或不同数值类型时不要凭直觉猜,交给编译器并写测试。

专业速查 ​

问题答案
表达式一定产生值吗不一定;void 调用不产生值,求值也可能因异常突然结束
赋值是表达式吗是;变量声明整体不是表达式
操作数顺序Java 通常按语法规定从左到右求值
优先级决定什么语法分组;不等同于任意重排求值
复合赋值等同普通赋值吗有隐式转换和左侧单次求值差异
三元何时合适简短、无副作用的二选一值

离开这一页前 ​

确认自己能区分声明语句与赋值表达式、优先级与求值顺序,并能预测字符串连接的左结合结果。然后清理首个示例:

bash
cd
ls -ld -- "$chapter3_workdir"
rm -r -- "$chapter3_workdir"

→ 下一步:算术、除法与余数

规范入口:JLS §15.7:表达式求值顺序、§15.26:赋值操作符。

Built with VitePress | Software Systems Atlas