工作中Java集合的规范使用操作详解

目录
  • 一、前言
  • 二、规范使用Java集合

一、前言

现代软件行业的高速发展对开发者的综合素质要求越来越高,因为不仅是编程知识点,其它维度的知识点也会影响到软件的最终交付质量。比如:五花八门的错误码会人为地增加排查问题的难度;数据库的表结构和索引设计缺陷带来的系统架构缺陷或性能风险;工程结构混乱导致后续项目维护艰难;没有鉴权的漏洞代码容易被黑客攻击等。依据约束力强弱及故障敏感性,规约依次分为【强制】、【推荐】、【参考】三大类。在延伸的信息中,“说明”对规约做了适当扩展和解释;“正例”提倡什么样的编码和实现方式;“反例”说明需要提防的雷区,以及真实的错误案例。

现代软件架构的复杂性需要协同开发完成,如何高效地协同呢?无规矩不成方圆,无规范难以协同,比如,制定交通法规表面上是要限制行车权,实际上是保障公众的人身安全,试想如果没有限速,没有红绿灯,谁还敢上路行驶?对软件来说,适当的规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率,降低沟通成本。代码的字里行间流淌的是软件系统的血液,代码质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量。

二、规范使用Java集合

1.【强制】关于 hashCode 和 equals 的处理,遵循如下规则:

1)只要覆写 equals,就必须覆写 hashCode。

2)因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须覆写这两种方法。

3)如果自定义对象作为 Map 的键,那么必须覆写 hashCode 和 equals。

说明:String 因为覆写了 hashCode 和 equals 方法,所以可以愉快地将 String 对象作为 key 来使用。

2.【强制】判断所有集合内部的元素是否为空,使用 isEmpty() 方法,而不是 size() == 0 的方式。

说明:在某些集合中,前者的时间复杂度为 O(1),而且可读性更好。

正例:

Map<String, Object> map = new HashMap<>(16);
if (map.isEmpty()) {
System.out.println("no element in this map.");
}

3.【强制】在使用 java.util.stream.Collectors 类的 toMap() 方法转为 Map 集合时,一定要使用参数类型为 BinaryOperator,参数名为 mergeFunction 的方法,否则当出现相同 key 时会抛出

IllegalStateException 异常。

说明:参数 mergeFunction 的作用是当出现 key 重复时,自定义对 value 的处理策略。

正例:

List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);
pairArrayList.add(new Pair<>("version", 12.10));
pairArrayList.add(new Pair<>("version", 12.19));
pairArrayList.add(new Pair<>("version", 6.28));
// 生成的 map 集合中只有一个键值对:{version=6.28}
Map<String, Double> map = pairArrayList.stream()
.collect(Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));

反例:

String[] departments = new String[]{"RDC", "RDC", "KKB"};
// 抛出 IllegalStateException 异常
Map<Integer, String> map = Arrays.stream(departments)
.collect(Collectors.toMap(String::hashCode, str -> str));

4.【强制】在使用 java.util.stream.Collectors 类的 toMap() 方法转为 Map 集合时,一定要注意当 value为 null 时会抛 NPE 异常。

说明:在 java.util.HashMap 的 merge 方法里会进行如下的判断:

if (value == null || remappingFunction == null)
throw new NullPointerException();

反例:

List<Pair<String, Double>> pairArrayList = new ArrayList<>(2);
pairArrayList.add(new Pair<>("version1", 8.3));
pairArrayList.add(new Pair<>("version2", null));
// 抛出 NullPointerException 异常
Map<String, Double> map = pairArrayList.stream()
.collect(Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));

5.【强制】ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException 异常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。

说明:subList() 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 本身,而是 ArrayList 的一个视图,对于SubList 的所有操作最终会反映到原列表上。同时注意数组越界异常。

6.【强制】使用 Map 的方法 keySet() / values() / entrySet() 返回集合对象时,不可以对其进行添加元素操作,否则会抛出 UnsupportedOperationException 异常。

7.【强制】Collections 类返回的对象,如:emptyList() / singletonList() 等都是 immutable list,不可对其进行添加或者删除元素的操作。

反例:如果查询无结果,返回 Collections.emptyList() 空集合对象,调用方一旦在返回的集合中进行了添加元素的操作,就会触发 UnsupportedOperationException 异常。

8.【强制】在 subList 场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生 ConcurrentModificationException 异常。

说明:抽查表明,90% 的程序员对此知识点都有错误的认知。

9.【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为0 的空数组。

反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现ClassCastException 错误。

正例:

List<String> list = new ArrayList<>(2);
list.add("guan");
list.add("bao");
String[] array = list.toArray(new String[0]);

说明:使用 toArray 带参方法,数组空间大小的 length:

1)等于 0,动态创建与 size 相同的数组,性能最好。

2)大于 0 但小于 size,重新创建大小等于 size 的数组,增加 GC 负担。

3)等于 size,在高并发情况下,数组创建完成之后,size 正在变大的情况下,负面影响与 2 相同。

4)大于 size,空间浪费,且在 size 处插入 null 值,存在 NPE 隐患。

10.【强制】使用 Collection 接口任何实现类的 addAll() 方法时,要对输入的集合参数进行 NPE 判断。

说明:在 ArrayList#addAll 方法的第一行代码即 Object[] a = c.toArray();其中 c 为输入集合参数,如果为 null,则直接抛出异常。

11.【强制】使用工具类 Arrays.asList() 把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/ remove / clear 方法会抛出 UnsupportedOperationException 异常。

说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适配器模式,只是转换接口,后台的数据仍是数组。

String[] str = new String[]{ "yang", "guan", "bao" };
List list = Arrays.asList(str);

第一种情况:list.add("yangguanbao"); 运行时异常。

第二种情况:str[0] = "change"; list 中的元素也会随之修改,反之亦然。

12.【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方法,而<? super T>不能使用 get 方法,两者在接口调用赋值的场景中容易出错。

说明:扩展说一下 PECS(Producer Extends Consumer Super) 原则,即频繁往外读取内容的,适合用<? extends T>,经常往里插入的,适合用<? super T>

13.【强制】在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行instanceof 判断,避免抛出 ClassCastException 异常。

说明:毕竟泛型是在 JDK5 后才出现,考虑到向前兼容,编译器是允许非泛型集合与泛型集合互相赋值。

反例:

List<String> generics = null;
List notGenerics = new ArrayList(10);
notGenerics.add(new Object());
notGenerics.add(new Integer(1));
generics = notGenerics;
// 此处抛出 ClassCastException 异常
String string = generics.get(0);

14.【强制】不要在 foreach 循环里进行元素的 remove / add 操作。remove 元素请使用 iterator 方式,如果并发操作,需要对 iterator 对象加锁。

正例:

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (删除元素的条件) {
iterator.remove();
}
}

反例:

for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}

说明:反例中的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”会是同样的结果吗?

15.【强制】在 JDK7 版本及以上,Comparator 实现类要满足如下三个条件,不然 Arrays.sort,Collections.sort 会抛 IllegalArgumentException 异常。

说明:三个条件如下

1)x,y 的比较结果和 y,x 的比较结果相反。

2)x > y,y > z,则 x > z。

3)x = y,则 x,z 比较结果和 y,z 比较结果相同。

反例:下例中没有处理相等的情况,交换两个对象判断结果并不互反,不符合第一个条件,在实际使用中可能会出现异常。

new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
}
};

16.【推荐】泛型集合使用时,在 JDK7 及以上,使用 diamond 语法或全省略。

说明:菱形泛型,即 diamond,直接使用<>来指代前边已经指定的类型。

正例:

// diamond 方式,即<>
HashMap<String, String> userCache = new HashMap<>(16);
// 全省略方式
ArrayList<User> users = new ArrayList(10);

17.【推荐】集合初始化时,指定集合初始值大小。

说明:HashMap 使用构造方法 HashMap(int initialCapacity) 进行初始化时,如果暂时无法确定集合大小,那么指定默认值(16)即可。

正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loaderfactor)默认为 0.75,如果暂时无法确定初始值大小,请设置为 16(即默认值)。

反例:HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素增加而不断扩容,resize() 方法总共会调用 8 次,反复重建哈希表和数据迁移。当放置的集合元素个数达千万级时会影响程序性能。

18.【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。

说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的 value。而entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.forEach 方法。

正例:values() 返回的是 V 值集合,是一个 list 集合对象;keySet() 返回的是 K 值集合,是一个 Set 集合对象;entrySet() 返回的是 K-V 值组合的 Set 集合。

19、【推荐】高度注意 Map 类集合 K / V 能不能存储 null 值的情况,如下表格:

反例: 由于 HashMap 的干扰,很多人认为 ConcurrentHashMap 是可以置入 null 值,而事实上,存储 null 值时会抛 出 NPE 异常。

20.【参考】合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。

说明:有序性是指遍历的结果是按某种比较规则依次排列的,稳定性指集合每次遍历的元素次序是一定的。如:ArrayList 是 order / unsort;HashMap 是 unorder / unsort;TreeSet 是 order / sort。

21.【参考】利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的contains() 进行遍历去重或者判断包含操作。

到此这篇关于工作中Java集合的规范使用操作详解的文章就介绍到这了,更多相关Java集合内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java Map集合使用方法全面梳理

    目录 Map接口 Map的常用方法 HashMap集合 Properties集合 Map接口 Map接口是双列集合,它的每一个元素都包含一个键对象key和值对象Value,键和值对象之间存在一种对应关系,称为映射,从Map集合中访问元素时,只要指定了Key,就能找到对应的Value. Map的常用方法 void put(Object key,Object value)//将指定的值与映射中的指定键关联     Object get(Object key)//返回指定键所映射的值,如果此映射不包含

  • Java集合类之Map集合的特点及使用详解

    目录 1.Map集合概述和使用 1.1 Map集合概述 1.2 Map集合常用方法 1.3 Map集合的遍历 1.Map集合概述和使用 1.1 Map集合概述 java.util接口 Map<K,V> 类型参数: K - 此映射所维护的键的类型 V - 映射值的类型 public interface Map<K,V> 将键映射到值的对象.一个映射不能包含重复的键:每个键最多只能映射到一个值. 此接口取代 Dictionary 类,后者完全是一个抽象类,而不是一个接口. Map 是一

  • Java十分钟精通集合的使用与原理下篇

    List集合: ArrayList: 底层是数组结构,储存有序并且可以重复的对象 package SetTest; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ArrayListTest { public static void main(String[] args) { //创建ArrayList的对象 List<Integer> list = ne

  • 工作中Java集合的规范使用操作详解

    目录 一.前言 二.规范使用Java集合 一.前言 现代软件行业的高速发展对开发者的综合素质要求越来越高,因为不仅是编程知识点,其它维度的知识点也会影响到软件的最终交付质量.比如:五花八门的错误码会人为地增加排查问题的难度:数据库的表结构和索引设计缺陷带来的系统架构缺陷或性能风险:工程结构混乱导致后续项目维护艰难:没有鉴权的漏洞代码容易被黑客攻击等.依据约束力强弱及故障敏感性,规约依次分为[强制].[推荐].[参考]三大类.在延伸的信息中,“说明”对规约做了适当扩展和解释:“正例”提倡什么样的编

  • java集合框架线程同步代码详解

    List接口的大小可变数组的实现.实现了所有可选列表操作,并允许包括null在内的所有元素.除了实现List接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小.(此类大致上等同于Vector类,除了此类是不同步的.)size.isEmpty.get.set.iterator和listIterator操作都以固定时间运行.add操作以分摊的固定时间运行,也就是说,添加n个元素需要O(n)时间.其他所有操作都以线性时间运行(大体上讲).与用于LinkedList实现的常数因子相比,此实现的

  • Java switch多值匹配操作详解

    这篇文章主要介绍了Java switch多值匹配操作详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 我们都知道 switch 用来走流程分支,大多情况下用来匹配单个值,如下面的例子所示: /** * @author 栈长 */ private static void test(int value) { switch (value) { case 1: System.out.println("1"); break; case 2:

  • Java实现截取字符串的操作详解

    目录 使用JDK截断一个字符串 使用 String 的 substring() 方法 使用 String 的 split() 方法 使用 Pattern 类 使用 CharSequence 的 codePoints() 方法 Apache Commons 库 使用 StringUtils的left() 方法 使用 StringUtils 的 truncate() 方法 Guava库 总结 大家好,我是指北君. 在本文中,我们将学习在Java中把一个String截断到所需的字符数的集中方法. 首先

  • Java集合删除元素ArrayList实例详解

    Java集合删除元素ArrayList实例详解 AbstractCollection集合类中有一个remove方法,该方法为了适配多种不同的集合,允许删除空的元素,看这部分代码的时候产生了疑问,为什么这里直接用it.remove()就直接删除了? public boolean remove(Object o) { Iterator<E> it = iterator(); if (o==null) { while (it.hasNext()) { if (it.next()==null) { i

  • Java实现Excel导入导出操作详解

    目录 前言 1. 功能测试 1.1 测试准备 1.2 数据导入 1.2.1 导入解析为JSON 1.2.2 导入解析为对象(基础) 1.2.3 导入解析为对象(字段自动映射) 1.2.4 导入解析为对象(获取行号) 1.2.5 导入解析为对象(获取原始数据) 1.2.6 导入解析为对象(获取错误提示) 1.2.7 导入解析为对象(限制字段长度) 1.2.8 导入解析为对象(必填字段验证) 1.2.9 导入解析为对象(数据唯一性验证) 1.3 数据导出 1.3.1 动态导出(基础) 1.3.2 动

  • Java Optional的判空操作详解

    目录 Optional判空 代码模拟 Optional常用方法 使用Optional一定比null好吗 Optional 使用场景 Optional判空 JAVA在1.8版本推出Optional,官方文档将其描述为可能包含或不包含非空值的容器对象,目前Optional用于避免程序出现异常NullPointerException. 代码模拟 // 下面所有类省略set,get方法 public class Employee { private String employeeName; privat

  • 对tensorflow中cifar-10文档的Read操作详解

    前言 在tensorflow的官方文档中得卷积神经网络一章,有一个使用cifar-10图片数据集的实验,搭建卷积神经网络倒不难,但是那个cifar10_input文件着实让我费了一番心思.配合着官方文档也算看的七七八八,但是中间还是有一些不太明白,不明白的mark一下,这次记下一些已经明白的. 研究 cifar10_input.py文件的read操作,主要的就是下面的代码: if not eval_data: filenames = [os.path.join(data_dir, 'data_b

  • QT中对Mat类的一些操作详解

    目录 一.类型转换 二.保存至数据库 一.类型转换 opencv在QT中的应用通常会涉及到这三者的转换,即Mat.QImage.QPixmap.下面分别给出了 Mat转QImage QImage转Mat Mat转QPixmap 1️⃣:Mat转QImage QImage MainWindow::MatToImage(const Mat &m) //Mat转Image { switch(m.type()) { case CV_8UC1: { QImage img((uchar *)m.data,m

  • Java实现顺序表的操作详解

    目录 一.顺序表是什么 二.自定义异常 空引用异常 下标越界异常 三.顺序表的方法 顺序表的实现 获取顺序表长度 顺序表是否为空 顺序表是否为满 打印顺序表 末尾新增元素 指定位置新增元素 判断是否包含某元素 查找某个元素对应的位置 获取 pos 位置的元素 给 pos 位置的元素赋值 删除第一次出现的关键字key 清空顺序表 四.自定义顺序表 一.顺序表是什么 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储.在数组上完成数据的增删查改. 数组不就是一个现

随机推荐