Java SPI 机制详解
SPI(Service Provider Interface)把「接口定义」与「实现部署」解耦:核心库只依赖抽象,具体实现在 classpath 通过配置文件声明,由 ServiceLoader 在运行时加载。 核心概念与 API 对比:API 是实现方提供接口+实现,调用方直接用;SPI 是调用方/框架定义接口,第三方 jar 提供实现。配置文件路径:META-INF/services/<接口全限定名>,每行一个实现类。ServiceLoader.load(Interface.class) 迭代得到全部实现(懒加载)。 关键机制与实践经典案例:JDBC 4+ 自动发现 Driver;SLF4J 绑定 Logback/Log4j2;Dubbo 扩展点(在其自有 SPI 上增强)。打破双亲委派:线程上下文类加载器(TCCL)常配合 SPI 让框架加载应用 classpath 中的实现。自定义 SPI 时保持实现类有无参构造,便于 ServiceLoader 实例化。 1234ServiceLoader<Driver> drivers ...
Java 序列化详解
序列化把对象变为可存储或传输的字节序列;反序列化是逆过程。RPC、缓存、Session 持久化都涉及,但 JDK 原生序列化在现代微服务里已很少作为首选。 核心概念实现 Serializable 标记接口即可参与 JDK 默认序列化。serialVersionUID 用于版本兼容,类结构变更而不更新 UID 可能抛 InvalidClassException。transient 字段跳过默认序列化;敏感或派生字段可自定义 writeObject/readObject。 关键机制与实践生产更常用 JSON(可读、跨语言)、Protobuf/Kryo/Hessian(二进制、体积小)。JDK 序列化缺点:体积大、速度慢、反序列化存在历史安全漏洞(gadget chain)。Redis 存对象常用 JSON 或专用序列化;RPC 框架自带 codec。若必须用 Java 序列化,避免反序列化不可信数据。 1234public class User implements Serializable { private static fina...
Java 反射机制详解
反射让程序在运行时检查与调用类成员,是「框架能扫描注解、注入 Bean、映射 SQL 字段」的底层能力之一;日常业务应克制使用,读框架与写扩展点时则离不开它。 核心概念Class 对象代表加载后的类型元数据。获取方式:类名.class、对象.getClass()、Class.forName()、类加载器 loadClass()(后两者触发时机略有不同)。核心 API:getDeclaredFields/Methods/Constructors、newInstance(已废弃路径)、Field#setAccessible、Method#invoke。 关键机制与实践Spring 解析 @Component 扫描 classpath;MyBatis 将结果集映射到 POJO 字段;JUnit 跑 @Test 方法——都依赖反射。JDK 动态代理的 invoke 同样调用 Method.invoke。性能方面:首次反射有校验开销,可缓存 Method/Constructor 并配合 setAccessible(true);极端场景可用 MethodHandle 或字节码生...
Java 代理模式详解
代理在不改目标类的前提下织入横切逻辑(日志、事务、鉴权)。Java 里分编译期确定的静态代理与运行期生成字节码的动态代理,Spring AOP 与 RPC 客户端都建立在这套机制上。 核心概念静态代理:手写代理类实现同一接口,委托目标对象;接口变更时代理也要改,维护成本高。JDK 动态代理:基于接口,Proxy.newProxyInstance + InvocationHandler,在 invoke 里前后增强。CGLIB:继承目标类生成子类,覆盖非 final 方法;无接口也可代理,但不能代理 final 类/方法。 关键机制与实践JDK 代理要求目标实现接口;Spring 默认有接口用 JDK,无接口用 CGLIB(Boot 2.x 后 CGLIB 更常见)。InvocationHandler.invoke 里通过 method.invoke(target, args) 调用真实方法,此处即反射调用点。Self-invocation(类内部 this.foo())不会走代理,是 @Transactional 失效经典原因之一。 123SmsService pr...
Java 金额用 long 还是 BigDecimal?
金额类型选型没有银弹:高并发记账常用「long 存最小货币单位」,复杂费率与多币种换算则倾向 BigDecimal;关键是存储单位、舍入规则、溢出三者一致。 核心概念long 分/厘:整数运算快、无浮点误差,适合订单、余额等高频加减;需约定 scale(如分),展示层再 /100。BigDecimal:适合利率、汇率、税务等多步除法与不同舍入策略;对象开销更大。数据库字段应与应用层单位一致(BIGINT 分 或 DECIMAL(p,s))。 关键机制与实践阿里规约类实践:运算用整数、展示用格式化;除法统一 RoundingMode(如 HALF_UP)并在 PRD 写清。long 注意 Long.MAX_VALUE 溢出,可预留更大类型或拆账户。跨币种先定「记账币种」与「展示币种」,避免混用 double。 12long priceCents = 1999L; // 19.99 元long taxCents = (priceCents * 6 + 50) / 100; // 6% 税,四舍五入到分 常见误区与小结 数据库存 double 做金额(绝对禁止)。 lo...
Java 关键字总结
Java 关键字约五十个,面试与 Code Review 里高频的是修饰类/成员可见性与生命周期的几个:final、static、this、super、volatile、transient 等。 核心概念final:类不可继承、方法不可重写(private 隐式 final)、变量引用不可改(基本类型值不可变)。static:属于类而非实例,静态块/方法无 this,常见单例与工具类。this/super:构造链调用、区分同名成员、访问父类被隐藏成员。volatile:保证可见性与有序性,不保证复合操作原子性。 关键机制与实践静态方法不能被重写(可被隐藏);实例方法通过虚表分派。static 字段在类加载初始化阶段赋值,注意与实例字段初始化顺序。接口中的 default/static 方法(Java 8+)改变了「接口纯抽象」的旧印象,但成员仍默认 public static final。 1234class Parent { void greet() { System.out.println("paren...
泛型&通配符详解
泛型把类型检查前移到编译期,让集合与通用返回值在写代码时就能发现类型错误;通配符则在「保持灵活」与「保证读写安全」之间做权衡。 核心概念泛型有三种形态:泛型类、泛型接口、泛型方法。Java 通过类型擦除实现兼容:编译后泛型参数变成上界类型(默认 Object),运行时无法做 new T() 或 instanceof List<String>。通配符 ? extends T(上界,只读为主)与 ? super T(下界,可写 T 及其子类)解决协变/逆变问题。 关键机制与实践PECS 口诀:Producer Extends, Consumer Super——从集合读用 extends,往集合写用 super。框架里常见 CommonResult<T>、Page<T>、Collections.sort(List<T>) 都是泛型落地。反射可通过 ParameterizedType 读取部分泛型声明信息,弥补擦除带来的元数据缺失。 12345void copy(List<? extends Number> src...
BigDecimal 详解
金额与计量场景里,float/double 的二进制表示无法精确覆盖多数十进制小数,直接做加减比较会踩坑;BigDecimal 用十进制语义做运算,是金融类业务的常规选择。 核心概念浮点误差来自「十进制 → 二进制 → 有限位存储」的舍入链。BigDecimal 内部用整数标度(unscaled value + scale)表示小数,运算时可显式指定精度与 RoundingMode,避免 0.1 + 0.2 != 0.3 这类问题。 关键机制与实践构造:优先 new BigDecimal("0.1") 或 BigDecimal.valueOf(0.1d),避免 new BigDecimal(0.1) 已带入 double 误差。运算:加减乘除都返回新对象,除法必须带 scale 与舍入模式。比较:用 compareTo,不要用 equals(1.0 与 1.00 标度不同则不相等)。 1234BigDecimal a = new BigDecimal("1.0");BigDecimal b = new BigDecimal(&...
Java 基础专题:语法、面向对象、泛型、反射、代理与序列化
Java 基础不是背语法清单,而是理解对象模型、参数传递、泛型擦除、反射调用和框架扩展机制——这些是集合、并发、JVM 与 Spring 的共同前置。 核心概念Java 基础可拆为四块:语言语法与 OOP(类、继承、多态、异常)、类型与参数语义(值传递、引用变量)、编译期类型系统(泛型、通配符、擦除)以及运行时元编程(反射、动态代理、SPI、序列化)。面试与工程里反复出现的,往往是后三块与日常写法的落差。 关键机制与实践建议按「能写代码 → 能解释行为 → 能读框架」递进:先过语法与 OOP,再专门攻克值传递与 equals/hashCode;然后学泛型与通配符的 PECS 原则;最后补反射、JDK/CGLIB 代理、SPI 与序列化边界。读 Spring、MyBatis 源码前,至少应能说明 @Autowired 和 Mapper 接口为何离不开反射与代理。 常见误区与小结 把「对象引用当引用传递」与 Java 只有值传递混为一谈。 认为 List<String> 运行时仍带类型信息(擦除后都是 raw type)。 生产环境直接使用 JDK...
Java 知识体系:从语言基础到并发、JVM 与 IO
系统学习 Java 的目标不是记住所有 API,而是建立从语言语义到运行时行为的连续模型。遇到性能、并发或兼容性问题时,能够沿着源码、字节码、JVM 和操作系统逐层定位,比背诵零散结论更重要。 学习顺序第一阶段聚焦语言基础。需要真正理解值传递、对象生命周期、继承与组合、异常、泛型擦除、反射和注解。每个概念都应配一个可运行的小实验,并区分编译期约束与运行期行为。equals 与 hashCode、不可变对象、资源关闭等主题会直接影响后续工程质量。 第二阶段学习集合。不要只背 ArrayList、LinkedList 和 HashMap 的差异,而要从数据布局、扩容、哈希冲突、迭代行为和线程安全解释选择依据。阅读关键源码时先找入口、核心字段与不变量,再跟踪一次典型操作,避免陷入无关实现细节。 第三阶段进入并发。以 Java 内存模型和 happens-before 为主线,依次理解 synchronized、显式锁、CAS、AQS、并发容器和线程池。并发正确性必须通过状态与时序分析,不能把“测试没有复现”当作安全证明。练习时应主动构造竞争、取消、超时和关闭场景。 JVM 与 IO...