跳到内容

第 1 卷 / 第 3 章 / 第 3 课

3.3 比较、相等与短路逻辑 ​

前置知识:算术、除法与余数

预计时间:30 分钟

完成标志:能区分数值比较、引用身份、内容相等和短路求值

关系操作符产生 boolean ​

java
int apples = 25;

boolean enough = apples > 20;
boolean exactlyTen = apples == 10;
boolean nonzero = apples != 0;
boolean atLeastFive = apples >= 5;
boolean belowThirty = apples < 30;

<、<=、>、>= 适用于数值类型,部分操作会先进行数值提升。boolean 不能使用大小关系操作符。

=、== 和内容相等 ​

= 是赋值操作符,== 是相等操作符。不能简单说“编译器会保护你”:

java
boolean expected = false;
boolean actual = true;

if (expected = actual) {
    System.out.println("runs");
}

这段可以编译,因为赋值表达式 expected = actual 的类型和值都是 boolean。若本意是比较,结果就是缺陷。减少在条件中赋值,配合编译器警告、静态分析和代码评审。

对于基本类型,== 比较经过必要转换后的值:

java
System.out.println(1 == 1L); // true
System.out.println(Double.NaN == Double.NaN); // false
System.out.println(0.0 == -0.0); // true

对于引用类型,== 判断两个引用是否指向同一对象或都为 null,不比较对象业务内容:

java
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 安全的内容比较:

java
import java.util.Objects;

boolean same = Objects.equals(left, right);

对象是否以及如何实现 .equals() 由其类型契约决定,后续面向对象章节会深入。

逻辑操作符 ​

java
boolean member = true;
double spent = 150.0;

boolean discount = member && spent >= 100.0;
boolean needsReview = !member || spent > 1_000.0;
操作符结果规则右侧是否可能跳过
&&两侧都为 true左侧为 false 时跳过
||至少一侧为 true左侧为 true 时跳过
!反转布尔值只有一个操作数
&布尔两侧都求值后做与不跳过
|布尔两侧都求值后做或不跳过

& 和 | 也能执行整数位运算;本章只讨论布尔用法。

短路是语言求值规则 ​

java
int count = 5;
boolean result = count > 10 && ++count > 0;

System.out.println(result); // false
System.out.println(count);  // 5

左侧已经让 && 的结果确定为 false,语言规则规定右侧不求值。这不是编译器优化,也不是偶然行为。

判空保护利用了这个保证:

java
boolean nonempty = text != null && !text.isEmpty();

顺序不能随意交换,!text.isEmpty() && text != null 会先解引用 text。如果右侧包含必须执行的写入、计数或网络调用,不应依赖逻辑表达式隐藏流程;改用清楚的控制语句。

浮点 == 不是“一律禁止” ​

== 精确比较两个浮点值。它适用于你确实要判断相同 IEEE 754 值的场景,但不适合把近似计算结果当作精确十进制答案:

java
double actual = 0.1 + 0.2;
System.out.println(actual == 0.3); // false

容差必须来自问题尺度。一个同时考虑绝对误差和相对误差的简化示例:

java
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 契约。

德摩根定律帮助化简否定 ​

text
!(A && B)  等价于  !A || !B
!(A || B)  等价于  !A && !B

例如:

java
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) 可能编译成功,== 对引用比较的是身份,&& 的短路是语言保证。浮点容差若没有问题尺度作依据,就只是一个猜测。

下一页把这些边界放进同一组实验。

→ 下一步:表达式与操作符实验

规范入口:JLS §15.21:相等操作符、§15.23–15.24:条件与、条件或。

Built with VitePress | Software Systems Atlas