3.3 比较、相等与短路逻辑
前置知识:算术、除法与余数
预计时间:30 分钟
完成标志:能区分数值比较、引用身份、内容相等和短路求值
关系操作符产生 boolean
int apples = 25;
boolean enough = apples > 20;
boolean exactlyTen = apples == 10;
boolean nonzero = apples != 0;
boolean atLeastFive = apples >= 5;
boolean belowThirty = apples < 30;<、<=、>、>= 适用于数值类型,部分操作会先进行数值提升。boolean 不能使用大小关系操作符。
=、== 和内容相等
= 是赋值操作符,== 是相等操作符。不能简单说“编译器会保护你”:
boolean expected = false;
boolean actual = true;
if (expected = actual) {
System.out.println("runs");
}这段可以编译,因为赋值表达式 expected = actual 的类型和值都是 boolean。若本意是比较,结果就是缺陷。减少在条件中赋值,配合编译器警告、静态分析和代码评审。
对于基本类型,== 比较经过必要转换后的值:
System.out.println(1 == 1L); // true
System.out.println(Double.NaN == Double.NaN); // false
System.out.println(0.0 == -0.0); // true对于引用类型,== 判断两个引用是否指向同一对象或都为 null,不比较对象业务内容:
String left = new String("apple");
String right = new String("apple");
System.out.println(left == right); // false
System.out.println(left.equals(right)); // true相同内容的字符串字面量会驻留,因而 == 可以返回 true;运行时构造的字符串不保证共享对象。比较内容仍要用内容契约。需要 null 安全的内容比较:
import java.util.Objects;
boolean same = Objects.equals(left, right);对象是否以及如何实现 .equals() 由其类型契约决定,后续面向对象章节会深入。
逻辑操作符
boolean member = true;
double spent = 150.0;
boolean discount = member && spent >= 100.0;
boolean needsReview = !member || spent > 1_000.0;| 操作符 | 结果规则 | 右侧是否可能跳过 |
|---|---|---|
&& | 两侧都为 true | 左侧为 false 时跳过 |
|| | 至少一侧为 true | 左侧为 true 时跳过 |
! | 反转布尔值 | 只有一个操作数 |
& | 布尔两侧都求值后做与 | 不跳过 |
| | 布尔两侧都求值后做或 | 不跳过 |
& 和 | 也能执行整数位运算;本章只讨论布尔用法。
短路是语言求值规则
int count = 5;
boolean result = count > 10 && ++count > 0;
System.out.println(result); // false
System.out.println(count); // 5左侧已经让 && 的结果确定为 false,语言规则规定右侧不求值。这不是编译器优化,也不是偶然行为。
判空保护利用了这个保证:
boolean nonempty = text != null && !text.isEmpty();顺序不能随意交换,!text.isEmpty() && text != null 会先解引用 text。如果右侧包含必须执行的写入、计数或网络调用,不应依赖逻辑表达式隐藏流程;改用清楚的控制语句。
浮点 == 不是“一律禁止”
== 精确比较两个浮点值。它适用于你确实要判断相同 IEEE 754 值的场景,但不适合把近似计算结果当作精确十进制答案:
double actual = 0.1 + 0.2;
System.out.println(actual == 0.3); // false容差必须来自问题尺度。一个同时考虑绝对误差和相对误差的简化示例:
static boolean nearlyEqual(double left, double right,
double absoluteTolerance,
double relativeTolerance) {
if (!Double.isFinite(absoluteTolerance) || absoluteTolerance < 0.0
|| !Double.isFinite(relativeTolerance) || relativeTolerance < 0.0
|| relativeTolerance >= 1.0) {
throw new IllegalArgumentException("invalid tolerance");
}
if (Double.isNaN(left) || Double.isNaN(right)) {
return false;
}
if (left == right) {
return true;
}
if (!Double.isFinite(left) || !Double.isFinite(right)) {
return false;
}
double difference = Math.abs(left - right);
double scale = Math.max(Math.abs(left), Math.abs(right));
return difference <= Math.max(absoluteTolerance,
relativeTolerance * scale);
}这个简化版本要求绝对容差有限且非负、相对容差在 [0, 1) 内;不符合条件就抛异常。它仍不是所有领域的万能比较器。测试框架通常提供带 tolerance 的断言;金额等精确十进制规则应采用 BigDecimal 并明确 scale 与舍入。
Double.compare 和 Double.equals 还为排序、集合等场景定义了与 == 不同的 NaN 和正负零处理,选择前先看 API 契约。
德摩根定律帮助化简否定
!(A && B) 等价于 !A || !B
!(A || B) 等价于 !A && !B例如:
boolean outside = !(age >= 18 && age <= 65);
boolean sameMeaning = age < 18 || age > 65;后者通常更直接。重写后用边界测试验证 17、18、65、66,不要只凭公式外观。
专业速查
| 需求 | 做法 |
|---|---|
| 基本类型值相等 | ==,注意数值提升和浮点特殊值 |
| 对象身份 | == |
| 字符串内容 | left.equals(right) 或 Objects.equals |
| null 安全访问 | value != null && ... |
| 两侧必须执行 | 用清楚语句;布尔 &/| 虽不短路但不宜隐藏副作用 |
| 近似浮点比较 | 由领域定义绝对/相对容差 |
| 精确十进制相等 | 使用合适十进制类型与明确契约 |
离开这一页前
记住 if (expected = actual) 可能编译成功,== 对引用比较的是身份,&& 的短路是语言保证。浮点容差若没有问题尺度作依据,就只是一个猜测。
下一页把这些边界放进同一组实验。