乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

"大翔在飞翔科技代码评审会上敲着白板:'优先级决定分组,但求值顺序是从左到右的铁律。这两者经常被混淆。'小崔恍然大悟:'所以 a + b * c 中,虽然 * 优先级高意味着 b * c 先结合,但操作数 a、b、c 的求值顺序仍然是从左到右!'白歌补充道:'还有类型提升——byte + byte 结果是 int,这可不是 bug,是 Java 语言规范的设计。'"

表达式

概述

表达式由操作数和运算符组成,用于计算一个值。在Java SE 8中,理解表达式的核心在于三个维度:求值顺序(操作数从左到右求值)、类型提升(窄类型自动扩展为宽类型)、以及表达式语句(有副作用的表达式可作为语句执行)。本章聚焦前两个关键机制,它们直接决定了表达式的结果是否正确。


深度原理分析

一、表达式求值顺序 —— 优先级 ≠ 求值顺序

关键区分:运算符优先级决定操作数的分组(哪个操作数与哪个操作符结合),但不决定操作数的求值顺序。Java严格规定了操作数是从左到右求值的。

int a = 1;
int b = 2;
int result = a + b * a++;
// 优先级:b * a++ 先结合(因为 * 优先级高于 +)
// 求值顺序(从左到右):
//   步骤1: 求值 a → 1
//   步骤2: 求值 b → 2
//   步骤3: 求值 a++ → 返回 1,然后 a 变成 2
//   步骤4: 计算 b * (a++ 的返回值) = 2 * 1 = 2
//   步骤5: 计算 a(的返回值) + 步骤4结果 = 1 + 2 = 3
System.out.println(result);  // 3
System.out.println(a);       // 2

二、类型提升(Type Promotion)规则

当二元运算符的两个操作数类型不同时,Java会自动将"窄"类型提升为"宽"类型,以保证精度不丢失。

提升层次(从窄到宽):

byte → short → int → long → float → double
                ↑
               char

三条核心规则:

  1. 如果任一操作数是 double,另一个提升为 double
  2. 否则,如果任一操作数是 float,另一个提升为 float
  3. 否则,如果任一操作数是 long,另一个提升为 long
  4. 否则,两个操作数都提升为 int(即使都是 byte 或 short)
byte b1 = 10;
byte b2 = 20;
// byte b3 = b1 + b2;     // ❌ 编译错误!b1+b2 被提升为 int
int b3 = b1 + b2;          // ✅ 必须用 int 接收

int i = 10;
float f = 3.14f;
float result = i + f;      // i 被提升为 float,结果为 float

double d = 5.0;
double result2 = i + d;    // i 被提升为 double,结果为 double

// 特别注意:char 和 short 都会先提升为 int
char c = 'A';
short s = 100;
int result3 = c + s;       // char→int, short→int,结果为 int

注意:long(64位整数)到 float(32位浮点)的转换虽然不会报错,但可能丢失精度——因为 float 只有23位有效精度位(mantissa),而 long 可以表示64位整数。


完整示例一:飞翔科技综合表达式求值

场景

白歌出了一道题给技术部新人——追踪复杂表达式的完整求值过程,理解求值顺序与类型提升。

/**
 * 飞翔科技 —— 表达式求值全过程追踪
 */
public class ExpressionEvaluation {
    public static void main(String[] args) {
        System.out.println("===== 飞翔科技 表达式求值实验 feixiang.net =====\n");

        // --- 实验1:求值顺序 vs 优先级 ---
        System.out.println("--- 实验1: 求值顺序 ---");
        int x = 1, y = 2, z = 3;
        int r1 = x + y * z++;
        System.out.println("计算前: x=1, y=2, z=3");
        System.out.println("x + y * z++ → " + r1);
        System.out.println("计算后: x=1, y=2, z=" + z + "(z已被z++修改)");
        System.out.println("过程: 先求值x→1, y→2, z++→返回3(然后z变4), 再算y*z=6, 最后1+6=7\n");

        // --- 实验2:类型提升 ---
        System.out.println("--- 实验2: 类型提升 ---");
        byte b1 = 100;
        byte b2 = 50;

        // byte + byte → int(自动提升)
        int sum = b1 + b2;  // 必须用 int 接收!
        System.out.println("byte(100) + byte(50) = " + sum + "(类型提升为int)");

        int intVal = 100;
        long longVal = 200L;
        float floatVal = 3.14f;
        double doubleVal = 2.718;

        // 混合类型运算:向最高类型统一
        System.out.println("int + long → long: " + (intVal + longVal));
        System.out.println("long + float → float: " + (longVal + floatVal));
        System.out.println("float + double → double: " + (floatVal + doubleVal));

        // long → float 精度丢失警告
        long bigLong = 9_223_372_036_854_775_807L;  // Long.MAX_VALUE
        float bigFloat = bigLong;  // 隐式转换,精度丢失
        System.out.println("\n⚠️  long→float 精度丢失:");
        System.out.println("  long原值:  " + bigLong);
        System.out.println("  float转换: " + bigFloat + "(丢失了有效位数!)");

        // --- 实验3:求值顺序中的副作用 ---
        System.out.println("\n--- 实验3: 求值顺序与副作用 ---");
        int[] arr = {10, 20, 30};
        int idx = 0;
        // 求值顺序保证:左边操作数先求值
        int r2 = arr[idx] + arr[++idx];
        System.out.println("arr[0] + arr[++idx] → arr[0] + arr[1] = " + arr[0] + " + " + arr[1] + " = " + r2);
        System.out.println("最终 idx = " + idx);
    }
}

运行输出:

===== 飞翔科技 表达式求值实验 feixiang.net =====

--- 实验1: 求值顺序 ---
计算前: x=1, y=2, z=3
x + y * z++ → 7
计算后: x=1, y=2, z=4(z已被z++修改)
过程: 先求值x→1, y→2, z++→返回3(然后z变4), 再算y*z=6, 最后1+6=7

--- 实验2: 类型提升 ---
byte(100) + byte(50) = 150(类型提升为int)
int + long → long: 300
long + float → float: 203.14
float + double → double: 5.857999896526337

⚠️  long→float 精度丢失:
  long原值:  9223372036854775807
  float转换: 9.223372E18(丢失了有效位数!)

--- 实验3: 求值顺序与副作用 ---
arr[0] + arr[++idx] → arr[0] + arr[1] = 10 + 20 = 30
最终 idx = 1

完整示例二:飞翔商城价格表达式逐步骤解析

场景

黄俪在飞翔商城开发促销规则引擎,手动拆解复杂表达式的每一步求值过程,理解求值顺序与类型提升的协同作用。

/**
 * 飞翔商城 —— 价格表达式手动求值器
 * 逐步解析复杂表达式,显式展示每一步的求值过程
 */
public class PriceExpressionParser {
    public static void main(String[] args) {
        System.out.println("===== 飞翔商城 价格表达式解析 feixiang.net =====\n");

        float basePrice = 188.88f;
        int quantity = 5;
        boolean isVip = true;
        boolean hasCoupon = false;
        float discount = 0.15f;   // 折扣率
        float shipping = 10.0f;   // 运费

        // --- 复杂表达式:最终价格 = 原价×数量×(1-折扣) + (VIP?0:运费) ---
        // 逐步手写求值过程

        System.out.println("表达式: finalPrice = basePrice * quantity * (1 - discount) + (isVip ? 0 : shipping)");
        System.out.println("已知: basePrice=" + basePrice + ", quantity=" + quantity
            + ", discount=" + discount + ", isVip=" + isVip + ", shipping=" + shipping + "\n");

        // 第一步:括号内优先 (1 - discount)
        float step1 = 1 - discount;
        System.out.println("步骤1: (1 - discount) = (1 - " + discount + ") = " + step1);

        // 第二步:三元运算 (isVip ? 0 : shipping)
        float step2 = isVip ? 0 : shipping;
        System.out.println("步骤2: (isVip ? 0 : shipping) = (" + isVip + " ? 0 : " + shipping + ") = " + step2);

        // 第三步:乘法(左结合) basePrice * quantity
        float step3 = basePrice * quantity;
        System.out.println("步骤3: basePrice * quantity = " + basePrice + " * " + quantity + " = " + step3);

        // 第四步:乘法继续 step3 * step1
        float step4 = step3 * step1;
        System.out.println("步骤4: " + step3 + " * " + step1 + " = " + step4);

        // 第五步:加法(最低优先级) step4 + step2
        float step5 = step4 + step2;
        System.out.println("步骤5: " + step4 + " + " + step2 + " = " + step5);

        System.out.println("\n最终价格: ¥" + step5);

        // --- 验证:直接计算,对比结果 ---
        float finalPrice = basePrice * quantity * (1 - discount) + (isVip ? 0 : shipping);
        System.out.println("直接表达式求值: ¥" + finalPrice);
        System.out.println("两步结果一致: " + (Math.abs(step5 - finalPrice) < 0.001f));

        // --- 类型提升的实际影响 ---
        System.out.println("\n===== 类型提升示例 =====");
        byte itemCount = 10;
        short unitPrice = 200;
        // byte + short → int + int → int
        int totalPrice = itemCount + unitPrice;
        System.out.println("byte(10) + short(200) = " + totalPrice + "(类型: int)");
        System.out.println("如果不提升为int,byte范围-128~127无法容纳10+200=210");
    }
}

运行输出:

===== 飞翔商城 价格表达式解析 feixiang.net =====

表达式: finalPrice = basePrice * quantity * (1 - discount) + (isVip ? 0 : shipping)
已知: basePrice=188.88, quantity=5, discount=0.15, isVip=true, shipping=10.0

步骤1: (1 - discount) = (1 - 0.15) = 0.85
步骤2: (isVip ? 0 : shipping) = (true ? 0 : 10.0) = 0.0
步骤3: basePrice * quantity = 188.88 * 5 = 944.4000
步骤4: 944.4000 * 0.85 = 802.74
步骤5: 802.74 + 0.0 = 802.74

最终价格: ¥802.74
直接表达式求值: ¥802.74
两步结果一致: true

===== 类型提升示例 =====
byte(10) + short(200) = 210(类型: int)
如果不提升为int,byte范围-128~127无法容纳10+200=210

易错场景

反例:忽略求值顺序导致逻辑错误

// ❌ 依赖自增/自减在表达式中的"直觉顺序"
int a = 1;
int result = a++ + a;  // 你以为:1 + 2 = 3?还是 2 + 2 = 4?
// 实际:a++ 返回 1(a变为2),然后 + a → 1 + 2 = 3
System.out.println(result);  // 3

// 类似陷阱:数组索引与自增
int[] arr = {10, 20, 30};
int i = 0;
int val = arr[i++] + arr[i];  // arr[0] + arr[1] = 10 + 20 = 30
System.out.println(val);  // 30,但写这种代码就是在埋雷

纠正:

// ✅ 正确:将副作用与纯计算分离
int a = 1;
int oldA = a;
a++;
int result = oldA + a;  // 1 + 2 = 3,意图明确

// 或:不在同一个表达式中混合使用
int[] arr = {10, 20, 30};
int val = arr[0] + arr[1];  // 清晰明了

面试考点

Q1:运算符优先级和求值顺序有什么区别?

优先级决定操作数的分组(哪两个操作数与操作符结合),但不决定操作数的求值顺序。Java严格规定了操作数是从左到右求值的,该规则不受优先级影响。例如 a + b * c——* 优先级高,意味着 b * c 先结合成一个操作数;但 a、b、c 三个子表达式的求值顺序始终是从左到右。

Q2:为什么 byte + byte 结果是 int?

Java语言规范规定:所有低于 int 的整数类型(byte、short、char)在参与算术运算前,都会被自动提升为 int。这是为了避免溢出——两个 byte 相加可能超过 -128~127 的范围。这也意味着 byte a=10, b=20; byte c = a + b; 是编译错误,必须 int c = a + b; 或 byte c = (byte)(a + b);。

Q3:long 到 float 的隐式转换安全吗?

不安全。虽然Java允许 long 隐式转换为 float(编译不会报错),但可能丢失精度。long 有64位,可以精确表示所有整数;float 只有23位有效精度位(尾数),超过 2^23(约838万)的整数无法精确表示。Long.MAX_VALUE(约92亿亿)转为 float 后会损失大约16位有效数字。关键业务场景(如金融金额)必须使用 BigDecimal。

上一页
运算符优先级