Java 语法糖详解
语法糖(Syntactic Sugar)是编译器提供的简写:不增加语言表达能力,只提升书写效率。JVM 只认识脱糖(desugar)之后的基础结构,很多「反直觉」的运行时行为——Integer 的 == 结果分裂、增强 for 里删除元素抛异常——把糖剥掉之后都一目了然。本文逐一拆解六种常用语法糖在编译后的真实形态。 它解决什么问题Java 语言相对克制,许多便利写法并非 JVM 原生能力,而是 javac 在编译期的变换。理解脱糖结果有三重价值: 解释怪异行为:包装类型 == 时真时假、泛型方法无法按类型参数重载,都是擦除与缓存的直接推论; 评估性能成本:循环里的自动装箱会悄悄产生海量临时对象,看字节码才能发现; 读懂反编译与字节码:排查线上问题时面对的从来不是源码,而是脱糖后的形态。 核心原理javac 的编译流水线中,语义分析阶段包含一个专门的解糖步骤:把增强 for、变长参数、字符串 switch 等高级写法还原为普通的循环、数组与整数分派,再生成字节码。也就是说,JVM 指令集中不存在这些语法的对应物。 验证方式很直接:javap -c 查看字节码,或用反编译工...
Java SPI 机制详解
SPI(Service Provider Interface,服务提供者接口)是 JDK 内置的服务发现机制:接口由调用方定义,实现由第三方 jar 提供,ServiceLoader 在运行时按 classpath 上的配置文件找到并加载这些实现。它把「用哪个实现」的决定权从代码挪到部署,是 JDBC、SLF4J、Dubbo 等框架插件化能力的共同基石。 它解决什么问题面向接口编程能降低模块耦合,但接口调用方总要面对一个问题:实例从哪来?如果调用方直接 new 一个实现类,就又把抽象绑死在了具体实现上,换实现就得改代码,违背开闭原则。SPI 给出的答案是「控制反转」的朴素版本——把实现类的名单写进配置文件放进 jar 包,框架在运行时扫描加载,换实现只需要换 jar 包,调用方一行代码不动。 它还顺手解决了类加载层面的一个结构性矛盾。双亲委派模型下,由启动类加载器(Bootstrap ClassLoader)加载的核心库看不见应用 classpath 上的类,可 JDBC 这类场景恰恰是核心库(DriverManager 在 JDK 内部)要去加载厂商提供的驱动实现(在应用 ...
Java 序列化详解
序列化(Serialization)把对象状态转成可存储、可传输的字节流,反序列化则是它的逆过程。RPC 调用、缓存读写、Session 复制都建立在这对操作之上;但 JDK 原生序列化因体积臃肿、性能平庸且背着反序列化漏洞的历史包袱,早已不是现代系统的默认选项。 它解决什么问题Java 对象只活在 JVM 堆内存里,进程一停就消失。一旦需要把对象写进磁盘、发到网络对端、放进 Redis 之类的外部存储,就必须先把它「拍平」成字节序列——这正是序列化的职责。放到网络模型里看,它对应 OSI 中表示层(Presentation Layer)的工作:把应用层数据结构编码为字节流,以及反向还原,所以序列化协议归属 TCP/IP 模型的应用层范畴。 围绕「对象 → 字节」这条通路,JDK 设计了一组控制点:Serializable 与 Externalizable 决定走哪套机制,serialVersionUID 管版本兼容,transient 管字段取舍。理解它们,才能谈得上正确使用和正确选型。 核心原理JDK 原生序列化的门槛极低:类实现 java.io.Seriali...
Java 反射机制详解
反射(Reflection)让程序在运行时检查类的结构、读写字段、调用方法,而不需要在编译期知道具体类型。它是 Spring 扫描注解、MyBatis 映射结果集、JDK 动态代理转发调用的底层能力;日常业务代码应当克制使用,但读框架源码、写扩展点时离不开它。 它解决什么问题普通 Java 代码是「编译期绑定」的:调用一个方法,编译器必须能在编译期看到类和方法签名。但框架面对的恰恰是编译期未知的类——Spring 不知道用户会写出什么 @Component,MyBatis 不知道结果集要装进哪个 POJO,JUnit 不知道哪些方法标了 @Test。 反射把绑定时机推迟到运行期:给框架一个类名或一个 Class 对象,它就能查出这个类有哪些字段、哪些方法、带着什么注解,并完成实例化和调用。可以说,没有反射就没有今天 Java 生态的「约定优于配置」;反射是框架向用户代码反向伸手的那只手。 核心原理JVM 加载一个类后,会在方法区(JDK 8 起为元空间)保存它的类型元数据,并在堆中创建一个对应的 java.lang.Class 对象作为访问入口。反射 API 就是围绕 Cla...
Java 代理模式详解
代理(Proxy)的核心思想是在不修改目标类的前提下,为其方法调用织入日志、事务、鉴权等横切逻辑。Java 中代理分两条路线:编译期就确定的静态代理,以及运行期动态生成字节码的动态代理;后者又分 JDK 动态代理与 CGLIB 两种实现,Spring AOP、RPC 客户端、ORM 框架都建立在这套机制之上。 它解决什么问题业务代码里常常混着一些与核心逻辑无关却又到处都需要的功能:记录方法耗时、开启数据库事务、校验权限、远程调用。如果直接写进业务方法,同一个逻辑会在几十上百个类里重复出现,改一处就要改一片。 代理模式(Proxy Pattern)的解法是引入一个「替身」:调用方拿到的不是真实对象,而是它的代理。代理持有真实对象的引用,在转发调用前后插入额外动作。真实对象对此一无所知,横切逻辑被收拢到代理这一层。当代理类由程序员手写时叫静态代理;当代理类的字节码在运行期由 JVM 动态生成时,就是动态代理。 核心原理三条路线的本质区别在于「代理类的字节码何时产生、以何种方式产生」: 静态代理:代理类是开发者手写的 .java 文件,编译期就固定下来。代理类与目标类实现同一个接...
Java 金额用 long 还是 BigDecimal?
金额该用 long 存分还是 BigDecimal 存元,是后台系统设计里的高频争论。两者并非互斥的替代品,而是「定点整数」与「十进制任意精度」两种表示模型,分别适配「已定局的金额存储」与「进行中的精确计算」。本文从表示原理出发,给出可落地的选型框架。 它解决什么问题金额处理有三个硬要求:不能丢精度、舍入规则明确、存储与计算口径一致。double 因为二进制浮点的近似表示直接出局后,剩下两条路: long 存最小货币单位(如分),回答的是「金额怎么存」; BigDecimal 做带舍入规则的十进制运算,回答的是「金额怎么算」。 很多争论源于把这两件事混为一谈。实际系统中两者常常共存:记账与存储用整数,费率与换算用 BigDecimal,边界处做严格换算。 核心原理long 存分:把小数点变成约定人民币精确到分,则 19.99 元保存为整数 1999。小数点的位置不再由数据类型表达,而是固化为一项全系统一致的业务约定(scale = 2)。所有运算退化为纯整数运算,天然没有浮点误差,硬件级速度,定长 8 字节。 代价是约定本身成为新的风险源:单位必须随数据一起流动...
Java 关键字总结
Java 有五十来个关键字,按字典逐个背没有意义。本文按用途把它们分成访问控制、类与继承、流程控制、异常、并发、类型与序列化六组,讲清每组的语义要点与常见误用,并对 final、static、synchronized、volatile、transient、instanceof 六个高频关键字给出代码示例。 分组总览访问控制:private、默认(包可见)、protected、public 四级可见性逐级放大。常见误用是所有成员都 public,破坏封装;另一个坑是忘记默认级别是包可见,跨包测试时才发现访问不到。 类与继承:class、interface、extends、implements、abstract、this、super。this 指向当前实例,super 用于访问父类成员——它不是「父类对象」的引用。构造器中 this(...) 和 super(...) 必须放在首行;静态上下文没有当前实例,this 和 super 都不能用。 流程控制:if、switch、for、while、do、break、continue、return。switch 从 Java 7 起支持...
泛型&通配符详解
泛型(Generics)把类型检查从运行期前移到编译期,让集合和通用 API 在写代码时就能暴露类型错误;通配符(Wildcard)则在「保持 API 灵活」与「保证读写安全」之间给出受控的折中。理解类型擦除(Type Erasure)是理解这一切的前提。 它解决什么问题JDK 5 之前,集合里取出的元素都是 Object,用之前得手动强转。转错了编译器不管,运行期才抛 ClassCastException——错误暴露得越晚,定位成本越高。泛型要解决的正是这个问题:把「这个列表只装 String」变成类型系统的一部分,让编译器在编译期就拦住错误,同时自动插入类型转换,省去满屏的强制转型。 泛型有三种使用形态:泛型类(class Box<T>)、泛型接口(interface Comparable<T>)、泛型方法(static <T> T pick(T a, T b))。其中泛型方法的类型参数声明在返回值之前,与所在类是否泛型无关,静态方法也完全可以拥有自己的类型参数。 核心原理类型擦除:兼容优先的设计Java 泛型没有选择 C# 那样的运行...
BigDecimal 详解
float/double 遵循 IEEE 754 二进制浮点标准,像 0.1 这样的十进制小数在二进制下是无限循环的,只能截断存储,误差随之产生。BigDecimal 用「任意精度整数 + 小数位数」的十进制模型做运算,是金额、计量等对精度敏感场景的标配。本文从表示原理出发,讲清它的正确用法与常见陷阱。 它解决什么问题先看一段几乎每个 Java 开发者都遇到过的代码: System.out.println(0.1 + 0.2); // 0.30000000000000004System.out.println(1.0 - 0.9); // 0.09999999999999998System.out.println(2.0f - 1.9f == 0.1f); // false 这不是 Java 的缺陷,而是二进制浮点数的固有性质,任何遵循 IEEE 754 的语言表现都相同。财务对账要求分毫不差,利率、汇率计算要求明确的舍入规则,浮点的近似表示在这些场景下不可接受。BigDecimal 存在的意义,就是把十进制小数的语义原样搬...
Java 基础专题:语法、面向对象、泛型、反射、代理与序列化
Java 基础专题覆盖语法、面向对象、泛型、反射、代理与序列化等内容。它的价值不在单个语法点,而在于这些机制是集合、并发、JVM 乃至 Spring 框架的共同前置——学完的标准是能解释框架的「魔法」,而不只是会调 API。 模块包含哪些主题按解决的问题划分,本专题可分为四组: 分组 主要主题 解决什么问题 语言底座 基本语法、面向对象、异常、常用类、关键字 建立写 Java 的基本功和对象模型直觉 参数与类型语义 值传递、equals/hashCode、对象可变性 解释「方法调用后对象状态为何改变」这类高频误解 编译期类型系统 泛型、通配符、类型擦除、语法糖 分清编译器检查了什么、运行期又擦掉了什么 运行时机制 反射、动态代理、SPI、序列化、Unsafe 看懂框架如何在运行时装配、拦截和扩展对象 BigDecimal 与金额计算属于工程细节补丁,相对独立,可在任意阶段按项目需要插入。 主题之间的依赖语言底座是一切的前提:不理解类与继承,泛型的边界和反射的 Class 模型都无从谈起。值传递与 equals/hashCode ...