聊聊Java中的of
从JDK到Spring到业务代码of无处不在Optional.of()、LocalDate.of()、Stream.of()、List.of()……翻开任何一个稍具规模的Java项目of方法的数量可能比你想的多得多。这篇文章尝试把of这个命名的前世今生聊一聊它从哪来、JDK里怎么用的、开源框架怎么用的、我自己写的业务代码怎么用的以及什么时候该用of、什么时候不该用。of的命名由来一个常见的猜测是of是不是Guava带起来的不是。EnumSet.of()在Java 5就已经存在了这个时候Guava还没开源。EnumSet是Java标准库中最早使用of命名的一批类之一Joshua Bloch本人就是EnumSet的设计者他同时也是Effective Java的作者。Joshua Bloch在Effective Java第一版中就讨论过静态工厂方法的命名问题。到第三版他对常见命名做了更系统的归纳其中of的定义是An aggregation method that takes multiple parameters and returns an instance of this type that incorporates them.翻译过来of是一个聚合方法接收多个参数返回一个融合了这些参数的本类型实例。这个定义很精确地描述了of的语义。LocalDate.of(2026, 6, 2)就是把年、月、日三个参数聚合成一个日期对象。Stream.of(1, 2, 3)就是把多个元素聚合成一个流。Guava开源后ImmutableList.of()、ImmutableSet.of()、ImmutableMap.of()这些方法让of命名在Java社区大规模流行。但Guava不是发明者而是推广者。真正让of成为Java标准库一等公民的是Java 8。java.time包由Stephen Colebourne主导设计整个时间日期API全面采用of命名LocalDate.of()、LocalTime.of()、Duration.of()、Period.of()……这些方法的可读性远好于new LocalDate(2026, 6, 2)这种构造器调用。同一年Java 8还带来了Optional.of()、Stream.of()、Collector.of()等of在JDK中的存在感一下子拉满了。再往后Java 9引入了List.of()、Set.of()、Map.of()这些不可变集合的工厂方法of就成了JDK中最常见的静态工厂方法命名。值得一提的是Bloch在Effective Java中还定义了另一个常用命名fromA type-conversion method that takes a single parameter and returns a corresponding instance of this type。from是类型转换of是聚合两者分工明确。LocalDate.of(year, month, day)是聚合LocalDate.from(temporal)是转换。这个区分在JDK的时间API中贯彻得很好。JDK里的of方法JDK中定义了of方法的类数量远比大多数人印象中的多。我统计了一下JDK 17的源码至少有40多个类提供了of方法覆盖的of方法总量超过60个。按场景来分大致有五类。值包装Optional.of(T value)、OptionalInt.of(int value)、OptionalLong.of(long value)、OptionalDouble.of(double value)。这里有一个值得注意的设计Optional.of()传入null会抛NullPointerException而Optional.ofNullable()才允许null值。这个区分不是多余的它传递了一个明确的语义当你用of的时候你在声明这个值一定不为null。如果调用者传了null那是一个编程错误应该尽早暴露。时间对象java.time包是of方法最密集的区域。LocalDate有2个of重载LocalTime有3个LocalDateTime有7个。这些重载的参数从粗到细可以只传时分也可以精确到纳秒。Duration.of(long amount, TemporalUnit unit)的设计也有意思。Duration.ofSeconds(30)比new Duration(30)语义清晰得多你不需要猜30是秒还是毫秒。集合创建List.of()、Set.of()、Map.of()是Java 9引入的不可变集合工厂。它们替代了Arrays.asList()和Collections.unmodifiableList()的组合写法一行搞定。EnumSet.of()是个老面孔了从Java 5就有。它有一个特殊的重载策略5个固定参数的重载of(E)、of(E,E)、of(E,E,E)……of(E,E,E,E,E)再加一个可变参数版本of(E first, E… rest)。这不是炫技而是有实际原因的可变参数的调用会创建数组对象对于只有一两个枚举值的场景固定参数重载避免了不必要的数组分配。在EnumSet这种追求极致性能的工具类上这个优化是有意义的。流操作Stream.of(T t)和Stream.of(T… values)IntStream、LongStream、DoubleStream各有两个重载。单个元素版本返回的不是可变参数的退化形式而是专门的Stream实现性能更好。枚举操作Month.of(int)、DayOfWeek.of(int)这两个方法把int值转换为对应的枚举常量。Month.of(6)返回Month.JUNE。比起直接用Month.JUNEof方法的价值在于处理运行时数据数据库里存的是int需要转成枚举。下面这张速查表把JDK中最常用的of方法按场景归类场景类典型调用核心语义值包装OptionalOptional.of(value)非null值包装值包装OptionalIntOptionalInt.of(42)基本类型值包装时间LocalDateLocalDate.of(2026, 6, 2)年月日聚合时间LocalTimeLocalTime.of(14, 30)时分秒聚合时间LocalDateTimeLocalDateTime.of(date, time)日期时间聚合时间DurationDuration.ofSeconds(30)时间段创建时间PeriodPeriod.of(1, 2, 3)年月日周期时间YearMonthYearMonth.of(2026, 6)年月聚合时间ZoneOffsetZoneOffset.of(“08:00”)时区偏移集合ListList.of(1, 2, 3)不可变列表集合SetSet.of(1, 2, 3)不可变集合集合MapMap.of(“k1”, 1, “k2”, 2)不可变映射集合EnumSetEnumSet.of(A, B)枚举集合流StreamStream.of(1, 2, 3)流创建流IntStreamIntStream.of(1, 2, 3)基本类型流收集器CollectorCollector.of(supplier, accumulator, …)自定义收集器枚举MonthMonth.of(6)int转枚举枚举DayOfWeekDayOfWeek.of(1)int转枚举格式化HexFormatHexFormat.of()默认实例开源框架里的ofJDK之外主流Java框架也大量使用of命名。不过不同框架对of的采用程度差异很大。Spring FrameworkSpring对of的使用不算多但每个都有明确的设计意图。DataSize是Spring自定义的值对象用来表示数据大小。它没有只提供一个of方法而是提供了ofBytes、ofKilobytes、ofMegabytes、ofGigabytes、ofTerabytes一组方法。这是of命名的一个变体of 单位名。DataSize.ofMegabytes(512)比new DataSize(536870912)或DataSize.of(512, DataUnit.MEGABYTES)可读性好太多。这种ofXxx的命名方式在值对象设计中很实用把数值和单位绑在一起消除歧义。LogMessage.of(Supplier)是另一个有意思的设计。Spring的日志工具类用of接收一个Supplier实现延迟求值。日志消息只在真正需要输出时才被构建避免了不必要的字符串拼接开销。RegisteredBean.of(ConfigurableListableBeanFactory, String)是Spring 6.0为AOT编译引入的用来表示一个已注册但尚未实例化的Bean的元数据。RocketMQRocketMQ 4.9.8中CompressionType.of(String name)是唯一自定义的of方法。它从配置字符串转换为压缩类型枚举支持大小写不敏感。典型的用法是CompressionType.of(System.getProperty(“compress.type”, “ZLIB”))从系统属性读取压缩配置。这个设计把配置和枚举解耦了配置层只需要传字符串运行时通过of方法转成类型安全的枚举。业务代码里的of讲JDK和开源框架的of方法网上已经有很多文章了。真正少有人聊的是在自己的业务代码里of方法能怎么用、怎么用得好。我自己撸的基础框架里也是有用of的。异常创建这是我们框架里of方法最密集的场景。BusinessException.of()有6个重载可以只传错误码可以传错误码加错误信息可以传ErrorCode接口实现可以传ErrorEnum枚举可以传Result对象。调用者不需要关心BusinessException的构造器签名是什么只需要把自己手上已有的错误信息扔给of方法就行。BusinessTaskHandleException更进一步除了of方法还提供了ignore()、warning()、close()、hangUp()这些语义化的便捷方法。这些方法内部仍然是创建异常对象但调用者读代码时一眼就能看出意图BusinessTaskHandleException.warning(“STOCK_NOT_ENOUGH”, “库存不足”)比new BusinessTaskHandleException(true, “STOCK_NOT_ENOUGH”, “库存不足”, false)清晰得多。用of方法创建异常有几个好处。第一异常类的构造器可能变但of方法的签名可以保持稳定调用者不受影响。第二of方法可以做参数适配同一个异常类支持多种入参格式。第三of方法的名字本身就在告诉调用者这是一个工厂操作比new更语义化。上下文构建ChainContext.of(T param)、BusinessTaskHandlerContext.of(Long taskId)、FlowCtrlContext.of(String channelKey)……这些上下文对象的共同特征是字段多、初始化逻辑简单、调用频繁。用of方法替代构造器调用者不需要记住参数顺序不需要写一大段setter链一行搞定。工具类初始化ConcurrentDateFormat.of(String format)创建线程安全的日期格式化器。Lazy.of(Supplier)创建延迟计算容器。XmlUtils.of(InputStream)或XmlUtils.of(String)创建XML解析器。QuickJsonReader.of(Object)创建JSON读取器。这些工具类的共同模式是内部初始化逻辑比较复杂创建线程池、解析文档、构建XPath对象等但对外只暴露一个简洁的of方法。调用者不需要知道内部细节of方法把复杂度封装在背后。DTO构建ErpNumber.of(Object number)创建与ERP系统交互的数字对象。InnerSkuStock.of(skuCode, shopCode, qty)创建库存变化DTO中的SKU对象。这类of方法的特点是参数少、逻辑简单但用of替代构造器后代码的可读性提升明显ErpNumber.of(123456)比new ErpNumber(123456)更口语化读起来更自然。模式归纳从这些业务代码中可以归纳出of方法在业务场景的四种典型用法用法典型类特征异常工厂BusinessException.of多重重载适配不同入参语义化便捷方法上下文创建ChainContext.of字段多但初始化逻辑简单一行创建工具类初始化ConcurrentDateFormat.of内部复杂但对外接口简洁DTO构建ErpNumber.of参数少可读性优于构造器of的实践建议聊了这么多of的用法最后说说什么时候该用of、什么时候不该用。该用的场景创建本类型实例参数到对象的聚合关系明确。LocalDate.of(year, month, day)比new LocalDate(year, month, day)可读性好因为of这个介词天然表达了一种组合关系。替代多参数构造器。当构造器参数超过3个调用者很难记住参数顺序。of方法可以通过命名和重载来消除歧义。需要隐藏实现细节。of方法可以返回子类型调用者不需要知道实际创建的是哪个实现类。不该用的场景类型转换场景用from更合适。LocalDate.from(temporalAccessor)是从另一种时间类型转换而来不是从零开始聚合。如果你方法的语义是把A类型转成B类型from比of更准确。需要返回不同类型时用getInstance或create。of方法的隐含约定是返回本类型实例。如果你的工厂方法返回的是别的类型用of会让调用者困惑。逻辑太重不适合of。of方法给人的预期是轻量级的对象创建如果内部有复杂的校验、IO操作或远程调用用of会误导调用者。设计技巧重载不宜过多。BusinessException.of有6个重载这已经偏多了。超过4个重载的时候考虑用Builder模式或引入参数对象。配合泛型保持类型安全。ChainContext.of(T param)的泛型签名让调用者不需要强转编译器帮你保证类型正确。考虑是否需要缓存实例。Boolean.valueOf(boolean)会缓存实例但大多数of方法不需要。如果你的of方法创建的是不可变对象且创建成本不低可以考虑缓存。JDK中Boolean、Integer等包装类的valueOf做了缓存但of方法一般不做因为of的语义是聚合创建每次聚合的参数通常不同。of和from的区分判断如果你的方法是多个参数聚合成一个对象用of如果是从一个已有对象转换类型用from。记住这一条命名就不会纠结。小结of不是什么高级设计模式它就是一个命名约定但它是一个被整个Java生态验证过的好约定。从2004年EnumSet.of()开始经过Guava的推广、JSR-310的确立、Java 9集合工厂方法的跟进of已经成为Java开发者最熟悉的静态工厂方法命名。在实际项目中of方法最有价值的地方不在于它比构造器优雅多少而在于它把创建对象的意图表达得更明确。new BusinessException(code, msg)你看不出这是在创建异常还是在做别的什么BusinessException.of(code, msg)一眼就知道这是工厂创建。这种语义上的清晰度在项目规模大了之后价值会越来越明显。