Java集合使用规范详解:提升代码质量与开发效率的关键技巧
详解Java集合在工作中的规范使用和操作

更新时间:2022年11月29日 10:04:16 作者:Cappuccino-Haichen
本文主要详细介绍在工作中使用Java集合时需要注意的规范。文章中的示例代码进行了详细的解释,对我们使用Java集合会有帮助。如果您有兴趣,可以了解更多
目录
一、前言
现代软件行业的快速发展对开发人员的综合素质提出了越来越高的要求,因为不仅仅是编程知识点,其他维度的知识点都会影响软件最终的交付质量。例如:各种各样的错误代码会人为地增加排查问题的难度;数据库表结构、索引设计缺陷导致的系统架构缺陷或性能风险;项目结构混乱,导致后续项目维护困难;未经认证的易受攻击的代码容易被黑客攻击等。根据约束力的强弱和故障敏感度,规定分为【强制】、【推荐】、【参考】三类。扩展信息中,“解释”对协议进行适当的扩展和解释; “正面例子”提倡什么样的编码和实现方法; “反例”解释了要警惕的雷区,以及真实的错误案例。
现代软件架构的复杂性需要协作开发。如何高效协作?没有规则就没有圈子,没有标准就很难协调。例如,制定交通法规的目的是限制驾驶权,但实际上是为了保护公众的人身安全。试想一下,如果没有限速,没有红绿灯,谁还敢在马路上开车?对于软件来说,适当的规范和标准绝不是消除代码内容的创造性和优雅性,而是限制过度的个性化,以普遍认可的统一方式协同工作,提高协作效率,降低沟通成本。软件系统的血液在代码的行间流动。代码质量的提升就是尽可能避免陷阱,消除重复的陷阱,有效提高系统稳定性和代码质量。
2.规范Java集合的使用
1、【强制】关于hashCode和equals的处理,遵循以下规则:
1)只要覆盖了equals,就必须覆盖hashCode。
2)由于Set存储的对象是唯一的,并且是根据hashCode和equals进行判断的,所以Set中存储的对象必须重写这两个方法。
3)如果使用自定义对象作为Map键,则必须覆盖hashCode和equals。
注意:由于 String 重写了 hashCode 和 equals 方法,因此您可以愉快地使用 String 对象作为键。
2. 【强制】判断所有集合中的元素是否为空,请使用 isEmpty() 方法,而不是 size() == 0 方法。
注意:在某些集合中,前者的时间复杂度为O(1),可读性更强。
正面例子:
Mapmap = new HashMap<>(16); if (map.isEmpty()) { System.out.println("no element in this map."); }
3、【强制】使用java.util.stream.Collectors类的toMap()方法转换为Map集合时,一定要使用参数类型为BinaryOperator、参数名为mergeFunction的方法,否则会抛出异常出现相同的键。
IllegalStateException 异常。
注:参数mergeFunction的作用是自定义发生key重复时的值处理策略。
正面例子:
List> 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 map = pairArrayList.stream() .collect(Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
反例:
String[] departments = new String[]{"RDC", "RDC", "KKB"};
// 抛出 IllegalStateException 异常
Map map = Arrays.stream(departments)
.collect(Collectors.toMap(String::hashCode, str -> str));
4. 【强制】使用java.util.stream.Collectors类的toMap()方法转换为Map集合时,一定要注意值为null时会抛出NPE异常。
注意:java.util.HashMap的merge方法中会进行如下判断:
if (value == null || remappingFunction == null) throw new NullPointerException();
反例:
List> pairArrayList = new ArrayList<>(2); pairArrayList.add(new Pair<>("version1", 8.3)); pairArrayList.add(new Pair<>("version2", null)); // 抛出 NullPointerException 异常 Map map = pairArrayList.stream() .collect(Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
5. 【强制】ArrayList的subList结果不能转换为ArrayList,否则会抛出ClassCastException异常:java.util.RandomAccessSubList无法转换为java.util.ArrayList。
注意:subList()返回的是ArrayList的内部类SubList,不是ArrayList本身,而是ArrayList的一个视图。对SubList的所有操作最终都会反映在原列表上。还要注意数组越界异常。
6. 【强制】使用Map方法keySet()/values()/entrySet()返回集合对象时,不能向其中添加元素,否则会抛出UnsupportedOperationException异常。
7、【强制】Collections类返回的对象,如:emptyList()/singletonList()等,都是不可变列表,不能添加或删除元素。
反例:如果查询没有结果,则返回Collections.emptyList()的空集合对象。一旦调用者将元素添加到返回的集合中,就会触发 UnsupportedOperationException 异常。
8、【强制】在subList场景下,要密切关注父集合元素的添加或删除,在遍历、添加、删除子列表时会引发ConcurrentModificationException异常。
注:抽查显示,90%的程序员对这个知识点的理解是错误的。
9. 【强制】使用集合转数组的方法,必须使用集合的toArray(T[] array)。输入是一个相同类型、长度为0的空数组。
反例:直接使用toArray无参数方法有问题。该方法的返回值只能是Object[]类。如果强制转换其他类型的数组,则会出现 ClassCastException 错误。
正面例子:
Listlist = new ArrayList<>(2); list.add("guan"); list.add("bao"); String[] array = list.toArray(new String[0]);
注意:使用带参数的toArray方法,数组空间大小的长度:
1)等于0,动态创建相同大小的数组,性能最好。
2)大于0但小于size,重新创建一个大小等于size的数组,增加GC负担。
3) 大小相等。在高并发情况下,数组创建后,size变大,负面影响与2相同。
4)如果大于size,则浪费空间,并且在size处插入空值,造成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";列表中的元素也会被修改,反之亦然。
12.【强制】通用通配符