一个 List.of 引发的“血案”

[Java] List.of() 报错问题解决 首先,检查您使用的Java版本是否正确? 由于Java 9才支持List接口的static工厂方法,请参见List.of。 private static List<Item> defaultItems() { //java 8 return Arrays.asList( new Item(1L, "Burger", 599L, "Tasty", "https://cdn.auth0.com/blog/whatabyte/burger- 阅读详情

本文作者将分享一个使用List.of后掉进的坑以及爬坑的全过程,希望大家能引以为戒同时引起这样的意识:在使用新技术前先搞清楚其实现的原理。

引

随着卓越工程的推进,很多底层技术的升级迭代被正式投入使用,例如 JDK11 的升级。然而,当我们拥抱变化,欣喜地使用一些新特性或者语法糖的同时,也有可能正在无意识的掉入一些陷阱。

本篇文章,我将分享一个使用List.of后掉进的坑以及爬坑的全过程,希望大家能引以为戒同时引起这样的意识:在使用新技术前先搞清楚其实现的原理。

案发现场

一句话总结:在一次后端发布的变更后,前端解析接口返回的格式失败。

前情提要:

  • 后端 JAVA 应用 JDK 版本11,提供 HSF 服务端接口。

  • 前端通过陆游平台(一个 Node 可视化逻辑编排的平台)配置接口,内部通过 node 泛化调用后端的 HSF 接口,平台解析返回接口结果。

过程回顾:

  1. 后端发布的变更示意:

// 发布前
public List<String> before(Long id) {
    ...
    if (...) {
        return null;
    }
    ...
}

// 发布后
public List<String> after(Long id) {
    ...
    if (...) {
        return List.of();
    }
    ...
}

这里的核心变化点就是将默认的返回从 null 改成了 List.of() 。

为什么可以这么改?已知前端对null和空数组[]做了同样的兼容逻辑。

  1. 前端获取到接口的格式变化:

// 发布前{  "test": null}// 发布后{  "test": {        "tag": 1    }}

这个结构的变更直接导致了前端后续的字段结构解析失败,因为理论上 test 字段需要提供一个数组的格式(也可以是null),但是实际变成了一个对象。

所以整个环节中最离奇的是:为什么我的List.of在前端调用返回的接口中变成了一个带有tag字段的对象,它到底经历了怎么样的转换过程?

案情推理

List.of 触发的离奇现象让我不得不重新审视它,一步步看下它的源码实现。

1. 初窥门径:List.of

public interface List<E> extends Collection<E> {    /**     * Returns an unmodifiable list containing zero elements.     *     * See <a href="#unmodifiable">Unmodifiable Lists</a> for details.     *     * @param <E> the {@code List}'s element type     * @return an empty {@code List}     *     * @since 9     */    static <E> List<E> of() {        return ImmutableCollections.emptyList();    }}

从官方注释中得到3点结论:

  1. 这是一个 JDK9 之后的特性;

  2. 返回的是一个不可修改的数组;

  3. 底层实现使用的 ImmutableCollections 的 emptyList 方法,而 ImmutableCollections 这个类是一个不可变集合的容器类;

2. 渐入佳境:ImmutableCollections.emptyList

class ImmutableCollections {
    
    static <E> List<E> emptyList() {
        return (List<E>) ListN.EMPTY_LIST;
    }

     static final class ListN<E> extends AbstractImmutableList<E>
            implements Serializable {

        // EMPTY_LIST may be initialized from the CDS archive.
        static @Stable List<?> EMPTY_LIST;

        static {
            VM.initializeFromArchive(ListN.class);
            if (EMPTY_LIST == null) {
                EMPTY_LIST = new ListN<>();
            }
        }
        ...
    }

    static abstract class AbstractImmutableList<E> extends AbstractImmutableCollection<E>
            implements List<E>, RandomAccess {
                ...
    }
}

到这一步,案件的主人公终于登场了:一个新的类 ListN。但是在这段代码中,还有很多隐藏的细节线索:

  1. ListN 是 List 的实现类:ListN 继承了AbstractImmutableList,而 AbstractImmutableList 实际又实现了List;

  2. ListN 中的静态变量 EMPTY_LIST 会被初始化为一个空的 ListN 的对象;

  3. emptyList 方法中做了 List 类型的强转,但是由于JAVA的类型转换原则,实际仍然返回的是一个ListN对象(这是关键线索之一),通过排查过程中发现的阿尔萨斯监控也可以确认这一点:

图片

3. 直击要害:node的 HSF 解析

陆游平台调取HSF接口走的是node的泛化调用,默认情况下node只能解析一些基础的java类型,例如List和Map。

图片

一个完整的类型映射表可以查看:java-对象与-node-的对应关系以及调用方法

而遇到这次返回的 ListN,可以确定是这种特殊类型在序列化/反序列化的过程中出现了不同的逻辑导致。

4. 真相大白:ListN的序列化​​​​​​​

static final class ListN<E> extends AbstractImmutableList<E>
            implements Serializable {
    @Stable
    private final E[] elements;

    @SafeVarargs
    ListN(E... input) {
        // copy and check manually to avoid TOCTOU
        @SuppressWarnings("unchecked")
        E[] tmp = (E[])new Object[input.length]; // implicit nullcheck of input
        for (int i = 0; i < input.length; i++) {
            tmp[i] = Objects.requireNonNull(input[i]);
        }
        elements = tmp;
    }       

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        throw new InvalidObjectException("not serial proxy");
    }
                
    private Object writeReplace() {
        return new CollSer(CollSer.IMM_LIST, elements);
    }
}

ListN实现了自定义的序列化方法 writeReplace 和反序列方法 readObject。readObject直接抛出异常是一个防御性措施,说明该类直接反序列化会报错,来保证自己的不可变性。而 writeReplace 表示在序列化写入的时候替换成另一个对象,在这里返回的是一个内部的序列化代理对象CollSer(关键线索之二)。在实例化这个CollSer对象的时候,传递了2个变量:

  • CollSer.IMM_LIST 静态值 = 1

  • elements 一个空的对象数组 = new Object[0]

final class CollSer implements Serializable {
    private static final long serialVersionUID = 6309168927139932177L;

    static final int IMM_LIST = 1;
    static final int IMM_SET = 2;
    static final int IMM_MAP = 3;
    
    private final int tag;

    /**
     * @serial
     * @since 9
     */
    private transient Object[] array;

    CollSer(int t, Object... a) {
        tag = t;
        array = a;
    }
}

注意这里见到了我们眼熟的 tag 字段,另外一个字段 array 被 transient 标识所以序列化处理过程中会被忽略,这下我们终于知道 tag = 1 是怎么来的了。

结案陈词

综上所述,当后端在HSF接口中使用了 List.of() 做返回,在 node 调用 HSF 序列化获取返回结果时会解析成一个带有tag字段的对象,而不是预期的空数组。这个问题其实想解决很简单,将 List.of() 替换成我们常用的 Lists.newArrayList() 就行,本质上还是对底层实现的不清晰不了解导致了这整个事件。

当然在结尾处,其实还有一个疑点,在 HSF 控制台调试这个接口的时候,我发现它的 json 结构是可以正确解析的:

图片

怀疑可能是序列化类型的问题,hsfops 也是用了泛化调用,序列化类型是 hessian,可能 node 的序列化类型不一样,这个后续研究确定后我再补充一下。

最后的反思与大家共勉:对于新技术(或者新特性)的应用一定要先搞清楚内部的实现细节,不然可能出现使用时的大坑。

GD32F303在FreeRTOS里用浮点数就HardFault?一个宏定义引发的血案与三种修复方案 本文深入分析了GD32F303在FreeRTOS中使用浮点运算导致HardFault的问题根源,揭示了中断处理与任务切换中的关键机制冲突。针对这一经典问题,提供了三种实用解决方案:修改FreeRTOSConfig.h宏定义、调整启动文件中断向量表以及优化编译器设置,帮助开发者彻底解决浮点运算引发的系统崩溃问题。 阅读详情

相关推荐

PyTDX get_security_list踩坑记:start=8000时数据为空?一个编码问题引发的血案

本文深入分析了PyTDX库中get_security_list函数在start=8000时数据获取失败的编码问题,揭示了GBK解码异常的根本原因,并提供了三种解决方案。通过详细的协议层分析和代码示例,帮助开发者有效解决类似的数据解析问题,提升量化系统的健壮性。

weixin_27372101的博客 175

List.of() 与 Arrays.asList()总结

List.of()和是 Java 中用于创建列表的两种不同方法,它们在不可变性、可变性、允许空元素、关联数组、返回类型等方面存在重要差异。你可以根据你的需求选择适当的方法。使用List.of()创建的列表是不可变的,适用于需要不可变性和线程安全性的情况。使用创建的列表是可变的,适用于需要在后续操作中修改列表的情况,同时它与底层数组关联。要注意List.of()不允许空元素,而允许。最终,选择哪种方法取决于你的具体需求和代码设计。

架构大数据双料架构师的博客 1万+

List.of()的坑——不能够序列化

List.of()的坑——不能够序列化 场景:在dubbo接口中返回了空集合是使用了List.of() 方法创建了个空的集合,放入到响应数据中 报错信息如下: Exception in thread "main" com.alibaba.com.caucho.hessian.io.HessianFieldException: com.demo.SerializeTest$ResponseDto.data: java.util.CollSer:java.lang.reflect.InvocationTarge

qq_43039260的博客 2246

Java9中List.of和Arrays.asList区别

Arrays.asList返回可变的list,而List.of返回的是不可变的list List<Integer> list = Arrays.asList(1, 2, null); list.set(1, 10); // OK List<Integer> list = List.of(1, 2, 3); list.set(1, 10); // Fails Arrays.asList支持null,而

fofabu2的博客 2万+

深度学习驱动材料科学:晶体生成与性质预测技术综述

深度学习作为人工智能的核心技术,通过模拟人脑神经网络处理复杂数据,在多个领域展现出强大的模式识别与生成能力。其原理在于构建多层非线性变换,从海量数据中自动学习特征表示与映射关系。这一技术价值在于能够高效处理高维、非结构化数据,解决传统方法计算成本高、探索空间有限的问题。在工程实践中,深度学习已广泛应用于计算机视觉、自然语言处理等领域,并正深刻变革材料科学的研究范式。具体到材料领域,它通过学习已知材料的构效关系,实现对新材料性能的快速预测与逆向设计。应用场景包括但不限于新能源材料、半导体、催化剂等领域的高通量

weixin_28695161的博客 416

AF驱动调试日志全解析:从配置到问题排查

本文全面解析AF驱动调试日志的配置、抓取与分析方法。从内核驱动与HAL层配置入手,确保日志正常输出,并详细解读开机初始化、马达识别、I2C通信等关键日志节点。通过多个实战案例,如对焦失败、前摄无功能、参数不生效及I2C报错等,系统性地展示了如何利用日志定位并解决AF对焦问题,是摄像头模组开发与调试的实用指南。

weixin_28285943的博客 306

Java中Arrays.asList() 和 List.of()区别

Java中`Arrays.asList()` 和 `List.of()`区别

记录学习的一些知识,也希望这些记录总结能帮到需要的人! 3075

从选卡到验证:CST仿真GPU加速全流程实战指南

本文详细介绍了CST仿真中GPU加速的全流程实战指南,从硬件选型到软件配置,再到性能验证和常见问题排查。通过对比专业卡与游戏卡的性能差异,提供硬件选型建议,并分享驱动安装、关键设置及CST软件配置的黄金法则,帮助用户显著提升仿真效率。

weixin_30650039的博客 395

Java集合遍历引发的“血案“

(1)可能出现的问题一:出现并发修改异常(ConcurrentModificationException)上述示例出现ConcurrentModificationException异常的原因在于不能对list集合同时进行读写操作。(2)可能出现的情况二:下标/游标错位上述结果显然是有问题的,我们认为的正确结果应该是:012执行remove操作4那为什么没有打印4呢?

旅、途的博客 496

从示波器波形看懂死区:手把手教你调ST MCU的PWM,避免三相桥直通炸管

本文详细解析了如何通过示波器波形判断PWM死区时间,并手把手教你调整STM32高级定时器寄存器,避免三相逆变桥直通炸管。文章涵盖死区时间的本质与风险、STM32配置详解、示波器调试技巧及FOC系统中的特殊考量,帮助工程师优化电机驱动性能与安全性。

weixin_30315723的博客 735

【深圳】IBM V3500存储维修 更换控制器故障

今天给大家分享的是深圳.福田区一个电商客户公司一台IBM Storwize v3500存储控制器故障从远程诊断 收集日志 分析结果的实际案例,如图所示通过存储管理口登陆进去后直接看到少了一个控制器,经过工程师分析日志后判定为该控制器损坏,接下来更换控制器配件,标准套件的控制器不带子卡和电池,所以需要把故障控制器上的子卡和电池拆下来装到新控制器上,这个电池拆还需要点技巧,接着把新的控制器插回V3500主机,等待大约十分钟后,新控制器指示灯全绿,管理界面看到节点已联机;冠峰科技-爱科技爱分享。

OuYangYuXi的博客 4581

Java集合遍历引发的“血案”

一、List集合迭代方式遍历一 <1>、可能出现的问题一:出现并发修改异常(ConcurrentModificationException) importjava.util.ArrayList; importjava.util.Iterator; importjava...

chiningbei0229的博客 140

九齐NY8B062F单片机:12位ADC与4路PWM在智能家居控制中的实战应用

本文详细介绍了九齐NY8B062F单片机在智能家居控制中的实战应用,重点解析其12位ADC高精度传感器数据采集和4路PWM精准驱动技术。通过实际案例展示如何利用该单片机实现温湿度监测、灯光调节及电机控制,并分享低功耗设计与抗干扰方案,为低成本高可靠性的智能家居设备开发提供完整解决方案。

rnn9storyteller的博客 732

welcome-file-list 引发的血案

最近在学习Spring,做了个简单的MultiActionController,在-servlet.xml的文件中没有配置HandlerMapping,所以默认使用DefaultAnnotationHandlerMapping。 写了个简单的例子:    @RequestMapping("/")    public String welcomeHandler() {      

hqu_wxf的专栏 1256

【AI智能体】Claude Code +Superpowers 项目实战操作详解

Claude Code +Superpowers 项目实战操作详解

congge 4054

手把手教你学Simulink--人形机器人控制场景实例:基于Simulink的多连杆人形机器人拉格朗日动力学建模仿真

matlab编辑% 连杆长度 (m)% 躯干(髋到CoM)% 大腿% 小腿% 质量 (kg)% 转动惯量(绕质心,简化为杆)matlab编辑% 躯干CoM(假设在髋上方L_body/2处)% 左大腿CoM% 左小腿CoM% (右腿类似,略)本文完成了基于Simulink的多连杆人形机器人拉格朗日动力学建模仿真,实现了:✅ 定义7-DOF平面人形结构✅ 使用Symbolic Math Toolbox自动推导动力学方程✅ 生成。

MHD0815的博客 147

一个printf(结构体指针)引发的血案

一、前言 二、问题导入 三、分析问题的思路 四、C语言中的可变参数 五、总结 一、前言 1. 为什么写这篇文章 在上周六,我在公众号里发了一篇文章:C语言指针-从底层原理到花式技巧,用图文和代码帮你讲解透彻,以直白的语言、一目了然的图片来解释指针的底层逻辑,有一位小伙伴对文中的代码进行测试,发现一个比较奇怪的问题。我把发来的测试代码进行验证,思考好久也无法解释为什么会出现那么奇怪的打印结果。 为了整理思路,我到阳台抽根烟。晚上的风很大,一根烟我抽了一半,..

自由的天空 1053

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

记一次忽略@SuppressLint(“NewApi“)提示引发的血案

记一次忽略@SuppressLint(“NewApi”)提示引发的血案 本次开发中用到了Java中的优先级队列,需要根据给定的优先级对元素进行排序,于是我写出了下面这段代码: private Comparator<CustomObject> comparator = new Comparator<CustomObject>() { @Override public int compare(CustomObject o1, CustomObject o2) {

tinyVampire的博客 518

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Java集合遍历引发的"血案"

一、List集合迭代方式遍历一 <1>、可能出现的问题一:出现并发修改异常(ConcurrentModificationException) importjava.util.ArrayList; importjava.util.Iterator; imp...

chimao8460的博客 184

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

mybatis批量查询引发的血案

mybatis提供了foreach语法用于所谓的批量查询,使用方式如下: ①、定义接口 /** * 批量获取任务id列表对应的任务名称 * @param taskIdList:任务id列表 * @return :任务id,及对应的任务名称的列表信息 */ List<Map<String, Object>&...

weixin_30492047的博客 158

list mybatis批量保存_Mybatis 批量插入引发的血案

点击上方Java编程技术乐园,轻松关注!及时获取有趣有料的技术文章做一个积极的人编码、改bug、提升自己我有一个乐园,面向编程,春暖花开!故事今天下午公司技术分享,一个伙伴提到他踩过坑:mybatis批量插入时动态sql允许的最大参数数量是2100个。即下面代码中“#”的数量。 insert into Adv_permeability values (#{permeabili...

weixin_35719380的博客 366

一个Sqrt函数引发的血案

转自:http://blogread.cn/it/article/3299?f=wb 好吧,我承认我标题党了,不过既然你来了,就认真看下去吧,保证你有收获。     我们平时经常会有一些数据运算的操作,需要调用sqrt,exp,abs等函数,那么时候你有没有想过:这个些函数系统是如何实现的?就拿最常用的sqrt函数来说吧,系统怎么来实现这个经常调用的函数呢?     虽然有可能你

xiehaoyun2012的专栏 570

C语言新手必看:#define后面到底要不要加分号?一个分号引发的‘血案’与避坑指南

本文深入探讨了C语言中`#define`预处理指令后是否应该加分号的问题,通过实际代码示例展示了误加分号可能引发的各种编译错误和逻辑问题,并提供了最佳实践和现代C++的替代方案。文章特别强调了`#define`作为文本替换的本质,以及如何避免因分号使用不当导致的'血案',是C语言新手的必读避坑指南。

weixin_33749242的博客 349
上一篇: limit 1000,10 和 limit 10一样快吗?
下一篇: SpringBoot 3.2正式版,开箱即用的虚拟线程和GraalVM,尝鲜一下!
IT界那些事儿
博客等级 码龄6年 2万+粉丝 22原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值