当前位置: 首页 > article >正文

Alibaba开发规范_编程规约之集合框架:最佳实践与常见陷阱

文章目录

  • 引言
  • 1. hashCode与equals方法的覆写
    • 1.1 规则
    • 1.2 解释
    • 1.3 代码示例
      • 正例
      • 反例
  • 2. ArrayList的subList方法
    • 2.1 规则
    • 2.2 解释
    • 2.3 代码示例
      • 正例
      • 反例
  • 3. Map的keySet、values和entrySet方法
    • 3.1 规则
    • 3.2 解释
    • 3.3 代码示例
      • 正例
      • 反例
  • 4. Collections类返回的不可变集合
    • 4.1 规则
    • 4.2 解释
    • 4.3 代码示例
      • 正例
      • 反例
  • 5. subList场景中的并发修改异常
    • 5.1 规则
    • 5.2 解释
    • 5.3 代码示例
      • 正例
      • 反例
  • 6. 集合转数组的方法
    • 6.1 规则
    • 6.2 解释
    • 6.3 代码示例
      • 正例
      • 反例
  • 7. addAll方法的NPE判断
    • 7.1 规则
    • 7.2 解释
    • 7.3 代码示例
      • 正例
      • 反例
  • 8. Arrays.asList方法的限制
    • 8.1 规则
    • 8.2 解释
    • 8.3 代码示例
      • 正例
      • 反例
  • 9. 泛型通配符的使用
    • 9.1 规则
    • 9.2 解释
    • 9.3 代码示例
      • 正例
      • 反例
  • 10. 泛型集合的赋值与类型检查
    • 10.1 规则
    • 10.2 解释
    • 10.3 代码示例
      • 正例
      • 反例
  • 11. foreach循环中的remove/add操作
    • 11.1 规则
    • 11.2 解释
    • 11.3 代码示例
      • 正例
      • 反例
  • 12. Comparator实现类的条件
    • 12.1 规则
    • 12.2 解释
    • 12.3 代码示例
      • 正例
      • 反例
  • 13. 泛型定义的diamond语法
    • 13.1 规则
    • 13.2 解释
    • 13.3 代码示例
      • 正例
      • 反例
  • 14. 集合初始化时指定初始值大小
    • 14.1 规则
    • 14.2 解释
    • 14.3 代码示例
      • 正例
      • 反例
  • 15. 使用entrySet遍历Map
    • 15.1 规则
    • 15.2 解释
    • 15.3 代码示例
      • 正例
      • 反例
  • 16. Map类集合的K/V存储null值
    • 16.1 规则
    • 16.2 解释
    • 16.3 代码示例
      • 正例
      • 反例
  • 17. 集合的有序性与稳定性
    • 17.1 规则
    • 17.2 解释
    • 17.3 代码示例
      • 正例
      • 反例
  • 18. 使用Set进行去重操作
    • 18.1 规则
    • 18.2 解释
    • 18.3 代码示例
      • 正例
      • 反例
  • 小结

在这里插入图片描述

引言

Java集合框架是Java编程中最常用的工具之一,它提供了丰富的接口和类来存储、操作和处理数据集合。然而,由于集合框架的复杂性和灵活性,在实际使用中常常会遇到一些陷阱和问题。

1. hashCode与equals方法的覆写

1.1 规则

  • 覆写equals方法时,必须覆写hashCode方法
  • Set存储的对象必须覆写hashCode和equals方法,因为Set依赖于这两个方法来判断对象的唯一性。
  • 自定义对象作为Map的键时,必须覆写hashCode和equals方法

1.2 解释

hashCodeequals方法是Java中用于对象比较的两个重要方法。equals方法用于判断两个对象是否相等,而hashCode方法则用于生成对象的哈希码。在集合框架中,hashCode方法主要用于散列表(如HashMapHashSet)中快速定位对象。

哈希碰撞是不可避免的,因为输入空间远远大于输出空间。例如,256位的哈希值,输出空间只有2的256次方。根据鸽笼原理,哈希碰撞是必然存在的。

鸽笼原理(抽屉原理)就是"如果有五个鸽子笼,养鸽人养了6只鸽子,那么当鸽子飞回笼中后,至少有一个笼子中装有2只鸽子。"


1.3 代码示例

正例

public class User {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age && Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

反例

public class User {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age && Objects.equals(name, user.name);
    }

    // 未覆写hashCode方法
}

在反例中,User类只覆写了equals方法,但没有覆写hashCode方法。这将导致在使用HashSetHashMap时,可能会出现无法正确识别对象唯一性的问题。


2. ArrayList的subList方法

2.1 规则

  • ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常。
  • subList返回的是ArrayList的内部类SubList,它是原列表的一个视图,对子列表的所有操作会反映到原列表上。

2.2 解释

subList方法返回的是一个视图,而不是一个新的ArrayList。这个视图是基于原列表的,因此对子列表的修改会直接影响原列表。

2.3 代码示例

正例

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> subList = list.subList(1, 3);
subList.set(0, 99);
System.out.println(list); // 输出: [1, 99, 3, 4, 5]

反例

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
ArrayList<Integer> subList = (ArrayList<Integer>) list.subList(1, 3); // 抛出ClassCastException

在反例中,尝试将subList的结果强制转换为ArrayList,这将导致ClassCastException异常。


3. Map的keySet、values和entrySet方法

3.1 规则

  • 使用Map的keySet()、values()、entrySet()返回的集合对象时,不可对其进行添加元素操作,否则会抛出UnsupportedOperationException异常。

3.2 解释

keySet()values()entrySet()返回的集合是Map的视图,它们不允许直接添加元素。因为这些视图是基于Map的,添加元素会导致Map的结构发生变化,从而引发异常。

3.3 代码示例

正例

Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);

Set<String> keys = map.keySet();
for (String key : keys) {
    System.out.println(key);
}

反例

Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);

Set<String> keys = map.keySet();
keys.add("c"); // 抛出UnsupportedOperationException

在反例中,尝试向keySet返回的集合中添加元素,这将导致UnsupportedOperationException异常。


4. Collections类返回的不可变集合

4.1 规则

  • Collections类返回的对象,如emptyList()、singletonList()等是不可变的,不可对其进行添加或删除元素的操作。

4.2 解释

Collections类提供了一些静态方法来返回不可变的集合对象。这些集合对象在创建后不允许修改,任何尝试修改的操作都会抛出UnsupportedOperationException异常。

4.3 代码示例

正例

List<String> emptyList = Collections.emptyList();
System.out.println(emptyList.size()); // 输出: 0

反例

List<String> emptyList = Collections.emptyList();
emptyList.add("a"); // 抛出UnsupportedOperationException

在反例中,尝试向emptyList中添加元素,这将导致UnsupportedOperationException异常。


5. subList场景中的并发修改异常

5.1 规则

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

5.2 解释

subList返回的视图是基于原列表的,如果原列表在子列表操作期间被修改(如增加或删除元素),子列表的遍历或修改操作可能会抛出ConcurrentModificationException异常。

5.3 代码示例

正例

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> subList = list.subList(1, 3);
for (Integer i : subList) {
    System.out.println(i);
}

反例

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> subList = list.subList(1, 3);
list.remove(0); // 修改原列表
for (Integer i : subList) {
    System.out.println(i); // 抛出ConcurrentModificationException
}

在反例中,原列表在子列表遍历期间被修改,导致ConcurrentModificationException异常。


6. 集合转数组的方法

6.1 规则

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

6.2 解释

toArray(T[] array)方法允许将集合转换为指定类型的数组。传入长度为0的空数组可以确保动态创建与集合大小相同的数组,性能最好。

6.3 代码示例

正例

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
String[] array = list.toArray(new String[0]);
System.out.println(Arrays.toString(array)); // 输出: [a, b, c]

反例

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
String[] array = (String[]) list.toArray(); // 抛出ClassCastException

在反例中,直接使用无参toArray方法返回的是Object[]类型,强制转换为String[]类型会导致ClassCastException异常。


7. addAll方法的NPE判断

7.1 规则

  • 在使用Collection接口任何实现类的addAll()方法时,都要对输入的集合参数进行NPE判断

7.2 解释

addAll方法会将输入集合中的所有元素添加到当前集合中。如果输入集合为null,则会抛出NullPointerException异常。

7.3 代码示例

正例

List<String> list1 = new ArrayList<>(Arrays.asList("a", "b"));
List<String> list2 = new ArrayList<>(Arrays.asList("c", "d"));
list1.addAll(list2);
System.out.println(list1); // 输出: [a, b, c, d]

反例

List<String> list1 = new ArrayList<>(Arrays.asList("a", "b"));
List<String> list2 = null;
list1.addAll(list2); // 抛出NullPointerException

在反例中,list2null,调用addAll方法时会抛出NullPointerException异常。


8. Arrays.asList方法的限制

8.1 规则

  • 使用Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,如addremoveclear,否则会抛出UnsupportedOperationException异常。

8.2 解释

Arrays.asList返回的集合是一个固定大小的列表,它不支持添加或删除元素的操作。这是因为Arrays.asList返回的列表是基于原始数组的,数组的大小是固定的。

8.3 代码示例

正例

String[] array = {"a", "b", "c"};
List<String> list = Arrays.asList(array);
System.out.println(list); // 输出: [a, b, c]

反例

String[] array = {"a", "b", "c"};
List<String> list = Arrays.asList(array);
list.add("d"); // 抛出UnsupportedOperationException

在反例中,尝试向Arrays.asList返回的列表中添加元素,这将导致UnsupportedOperationException异常。


9. 泛型通配符的使用

9.1 规则

  • 泛型通配符<? extends T>来接收返回的数据时,此写法的泛型集合不能使用add方法
  • 泛型通配符<? super T>不能使用get方法

9.2 解释

<? extends T>表示泛型类型是TT的子类,这种类型的集合只能读取数据,不能写入数据。<? super T>表示泛型类型是TT的父类,这种类型的集合只能写入数据,不能读取数据。

9.3 代码示例

正例

List<? extends Number> numbers = new ArrayList<>(Arrays.asList(1, 2, 3));
Number num = numbers.get(0); // 可以读取
// numbers.add(4); // 编译错误,不能写入

反例

List<? super Number> numbers = new ArrayList<>();
numbers.add(1); // 可以写入
// Number num = numbers.get(0); // 编译错误,不能读取

在反例中,尝试从<? super Number>类型的集合中读取数据,这将导致编译错误。


10. 泛型集合的赋值与类型检查

10.1 规则

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

10.2 解释

Java的泛型是编译时的类型检查,运行时泛型信息会被擦除。因此,在将无泛型限制的集合赋值给泛型限制的集合时,可能会导致类型不匹配的问题。

10.3 代码示例

正例

List<String> generics = new ArrayList<>();
List notGenerics = new ArrayList();
notGenerics.add("a");
notGenerics.add(1);

for (Object obj : notGenerics) {
    if (obj instanceof String) {
        generics.add((String) obj);
    }
}
System.out.println(generics); // 输出: [a]

反例

List<String> generics = new ArrayList<>();
List notGenerics = new ArrayList();
notGenerics.add("a");
notGenerics.add(1);

generics = notGenerics; // 编译通过,但运行时抛出ClassCastException
String str = generics.get(1); // 抛出ClassCastException

在反例中,将无泛型限制的集合赋值给泛型限制的集合,导致在运行时抛出ClassCastException异常。


11. foreach循环中的remove/add操作

11.1 规则

  • 不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。

11.2 解释

foreach循环底层使用的是Iterator,如果在遍历过程中修改集合(如删除或添加元素),会导致Iterator的游标错乱,从而抛出ConcurrentModificationException异常。

11.3 代码示例

正例

List<String> list = new ArrayList<>(Arrays.asList("1", "2"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if ("1".equals(item)) {
        iterator.remove();
    }
}
System.out.println(list); // 输出: [2]

反例

List<String> list = new ArrayList<>(Arrays.asList("1", "2"));
for (String item : list) {
    if ("1".equals(item)) {
        list.remove(item); // 抛出ConcurrentModificationException
    }
}

在反例中,尝试在foreach循环中删除元素,这将导致ConcurrentModificationException异常。


12. Comparator实现类的条件

12.1 规则

  • 在JDK7及以上版本,Comparator实现类要满足自反性、传递性和对称性,否则Arrays.sortCollections.sort会抛出IllegalArgumentException异常。

12.2 解释

Comparator接口用于定义对象的排序规则。如果Comparator实现类不满足自反性、传递性和对称性,排序操作可能会导致不可预期的结果。

12.3 代码示例

正例

Comparator<Integer> comparator = (o1, o2) -> o1.compareTo(o2);
List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 2));
Collections.sort(list, comparator);
System.out.println(list); // 输出: [1, 2, 3]

反例

Comparator<Integer> comparator = (o1, o2) -> o1 > o2 ? 1 : -1; // 不满足自反性
List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 2));
Collections.sort(list, comparator); // 抛出IllegalArgumentException

在反例中,Comparator实现类不满足自反性,导致Collections.sort抛出IllegalArgumentException异常。


13. 泛型定义的diamond语法

13.1 规则

  • 在JDK7及以上,使用diamond语法或全省略来定义集合泛型。

13.2 解释

diamond语法(即<>)允许在创建泛型对象时省略类型参数,编译器会自动推断类型。这可以减少代码冗余,提高代码的可读性。

13.3 代码示例

正例

Map<String, String> map = new HashMap<>(16);
List<String> list = new ArrayList<>(10);

反例

Map<String, String> map = new HashMap<String, String>(16); // 冗余的类型参数
List<String> list = new ArrayList<String>(10); // 冗余的类型参数

在反例中,类型参数是冗余的,使用diamond语法可以简化代码。


14. 集合初始化时指定初始值大小

14.1 规则

  • 集合初始化时,指定集合初始值大小

14.2 解释

指定集合的初始容量可以减少集合在扩容时的性能开销。特别是在处理大量数据时,频繁的扩容操作会严重影响性能。

14.3 代码示例

正例

Map<String, String> map = new HashMap<>(16);
List<String> list = new ArrayList<>(10);

反例

Map<String, String> map = new HashMap<>(); // 未指定初始容量
List<String> list = new ArrayList<>(); // 未指定初始容量

在反例中,未指定集合的初始容量,可能导致集合在添加元素时频繁扩容,影响性能。


15. 使用entrySet遍历Map

15.1 规则

  • 使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历

15.2 解释

entrySet方法返回的是Map.Entry对象的集合,它包含了键和值。使用entrySet遍历Map时,只需要遍历一次即可获取键和值,而使用keySet方式则需要遍历两次。

15.3 代码示例

正例

Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);

for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

反例

Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);

for (String key : map.keySet()) {
    System.out.println(key + ": " + map.get(key)); // 遍历两次
}

在反例中,使用keySet方式遍历Map时,需要遍历两次,效率较低。


16. Map类集合的K/V存储null值

16.1 规则

  • 高度注意Map类集合K/V能不能存储null值的情况
集合类KeyValueSuper说明
HashTable不允许为null不允许为nullDictionary线程安全
ConcurrentHashMap不允许为null不允许为nullAbstractMap锁分段技术(JDK8:CAS)
TreeMap不允许为null允许为nullAbstractMap线程不安全
HashMap允许为null允许为nullAbstractMap线程不安全

16.2 解释

不同的Map实现类对null值的支持不同。例如,HashMap允许keyvaluenull,而ConcurrentHashMap不允许keyvaluenull

16.3 代码示例

正例

Map<String, Integer> map = new HashMap<>();
map.put(null, 1);
map.put("a", null);
System.out.println(map); // 输出: {null=1, a=null}

反例

Map<String, Integer> map = new ConcurrentHashMap<>();
map.put(null, 1); // 抛出NullPointerException
map.put("a", null); // 抛出NullPointerException

在反例中,尝试向ConcurrentHashMap中插入null值,这将导致NullPointerException异常。


17. 集合的有序性与稳定性

17.1 规则

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

17.2 解释

有序性是指集合中的元素按某种规则排列,稳定性是指集合每次遍历的元素次序是一定的。例如,ArrayList是有序但不稳定的,TreeSet是有序且稳定的。 ArrayList 是 order/unsort;HashMap 是 unorder/unsort;TreeSet 是 order/sort

17.3 代码示例

正例

List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 2));
Collections.sort(list);
System.out.println(list); // 输出: [1, 2, 3]

反例

Set<Integer> set = new HashSet<>(Arrays.asList(3, 1, 2));
System.out.println(set); // 输出顺序不确定

在反例中,HashSet是无序的,输出的元素顺序不确定。


18. 使用Set进行去重操作

18.1 规则

  • 利用Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List的contains方法进行遍历、对比、去重操作。

18.2 解释

Set集合中的元素是唯一的,因此可以利用Set来快速去重。而Listcontains方法需要遍历整个集合,效率较低。 list contains方法是迭代比较去 去重。从0开始比,依次比,集合多,会很慢。而set底层是依据hashCodeequals进行判断

18.3 代码示例

正例

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "a", "c"));
Set<String> set = new HashSet<>(list);
System.out.println(set); // 输出: [a, b, c]

反例

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "a", "c"));
List<String> uniqueList = new ArrayList<>();
for (String item : list) {
    if (!uniqueList.contains(item)) {
        uniqueList.add(item);
    }
}
System.out.println(uniqueList); // 输出: [a, b, c]

在反例中,使用Listcontains方法进行去重操作,效率较低。


小结

Java集合框架提供了强大的功能,但也伴随着一些潜在的陷阱。通过遵循最佳实践,开发者可以避免常见的错误,并编写出高效、健壮的代码。

在这里插入图片描述


http://www.kler.cn/a/530275.html

相关文章:

  • 【自开发工具介绍】SQLSERVER的ImpDp和ExpDp工具01
  • Oracle数据库高效管理与优化实践
  • MySQL 存储函数:数据库的自定义函数
  • 猴子吃桃问题
  • 游戏引擎 Unity - Unity 设置为简体中文、Unity 创建项目
  • 记录 | 基于MaxKB的文字生成视频
  • MBTI之INFJ型人格解读,INFJ的职业倾向、人际关系和INFJ的心理健康
  • doris:主键模型的导入更新
  • 系统URL整合系列视频一(需求方案)
  • ifconfig/hostname/hosts文件等学习
  • springboot/ssm教学资源管理系统web在线课程教学视频Java代码编写
  • 一文了解制造业中的QC是什么
  • 微信登录模块封装
  • 第一性原理:游戏开发成本的思考
  • 索罗斯的“反身性”(Reflexivity)理论:市场如何扭曲现实?(中英双语)
  • 【PyQt】lambda函数,实现动态传递参数
  • 本地Deepseek添加个人知识库(Page Assist/AnythingLLM)
  • 不确定性采样在分类任务中的应用
  • 【Navicat】设置字段根据当前时间更新
  • C++模板初了解
  • Vue 2 项目中 Mock.js 的完整集成与使用教程
  • C# 继承与多态详解
  • 新到手路由器宽带上网设置八步法
  • 2025.2.1——八、Web_php_wrong_nginx_config
  • 【大模型专栏—基础篇】智能体入门
  • TypeScript语言的语法糖