尽量避免删改List

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析

阶段4、深入jdk其余源码解析

阶段5、深入jvm源码解析

尽管在之前介绍了如何避免并发修改异常,但那篇文章的目的,更多的是为了介绍底层原理及应付面试,实际开发中并不推荐大家对原List做增删改操作。

我的观点是,对于一个初始化完毕的List,尽量把它当做只读的,不要贸然做增删改操作。比如Java8的Stream,它所有的操作都是基于新的List,并不会改变原数据,包括JDK、Google Common以及Apache Common等工具类提供的不可变集合(Immutable Collections),其实都是在传递这种思想(Google Common甚至直接屏蔽了增删改方法):

接下来,给大家分享两个实际开发中遇到的问题,都与List操作有关。

用skip()、limit()代替subList()

对于List的截取,可能大家都习惯用List.subList(),但它有个隐形的坑:对截取后的List进行元素修改,会影响原List(除非你就希望改变原List)。

究其原因,subList()并非真的从原List截取出元素,而是偏移原List的访问坐标罢了:

比如你要截取(5, 6),那么下次你get(index),我就直接返回5+index给你,看起来好像真的截取了。

另外,这个方法限制太大,用起来也麻烦,比如对于一个不确定长度的原List,如果你想做以下截取操作:list.subList(0, 5)或者list.subList(2, 5),当原List长度不满足List.size()>=5时,会抛异常。为了避免误操作,你必须先判断size:

if(list != null && list.size() >= 5) {return list.subList(2, 5);
}

较为简便和安全的做法是借助Stream(Stream一个很重要的特性是,不修改原数据,而是新产生一个流):

public static void main(String[] args) {List<String> list = Lists.newArrayList("a", "b", "c", "d");List<String> limit3 = list.stream().limit(3).collect(Collectors.toList());// 超出实际长度也不会报错List<String> limit5 = list.stream().limit(5).collect(Collectors.toList());List<String> range3_4 = list.stream().skip(2).limit(2).collect(Collectors.toList());// 超出实际长度也不会报错List<String> range3_5 = list.stream().skip(2).limit(3).collect(Collectors.toList());System.out.println(limit3 + " " + limit5 + " " + range3_4 + " " + range3_5);
}

用filter()代替remove()

很多同学对内存占用极其敏感,恨不得用同一份内存把A、B、C三件事都干了(特别是经历了LeetCode摧残的人)。这种想法是好的,但对于List这样有并发修改限制的容器来说,一不留神就有可能出现问题。举个例子:

假设后台要支持配置定向推广的商品,并且需要将配置的商品在当前时间轴置顶(比如09:00下)。原本时间轴的列表是AList,长度为10,而后台配置的商品为BList,长度不确定,在0~10之间。考虑到后台配置的商品可能与原List中的商品重复,所以这里要加一个去重操作。很多人可能会想到利用Set或者Map的key不重复的特性去重,但试了以后会发现顺序可能被打乱。那么,最直观的方法就是双层for遍历:先遍历原来的AList,然后拿着AList的item去BList遍历,如果这个item在BList中已经存在,就把这个item从AList删除。

public class ListRemoveTest {public static void main(String[] args) {// 前台ListList<Item> aList = Lists.newArrayList(new Item(1, "甲"),new Item(2, "乙"),new Item(3, "丙"));// 后台ListList<Item> bList = Lists.newArrayList(new Item(99, "对照数据"),new Item(3, "丙"));// 对前台List去重for (int i = aList.size() - 1; i >= 0; i--) {for (Item user : bList) {if (Objects.equals(user.getId(), aList.get(i).getId())) {aList.remove(i);}}}// 组合去重后的两个List,后台List置顶bList.addAll(aList);System.out.println(JSON.toJSONString(bList));}@Getter@Setter@AllArgsConstructorstatic class Item {private Integer id;private String title;}}

即使我对并发修改异常“了如指掌”,在实际开发时还是写出了上面的代码。最致命的是,上面的代码还不一定会出错!如果重复商品只有一个,且恰好出现在bList的末尾,上面的代码是不会报错的。如果我们将上面bList元素顺序对调,再次运行就会发生数组越界异常:

原因是,当bList重复的元素只有一个且恰好在末尾时,第二层for在执行aList.remove()以后就直接退出第二层for,不会继续执行if逻辑,也就不会执行aList.get(i),所以不会发生数组越界(可能比较难理解,大家可以复制代码实际观察一下)。

当初虽然考虑到并发修改异常的可能,但不巧的是构造测试数据时只构造了一个重复的商品,而且排序系数设置为最高,恰好处于bList的末尾,完美地避开了问题...实际上线几天后的某个早晨,运营配置了多个商品,而且恰好重复了,于是首页直接崩了...这是一个很严重的事故。

一个可行的处理方式是:

public static void main(String[] args) {// 前台ListList<Item> aList = Lists.newArrayList(new Item(1, "甲"),new Item(2, "乙"),new Item(3, "丙"));// 后台ListList<Item> bList = Lists.newArrayList(new Item(3, "丙"),new Item(99, "对照数据"));// 对aList进行筛选(bList中不存在的item)Map<Integer, Item> bItemMap = bList.stream().collect(Collectors.toMap(Item::getId, v -> v, (v1, v2) -> v1));List<Item> filteredAList = aList.stream().filter(aItem -> !bItemMap.containsKey(aItem.getId())).collect(Collectors.toList());// 组合去重后的两个List,后台List置顶bList.addAll(filteredAList);System.out.println(JSON.toJSONString(bList));
}

当然,List本身提供了诸如allAll()、retainAll()、removeAll()等操作,可以很方便的实现并集、交集、差集。所以,上面的去重取并集可以这样:

public class ListRemoveTest {public static void main(String[] args) {// 前台ListList<Item> aList = Lists.newArrayList(new Item(1, "甲"),new Item(2, "乙"),new Item(3, "丙"));// 后台ListList<Item> bList = Lists.newArrayList(new Item(3, "丙"),new Item(99, "对照数据"));// 先去重,再合并aList.removeAll(bList);bList.addAll(aList);System.out.println(JSON.toJSONString(bList));}@Getter@Setter@AllArgsConstructor@EqualsAndHashCode // 注意,这里要重写equals和hash,否则默认比较地址值static class Item {private Integer id;private String title;}}

说了这么多,就是想强调,无论是并发修改异常还是数组越界,通常情况下都不会发生,但当你企图对原List进行增删改操作时,只要没考虑周全,就有极大概率发生。由于Stream的任何操作都不会改变原数据,所以从根源上杜绝了增删改可能隐藏的问题,是比较安全的方式,也推荐大家多使用Stream,无论从代码可读性还是健壮性来说,都会好很多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/227538.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【代码随想录】刷题笔记Day42

前言 这两天机器狗终于搞定了&#xff0c;一个控制ROS大佬&#xff0c;一个计院编程大佬&#xff0c;竟然真把创新点这个弄出来了&#xff0c;牛牛牛牛&#xff08;菜鸡我只能负责在旁边喊加油&#xff09;。下午翘了自辩课来刷题&#xff0c;这次应该是元旦前最后一刷了&…

苹果CMS超级播放器专业版无授权全开源,附带安装教程

源码介绍 超级播放器专业版v1.0.8&#xff0c;内置六大主流播放器&#xff0c;支持各种格式的视频播放&#xff0c;支持主要功能在每一个播放器内核中都相同效果。 搭建教程 1.不兼容IE浏览器 2.php版本推荐7.4 支持7.1~7.4 3.框架引入不支持同时引入多个播放器 json对接教…

搭建maven私服

maven maven简介 什么是maven&#xff1f; Maven这个单词来自于意第绪语&#xff08;犹太语&#xff09;&#xff0c;意为知识的积累。 Maven项目对象模型(POM)&#xff0c;可以通过一小段描述信息来管理项目的构建&#xff0c;报告和文档的项目管理工具软件。 Maven 除了以…

数据结构与算法 - 查找

文章目录 第1关&#xff1a;实现折半查找第2关&#xff1a;实现散列查找 第1关&#xff1a;实现折半查找 代码如下&#xff1a; /*************************************************************date: April 2009copyright: Zhu EnDO NOT distribute this code. ***********…

记录一下imx6ull linux 5.10.9多点电容触摸屏驱动报错问题解决方法

最近再研究如何将linux 5.10.9移植到imx6ull&#xff0c;用的原子的开发板&#xff0c;在移植电容触摸屏驱动时报错gpio gpiochip0: (209c000.gpio): gpiochip_lock_as_irq: tried to flag a GPIO set as output for IRQ&#xff0c;如下图&#xff1a; 该错误的意思就是尝试将…

Flink1.17实战教程(第三篇:时间和窗口)

系列文章目录 Flink1.17实战教程&#xff08;第一篇&#xff1a;概念、部署、架构&#xff09; Flink1.17实战教程&#xff08;第二篇&#xff1a;DataStream API&#xff09; Flink1.17实战教程&#xff08;第三篇&#xff1a;时间和窗口&#xff09; Flink1.17实战教程&…

[足式机器人]Part2 Dr. CAN学习笔记-Ch00 - 数学知识基础

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-Ch00 - 数学知识基础 1. Ch0-1矩阵的导数运算1.1标量向量方程对向量求导&#xff0c;分母布局&#xff0c;分子布局1.1.1 标量方程对向量的导数1.1.2 向量方程对向量的导数 1.2 案例分析&#xf…

Java项目:101SpringBoot仓库管理系统

博主主页&#xff1a;Java旅途 简介&#xff1a;分享计算机知识、学习路线、系统源码及教程 文末获取源码 一、项目介绍 仓库管理系统基于SpringBootMybatis开发&#xff0c;系统使用shiro框架做权限安全控制&#xff0c;超级管理员登录系统后可根据自己的实际需求配角色&…

项目中使用Java中List.subList()的注意事项

使用介绍 在Java中&#xff0c;subList是List接口的一个方法&#xff0c;用于获取原始列表的子列表 方法的声明如下 List<E> subList(int fromIndex, int toIndex);fromIndex&#xff1a;起始索引&#xff08;包括&#xff09;toIndex&#xff1a;结束索引&#xff08…

SpringMVC学习与开发(四)

注&#xff1a;此为笔者学习狂神说SpringMVC的笔记&#xff0c;其中包含个人的笔记和理解&#xff0c;仅做学习笔记之用&#xff0c;更多详细资讯请出门左拐B站&#xff1a;狂神说!!! 11、Ajax初体验 1、伪造Ajax 结果&#xff1a;并未有xhr异步请求 <!DOCTYPE html> &…

NXP实战笔记(一):基于RTD-SDK新建一个S32DS工程

目录 1、概述 2、操作步骤 2.1、新建Application工程 2.2、命名工程、选择芯片型号、选择编译器GCC版本 2.3、配置基本参数 3、文件描述 3.1、文件结构描述 3.2、编译之后 4、下载调试 1、概述 安装了S32DS之后&#xff0c;导入SDK插件&#xff0c;这个步骤不赘述&…

R统计学1 - 基础操作入门问题1-20

R统计学入门基础问题 1. 如何生成100个高斯&#xff08;正态&#xff09;分布随机数 x <- rnorm(100, mean 5, sd 0.1) x # [1] 4.893534 5.046611 5.081097 4.979164 5.181700 5.038192 5.135376 5.173346 4.968877 4.986146 # [11] 4.946258 5.198199 5.055531 4.9430…

LSTM中文新闻分类源码详解

LSTM中文新闻分类 一、导包二、读取数据三、数据预处理1.分词、去掉停用词和数字、字母转换成小写等2.新闻文本标签数值化 三、创建词汇表/词典1.data.Field()2.空格切分等3.构建词汇表/词典使用训练集构建单词表&#xff0c;vectorsNone:没有使用预训练好的词向量,而是使用的是…

【机器学习合集】深度生成模型 ->(个人学习记录笔记)

深度生成模型 深度生成模型基础 1. 监督学习与无监督学习 1.1 监督学习 定义 在真值标签Y的指导下&#xff0c;学习一个映射函数F&#xff0c;使得F(X)Y 判别模型 Discriminative Model&#xff0c;即判别式模型&#xff0c;又称为条件模型&#xff0c;或条件概率模型 生…

swift-碰到的问题

如何让工程不使用storyboard和scene 删除info.plist里面的Application Scene mainifest 删除SceneDelegate.swift 删除AppDelegate.swift里面的这两个方法 func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession…

[C#]opencvsharp进行图像拼接普通拼接stitch算法拼接

介绍&#xff1a; opencvsharp进行图像拼一般有2种方式&#xff1a;一种是传统方法将2个图片上下或者左右拼接&#xff0c;还有一个方法就是融合拼接&#xff0c;stitch拼接就是一种非常好的算法。opencv里面已经有stitch拼接算法因此我们很容易进行拼接。 效果&#xff1a; …

十三:爬虫-Scrapy框架(下)

一&#xff1a;各文件的使用回顾 1.items的使用 items 文件主要用于定义储存爬取到的数据的数据结构&#xff0c;方便在爬虫和 Item Pipeline 之间传递数据。 items.pyimport scrapyclass TencentItem(scrapy.Item):# define the fields for your item here like:title scr…

[mysql 基于C++实现数据库连接池 连接池的使用] 持续更新中

目背景 常见的MySQL、Oracle、SQLServer等数据库都是基于C/S架构设计的&#xff0c;即&#xff08;客户端/服务器&#xff09;架构&#xff0c;也就是说我们对数据库的操作相当于一个客户端&#xff0c;这个客户端使用既定的API把SQL语句通过网络发送给服务器端&#xff0c;MyS…

Animate 2024(Adobe an2024)

Animate 2024是一款由Adobe公司开发的动画和互动内容创作工具&#xff0c;是Flash的演进版本。Animate 2024为设计师和开发者提供了更丰富的功能&#xff0c;让他们能够创建各种类型的动画、交互式内容和多媒体应用程序。 Animate 2024具有以下特点&#xff1a; 强大的设计工…

一个静态网站可以增加什么第三方功能/服务

一个静态网站&#xff0c;无后台功能&#xff0c;怎么增加一些实用功能呢&#xff1f;我们来看看一些免费的第三方服务。 静态页面寄存 Gitee pages/Github pages 都可以&#xff0c;绑定一个域名&#xff0c;版本一提交&#xff0c;直接发布有效果。 评论 每个 URL 页面都…