"白歌在代码审查时指着小崔的代码:'
if (employeeName == "大翔")—— 这个判断永远为false,因为你在比较引用地址,不是字符串内容。'小崔恍然大悟:'难怪飞翔科技的员工登录模块一直匹配不成功!'黄俪补充道:'还有浮点数比较也不能直接用==,0.1 + 0.2 == 0.3结果是 false,因为二进制无法精确表示十进制小数。'"
关系运算符
概述
关系运算符用于比较两个值的大小或相等关系,结果为 boolean 类型(true / false)。Java SE 8 提供6种关系运算符:==(等于)、!=(不等于)、>(大于)、<(小于)、>=(大于等于)、<=(小于等于),以及类型判断运算符 instanceof。
操作符行为表
| 操作符 | 含义 | 示例(int a=10, b=3) | 结果 |
|---|---|---|---|
== | 等于 | a == b | false |
!= | 不等于 | a != b | true |
> | 大于 | a > b | true |
< | 小于 | a < b | false |
>= | 大于等于 | a >= 10 | true |
<= | 小于等于 | a <= 3 | false |
instanceof | 类型判断 | "hello" instanceof String | true |
深度原理分析
一、== 与 .equals() 的本质区别
这是Java初学者最容易混淆的核心概念。理解这一点需要区分值比较和引用比较。
| 比较目标 | 使用方式 | 比较内容 |
|---|---|---|
| 基本数据类型(int, float, char...) | == | 直接比较值 |
| 引用类型(String, Object...) | == | 比较引用地址(是否指向同一对象) |
| 引用类型的内容比较 | .equals() | 比较逻辑内容(由类的equals方法定义) |
String s1 = new String("飞翔");
String s2 = new String("飞翔");
String s3 = "飞翔";
String s4 = "飞翔";
System.out.println(s1 == s2); // false —— 两个不同的堆对象,地址不同
System.out.println(s1.equals(s2)); // true —— String重写了equals,比较内容
System.out.println(s3 == s4); // true —— 字符串常量池,指向同一对象
二、关系运算符的浮点数陷阱
由于浮点数在二进制中无法精确表示很多十进制小数,直接使用 == 比较 float 或 double 几乎总是错误的:
double a = 0.1 + 0.2;
double b = 0.3;
System.out.println(a == b); // false!0.1+0.2 实际上是 0.30000000000000004
System.out.println(a); // 0.30000000000000004
正确做法:使用容差比较(epsilon comparison):
double epsilon = 1e-10; // 容差
boolean isEqual = Math.abs(a - b) < epsilon; // true
完整示例一:飞翔科技员工薪资等级评定
场景
白歌负责飞翔科技内部系统的薪资等级评定,需要用关系运算符判断员工薪资所处的级别区间。
/**
* 飞翔科技员工薪资等级评定
* 演示关系运算符
*/
public class SalaryGradeEvaluation {
public static void main(String[] args) {
System.out.println("===== 飞翔科技内部系统 feixiang.net =====\n");
String inputUsername = "大翔";
String correctUsername = "大翔";
boolean isAccountLocked = false;
// --- == 与 equals 的区别演示 ---
System.out.println("--- 字符串比较 ---");
String dept1 = new String("技术部");
String dept2 = "技术部";
System.out.println("new String(\"技术部\") == \"技术部\": " + (dept1 == dept2));
System.out.println("new String(\"技术部\").equals(\"技术部\"): " + dept1.equals(dept2));
// 常量池复用
String s1 = "飞翔";
String s2 = "飞翔";
System.out.println("\"飞翔\" == \"飞翔\": " + (s1 == s2) + "(常量池复用,同一对象)");
// --- 关系运算符:员工薪资等级判断 ---
System.out.println("\n--- 薪资等级评定 ---");
float salary = 8888.88f;
System.out.println("当前月薪: ¥" + salary);
if (salary >= 20000) {
System.out.println("等级: P7(高级)");
} else if (salary >= 10000) {
System.out.println("等级: P6(中级)");
} else if (salary >= 5000) {
System.out.println("等级: P5(初级)"); // 8888.88 走这个分支
} else {
System.out.println("等级: P4(助理)");
}
// --- 浮点数比较陷阱 ---
System.out.println("\n--- 浮点数比较陷阱 ---");
double a = 0.1 + 0.2;
double b = 0.3;
System.out.println("0.1 + 0.2 = " + a);
System.out.println("0.1 + 0.2 == 0.3 ? " + (a == b));
double epsilon = 1e-10;
System.out.println("Math.abs(a - b) < epsilon ? "
+ (Math.abs(a - b) < epsilon) + "(容差比较,正确)");
// --- instanceof 类型判断 ---
System.out.println("\n--- instanceof 类型判断 ---");
Object obj = "飞翔科技";
System.out.println("obj instanceof String: " + (obj instanceof String));
System.out.println("obj instanceof Object: " + (obj instanceof Object));
System.out.println("obj instanceof Integer: " + (obj instanceof Integer));
}
}
运行输出:
===== 飞翔科技内部系统 feixiang.net =====
--- 字符串比较 ---
new String("技术部") == "技术部": false
new String("技术部").equals("技术部"): true
"飞翔" == "飞翔": true(常量池复用,同一对象)
--- 薪资等级评定 ---
当前月薪: ¥8888.88
等级: P5(初级)
--- 浮点数比较陷阱 ---
0.1 + 0.2 = 0.30000000000000004
0.1 + 0.2 == 0.3 ? false
Math.abs(a - b) < epsilon ? true(容差比较,正确)
--- instanceof 类型判断 ---
obj instanceof String: true
obj instanceof Object: true
obj instanceof Integer: false
完整示例二:飞翔商城价格区间筛选
场景
黄俪在飞翔商城开发商品价格筛选功能,演示关系运算符的组合使用和浮点比较的正确姿势。
/**
* 飞翔商城价格区间筛选
* 演示关系运算符组合
*/
public class PriceRangeFilter {
public static void main(String[] args) {
System.out.println("===== 飞翔商城 feixiang.net 价格筛选 =====\n");
float[] prices = { 9.99f, 49.50f, 88.88f, 168.00f, 299.99f, 520.00f };
float minPrice = 50.0f;
float maxPrice = 300.0f;
System.out.println("筛选条件: ¥" + minPrice + " ~ ¥" + maxPrice);
System.out.println("符合条件的商品:");
int count = 0;
for (float price : prices) {
// 组合关系运算符:区间筛选
if (price >= minPrice && price <= maxPrice) {
System.out.println(" ¥" + price);
count++;
}
}
System.out.println("共 " + count + " 件商品");
// --- 浮点数区间边界比较 ---
System.out.println("\n--- 浮点边界测试 ---");
float discountPrice = 49.99f + 0.01f; // 期望 50.00
System.out.println("49.99 + 0.01 = " + discountPrice);
System.out.println("discountPrice == 50.0f ? " + (discountPrice == 50.0f));
System.out.println("discountPrice >= 50.0f ? " + (discountPrice >= 50.0f));
// 使用 Float.compare 进行精确比较
System.out.println("Float.compare(discountPrice, 50.0f) == 0 ? "
+ (Float.compare(discountPrice, 50.0f) == 0));
}
}
运行输出:
===== 飞翔商城 feixiang.net 价格筛选 =====
筛选条件: ¥50.0 ~ ¥300.0
符合条件的商品:
¥88.88
¥168.0
¥299.99
共 3 件商品
--- 浮点边界测试 ---
49.99 + 0.01 = 50.0
discountPrice == 50.0f ? true
discountPrice >= 50.0f ? true
Float.compare(discountPrice, 50.0f) == 0 ? true
易错场景
反例一:== 比较字符串内容
// ❌ 错误:用 == 比较 String 内容
String dept = new String("技术部");
if (dept == "技术部") { // false!比较的是引用地址
System.out.println("匹配");
}
纠正:
// ✅ 正确:用 .equals() 比较字符串内容
String dept = new String("技术部");
if ("技术部".equals(dept)) { // 推荐:常量在前,可防NPE
System.out.println("匹配");
}
最佳实践:
"常量".equals(变量)把常量放前面,即使变量为null也不会抛NPE,而变量.equals("常量")在变量为null时会崩溃。
反例二:浮点数直接用 == 比较
// ❌ 错误:浮点数直接 == 比较
float price = 188.88f;
float tax = price * 0.13f; // 24.5544
float expected = 24.5544f;
System.out.println(tax == expected); // 可能为 false!浮点精度问题
纠正:
// ✅ 正确:使用容差比较
float price = 188.88f;
float tax = price * 0.13f;
float expected = 24.5544f;
float epsilon = 0.0001f;
boolean isClose = Math.abs(tax - expected) < epsilon;
System.out.println(isClose); // true
面试考点
Q1:== 和 .equals() 有什么区别?String 比较应该用哪个?
==对于基本类型比较值,对于引用类型比较内存地址(是否同一个对象)。.equals()是Object类的方法,默认实现也是比较地址(等同于==),但String、Integer等类重写了equals(),比较的是逻辑内容。String比较必须用
.equals(),推荐写法"常量".equals(变量)以防御NPE。
Q2:如何安全地比较两个浮点数是否相等?
不能直接用
==比较float或double,因为二进制浮点数无法精确表示许多十进制小数。应使用容差比较:Math.abs(a - b) < epsilon,其中epsilon是一个极小的正数(如1e-10)。对于float精度要求不高的场景可用Float.compare(a, b) == 0。
Q3:instanceof 关键字的作用是什么?有什么使用注意事项?
instanceof用于在运行时判断对象是否是指定类型(或其子类型)的实例,返回boolean。常用于向下转型前的安全检查。注意:(1)null instanceof X始终返回false;(2)如果编译期就能确定类型不兼容(如"hello" instanceof Integer),编译器会直接报错;(3)JDK 16 引入的模式匹配instanceof(如obj instanceof String s)在 JDK 8 中不可用。