Java进阶3 集合体系(上)
创始人
2025-06-01 08:55:51

单列集合(Collection)

  • 一、集合分类
  • 二、Collection
    • 2.1 Collection集合的常用方法
    • 2.2 Collection的遍历
      • 2.2.1 迭代器
      • 2.2.2 增强for
      • 2.2.3 forEach
  • 三、List系列集合
    • 3.1、List集合
      • 3.1.1、List集合的遍历
    • 3.2、ArrayList集合
      • ● ArrayList底层原理
    • 3.3、LinkedList集合
      • ● LinkedList底层原理
      • ● LinkedList的应用场景
        • ○ 模拟队列结构
        • ○ 模拟栈结构
  • 四、Set系列集合
    • 4.1、HashSet集合
      • ● HashSet底层实现
      • ● HashSet去重原理
    • 4.2、LinkedHashSet集合
      • ● LinkedHashSet底层实现
    • 4.1、TreeSet集合
  • 五、并发修改异常
  • (☆) Collection集合总结


一、集合分类

集合种类
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

目前常用的集合分为两大类,一类是单列集合(Collection类集合):一个一个的元素;一类是双列集合(Map类集合):一对一对的元素。

这一章内容主要介绍单列集合(Collection类集合)。

二、Collection

Collection,是单列集合的根接口,在它的下面又有两个子接口List接口、Set接口,而List与Set下面又分别有不同的实现类,具体如下图所示:

在这里插入图片描述

上图各种集合的特点为:

在这里插入图片描述


2.1 Collection集合的常用方法

在这里插入图片描述

上图为Collection的常用方法,而它下面的ArrayList、LinkedList、HashSet、LinkedHashSet、TreeSet集合都可以调用上面的方法。

现在通过代码来使用这些方法,以创建ArrayList为例。

//add(元素)  添加元素到集合中
//clear()   清空集合的元素
//remove(元素)  删除集合中的元素
//contains(元素)  判断集合的元素是否存在
//isEmpty() 判断集合的元素是否为空
//size()    查看集合的元素个数
//toArray()  把集合转化为Object类型数组Collection c = new ArrayList<>();
//1.public boolean add(E e): 添加元素到集合
c.add("java1");
c.add("java1");
c.add("java2");
c.add("java2");
c.add("java3");
System.out.println(c); //打印: [java1, java1, java2, java2, java3]//2.public int size(): 获取集合的大小
System.out.println(c.size()); //5//3.public boolean contains(Object obj): 判断集合中是否包含某个元素
System.out.println(c.contains("java1")); //true
System.out.println(c.contains("Java1")); //false//4.pubilc boolean remove(E e): 删除某个元素,如果有多个重复元素只能删除第一个
System.out.println(c.remove("java1")); //true
System.out.println(c); //打印: [java1,java2, java2, java3]//5.public void clear(): 清空集合的元素
c.clear(); 
System.out.println(c); //打印:[]//6.public boolean isEmpty(): 判断集合是否为空 是空返回true 反之返回false
System.out.println(c.isEmpty()); //true//7.public Object[] toArray(): 把集合转换为数组
Object[] array = c.toArray();
System.out.println(Arrays.toString(array)); //[java1,java2, java2, java3]//8.如果想把集合转换为指定类型的数组,可以使用下面的代码
String[] array1 = c.toArray(new String[c.size()]);
System.out.println(Arrays.toString(array1)); //[java1,java2, java2, java3]//9.还可以把一个集合中的元素,添加到另一个集合中
Collection c1 = new ArrayList<>();
c1.add("java1");
c1.add("java2");
Collection c2 = new ArrayList<>();
c2.add("java3");
c2.add("java4");
c1.addAll(c2); //把c2集合中的全部元素,添加到c1集合中去
System.out.println(c1); //[java1, java2, java3, java4]

2.2 Collection的遍历

通过普通的for循环,可以遍历接触最多的ArrayList集合,但是也仅限于List集合,而没有索引的Set集合,是不能通过普通的for来循环的,这个时候就应该掌握新的遍历方式。


2.2.1 迭代器

迭代器:集合的通用遍历方式。

迭代器代码的原理:

  • 当调用iterator()方法获取迭代器时,当前指向第一个元素
  • hasNext()方法则判断这个位置是否有元素,如果有则返回true,进入循环
  • 调用next()方法获取元素,并将当前元素指向下一个位置,
  • 等下次循环时,则获取下一个元素,依此内推

代码演示

Collection c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
c.add("灭绝");
System.out.println(c); //[赵敏, 小昭, 素素, 灭绝]//第一步:先获取迭代器对象
//解释:Iterator就是迭代器对象,用于遍历集合的工具)
Iterator it = c.iterator();//第二步:用于判断当前位置是否有元素可以获取
//解释:hasNext()方法返回true,说明有元素可以获取;反之没有
while(it.hasNext()){//第三步:获取当前位置的元素,然后自动指向下一个元素.String e = it.next();System.out.println(s);
}

最后小结一下迭代器遍历会用到哪些方法。

在这里插入图片描述


2.2.2 增强for

for循环的三种用法:fori、增强for、forEach。

fori与增强for的区别:
fori:可以遍历数组、有序的集合。
增强for:可以遍历数组、有序或无序的集合。

增强for的语法

for(元素的数据类型  变量名 : 数组或者集合){
}

使用演示

Collection c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
c.add("灭绝");//1.使用增强for遍历集合
for(String s: c){System.out.println(s); 
}//2.再尝试使用增强for遍历数组
String[] arr = {"迪丽热巴", "古力娜扎", "稀奇哈哈"};
for(String name: arr){System.out.println(name);
}

2.2.3 forEach

在JDK8版本后,Java迎来了一个新的遍历方式:forEach。

forEach本质上是一个方法,它的参数是一个Consumer接口,而Consumer是一个函数式接口,所以使用Lambda表达式来改进。

forEach方法

在这里插入图片描述

forEach演示

Collection c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
c.add("灭绝");//调用forEach方法
//由于参数是一个Consumer接口,所以可以传递匿名内部类
c.forEach(new Consumer{@Overridepublic void accept(String s){System.out.println(s);}
});//也可以使用lambda表达式对匿名内部类进行简化
c.forEach(s->System.out.println(s)); //[赵敏, 小昭, 素素, 灭绝]

三、List系列集合

Collection了解完后,走向它的子类,List与Set,现在介绍其中一个子类:List集合。

List集合
在这里插入图片描述

List系列集合的特点

  • 有序
  • 可重复
  • 有索引

3.1、List集合

List集合是有索引的,所以会比Set类集合多了索引的操作方法,具体方法如下:

List集合的常用方法

在这里插入图片描述
List的代码演示

//1.创建一个ArrayList集合对象(有序、有索引、可以重复)
List list = new ArrayList<>();
list.add("蜘蛛精");
list.add("至尊宝");
list.add("至尊宝");
list.add("牛夫人"); 
System.out.println(list); //[蜘蛛精, 至尊宝, 至尊宝, 牛夫人]//2.public void add(int index, E element): 在某个索引位置插入元素
list.add(2, "紫霞仙子");
System.out.println(list); //[蜘蛛精, 至尊宝, 紫霞仙子, 至尊宝, 牛夫人]//3.public E remove(int index): 根据索引删除元素, 返回被删除的元素
System.out.println(list.remove(2)); //紫霞仙子
System.out.println(list);//[蜘蛛精, 至尊宝, 至尊宝, 牛夫人]//4.public E get(int index): 返回集合中指定位置的元素
System.out.println(list.get(3));//5.public E set(int index, E e): 修改索引位置处的元素,修改后,会返回原数据
System.out.println(list.set(3,"牛魔王")); //牛夫人
System.out.println(list); //[蜘蛛精, 至尊宝, 至尊宝, 牛魔王]

3.1.1、List集合的遍历

List集合的遍历相比于Collection多了一种方式:普通for循环,因为只有List里有索引,所以它的循环方式一共有:

  • 普通for循环(只有List类的集合能用)
  • 迭代器
  • 增强for
  • Lambda表达式(forEach)

List集合的代码使用

List list = new ArrayList<>();
list.add("蜘蛛精");
list.add("至尊宝");
list.add("糖宝宝");//1.普通for循环
for(int i = 0; i< list.size(); i++){//i = 0, 1, 2String e = list.get(i);System.out.println(e);
}//2.增强for遍历
for(String s : list){System.out.println(s);
}//3.迭代器遍历
Iterator it = list.iterator();
while(it.hasNext()){String s = it.next();System.out.println(s);
}//4.lambda表达式遍历
list.forEach(s->System.out.println(s));

3.2、ArrayList集合

ArrayList:最常用也是最熟悉的集合。有序、可重复、有索引。

● ArrayList底层原理

ArrayList集合的底层,是基于数组结构实现的,也就是说,当你往集合容器中存储元素时,底层本质上是往数组中存储元素。

既然,ArrayList是基于数组实现的,那么就会有数组的特点,而数组的特点是:查询快、增删慢,所以ArrayList的特点为:

  • 查询速度快(注:因为根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同。
  • 删除效率低:可能需要把后面很多的数据前移。
  • 添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者扩容。

数组的长度是固定的,但集合的长度是可变的,那ArrayList的底层是数组,这不是相悖论吗?其实不然,原因如下:

  1. 利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组。
  2. 添加第一个元素时,底层会创建一个新的长度为10的数组。
  3. 存满时,会自动扩容1.5倍。
  4. 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准。

注意:

数组扩容,并不是在原数组上扩容(因为原数组是不可以扩容的),底层会重新创建一个数组,把原数组中的元素复制到新数组中去。

在这里插入图片描述


3.3、LinkedList集合

LinkedList:有序、可重复、有索引。


● LinkedList底层原理

与ArrayList不一样的是,ArrayList的底层是通过数组来实现的,而LinkedList的底层则通是链表结构来实现的。

链表结构:由一个一个的节点组成,一个节点由数据值、下一个元素的地址组成。

在这里插入图片描述
链表的特点为:

  1. 查询慢(无论查询哪个数据都要从头开始找)
  2. 增删相对快(不用一个个的查找)

如果,现在要在B节点和D节点中插入一个元素,只需要把B节点指向D节点的地址断开,重新指向新节点地址就可以了。

修改

在这里插入图片描述

如果,现在想要把D节点删除,只需要让C节点指向E节点的地址,然后把D节点指向E节点的地址断掉。此时D节点就会变成垃圾,会被垃圾回收器清理掉。

删除
在这里插入图片描述

上面的结构都是单向的链表,它的方向是从头节点指向尾节点的,只能从左往右查找元素,这样查询效率比较慢;还有一种链表叫双向链表,不关可以从左往右找,还能从右往左找。

单双链表

在这里插入图片描述

双向链表的特点:查询慢,增删相对较快,但对首尾元素进行增删查改的速度是极快的。


● LinkedList的应用场景

因为LinkedList可单双向链表的特点,LinkedList可以用来设计栈结构与队列结构。

○ 模拟队列结构

队列:先进先出,后进后出

队列结构模型

在这里插入图片描述

使用LinkedList模拟入队

//1.创建一个队列:先进先出、后进后出
LinkedList queue = new LinkedList<>();
//入对列
queue.addLast("第1号人");
queue.addLast("第2号人");
queue.addLast("第3号人");
queue.addLast("第4号人");
System.out.println(queue);//出队列
System.out.println(queue.removeFirst());	//第4号人
System.out.println(queue.removeFirst());	//第3号人
System.out.println(queue.removeFirst());	//第2号人
System.out.println(queue.removeFirst());	//第1号人

○ 模拟栈结构

栈:先进后出,后进先出。

栈结构

在这里插入图片描述

使用LinkedList模拟栈结构

//1.创建一个栈对象
LinkedList stack = new ArrayList<>();
//压栈(push) 等价于 addFirst()
stack.push("第1颗子弹");
stack.push("第2颗子弹");
stack.push("第3颗子弹");
stack.push("第4颗子弹");
System.out.println(stack); //[第4颗子弹, 第3颗子弹, 第2颗子弹,第1颗子弹]//弹栈(pop) 等价于 removeFirst()
System.out.println(statck.pop()); //第4颗子弹
System.out.println(statck.pop()); //第3颗子弹
System.out.println(statck.pop()); //第2颗子弹
System.out.println(statck.pop()); //第1颗子弹//弹栈完了,集合中就没有元素了
System.out.println(list); //[]

四、Set系列集合

Set系列集合:Collection中的另一个子集合。

Set系列集合

在这里插入图片描述

Set系列集合的特点:

  • HashSet:无序、不重复、无索引。
  • LinkedHashSet:有序、不重复、无索引。
  • TreeSet:排序、不重复、无索引。

Set集合的演示

//Set set = new HashSet<>();	//无序、无索引、不重复
//Set set = new LinkedHashSet<>(); //有序、无索引、不重复
Set set = new TreeSet<>(); //可排序(升序)、无索引、不重复
set.add(666);
set.add(555);
set.add(555);
set.add(888);
set.add(888);
set.add(777);
set.add(777);
System.out.println(set); //[555, 666, 777, 888]

4.1、HashSet集合

HashSet:无序、不重复、无索引。

● HashSet底层实现

HashSet为什么可以去重?是因为HashSet的底层是基于哈希表实现的,哈希表根据JDK版本的不同,也会有所区别。

  • JDK8前:哈希表 = 数组 + 链表
  • JDK8后:哈希表 = 数组 + 链表 + 红黑树

JDK8前的底层原理:

在这里插入图片描述

往HashSet集合中存储元素时,底层调用了元素的两个方法:一个是hashCode方法:获取元素的hashCode值(哈希值);另一个则是调用了元素的equals方法:用来比较新添加的元素和集合中已有的元素是否相同。

  • 只有新添加元素的hashCode值和集合中已有元素的hashCode值相同、新添加的元素调用equals方法,和集合中已有元素比较结果为true,才认为元素重复。
  • 如果hashCode值相同,equals比较不同,则以链表的形式链接,以数组的同一个索引为位置(如上图所示)

JDK8之后的底层原理

在这里插入图片描述

JDK8开始后,为了提高性能,当链表的长度超过8时,就会把链表转换为红黑树。


● HashSet去重原理

了解了HashSet存储元素的原理,依赖于两个方法:一个是hashCode方法;另一个是用equals方法。

但要想保证在HashSet集合中没有重复元素,我们需要重写元素类的hashCode和equals方法。

以下介绍一个案例:

以Student类为例,假设把Student类的对象作为HashSet集合的元素,想要让学生的姓名和年龄相同,就认为元素重复。

Student类

public class Student{private String name; //姓名private int age; //年龄private double height; //身高//无参数构造方法public Student(){}//全参数构造方法public Student(String name, int age, double height){this.name=name;this.age=age;this.height=height;}//...get、set、toString()方法自己补上..//按快捷键生成hashCode和equals方法//alt+insert 选择 hashCode and equals@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;if (age != student.age) return false;if (Double.compare(student.height, height) != 0) return false;return name != null ? name.equals(student.name) : student.name == null;}@Overridepublic int hashCode() {int result;long temp;result = name != null ? name.hashCode() : 0;result = 31 * result + age;temp = Double.doubleToLongBits(height);result = 31 * result + (int) (temp ^ (temp >>> 32));return result;}
}

Test类

public class Test{public static void main(String[] args){Set students = new HashSet<>();Student s1 = new Student("至尊宝",20, 169.6);Student s2 = new Student("蜘蛛精",23, 169.6);Student s3 = new Student("蜘蛛精",23, 169.6);Student s4 = new Student("牛魔王",48, 169.6);students.add(s1);students.add(s2);students.add(s3);students.add(s4);for(Student s : students){System.out.println(s);}}
}

在上面的代码中存入了两个蜘蛛精,但实际打印出来却只有一个,打印结果如下:

Student{name='牛魔王', age=48, height=169.6}
Student{name='至尊宝', age=20, height=169.6}
Student{name='蜘蛛精', age=23, height=169.6}

4.2、LinkedHashSet集合

LinkedHashSet:有序、无重复、无索引。

● LinkedHashSet底层实现

LinkedHashSet的底层采用的也是哈希表结构(链表 + 数组 + 红黑树),只不过不同的点是,它额外新增了一个双向链表来维护元素的存取顺序。

LinkedHashSet底层实现

在这里插入图片描述
每次添加元素,就和上一个元素用双向链表连接一下。第一个添加的元素是双向链表的头节点,最后一个添加的元素是双向链表的尾节点。

把上个案例中使用的集合改为LinkedHashSet集合,看看效果如何。

public class Test{public static void main(String[] args){Set students = new LinkedHashSet<>();Student s1 = new Student("至尊宝",20, 169.6);Student s2 = new Student("蜘蛛精",23, 169.6);Student s3 = new Student("蜘蛛精",23, 169.6);Student s4 = new Student("牛魔王",48, 169.6);students.add(s1);students.add(s2);students.add(s3);students.add(s4);for(Student s : students){System.out.println(s);}}
}

打印结果依然相同

Student{name='至尊宝', age=20, height=169.6}
Student{name='蜘蛛精', age=23, height=169.6}
Student{name='牛魔王', age=48, height=169.6}

4.1、TreeSet集合

TreeSet集合:可以对元素进行排序,但必须指定元素的排序规则。

注意:如果元素本身存在排序规则,则可以直接排序,如果元素不存在自然顺序,就需要我们实现Comparable接口,重写compareTo方法

如果往集合中存储String类型的元素,或者Integer类型的元素,它们本身就具备排序规则,所以直接就可以排序。

Set set1= new TreeSet<>();
set1.add(8);
set1.add(6);
set1.add(4);
set1.add(3);
set1.add(7);
set1.add(1);
set1.add(5);
set1.add(2);
System.out.println(set1); //[1,2,3,4,5,6,7,8]Set set2= new TreeSet<>();
set2.add("a");
set2.add("c");
set2.add("e");
set2.add("b");
set2.add("d");
set2.add("f");
set2.add("g");
System.out.println(set1); //[a,b,c,d,e,f,g]

如果往TreeSet集合中存储自定义类型的元素,比如说Student类型,则需要我们自己定义排序规则,否则会出现异常。此时我们定义的排序规则一共有两种可能:

第一种: 让元素的类实现Comparable接口,重写compareTo方法。
第二种: 在创建TreeSet集合时,通过构造方法传递Compartor比较器对象。

排序方式一的演示

//第一步:先让Student类,实现Comparable接口
//注意:Student类的对象是作为TreeSet集合的元素的
public class Student implements Comparable{private String name;private int age;private double height;//无参数构造方法public Student(){}//全参数构造方法public Student(String name, int age, double height){this.name=name;this.age=age;this.height=height;}//...get、set、toString()方法自己补上..//第二步:重写compareTo方法//按照年龄进行比较,只需要在方法中让this.age和o.age相减就可以。/*原理:在往TreeSet集合中添加元素时,add方法底层会调用compareTo方法,根据该方法的结果是正数、负数、还是零,决定元素放在后面、前面还是不存。*/@Overridepublic int compareTo(Student o) {//this:表示将要添加进去的Student对象//o: 表示集合中已有的Student对象return this.age-o.age;}
}

排序方式二演示:

//创建TreeSet集合时,传递比较器对象排序
/*
原理:当调用add方法时,底层会先用比较器,根据Comparator的compare方是正数、负数、还是零,决定谁在后,谁在前,谁不存。
*/
//下面代码中是按照学生的年龄升序排序
Set students = new TreeSet<>(new Comparator{@Overridepublic int compare(Student o1, Student o2){//需求:按照学生的身高排序return Double.compare(o1,o2); }
});//创建4个Student对象
Student s1 = new Student("至尊宝",20, 169.6);
Student s2 = new Student("紫霞",23, 169.8);
Student s3 = new Student("蜘蛛精",23, 169.6);
Student s4 = new Student("牛魔王",48, 169.6);//添加Studnet对象到集合
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
System.out.println(students); 

五、并发修改异常

了解完Collection集合后,我们知道可以使用:迭代器、增强for与forEach来实现集合的遍历,但是注意一个点:在使用迭代器遍历集合时,可能存在并发修改异常。

现在把这个异常用代码演示出来:

List list = new ArrayList<>();
list.add("王麻子");
list.add("小李子");
list.add("李爱花");
list.add("张全蛋");
list.add("晓李");
list.add("李玉刚");
System.out.println(list); // [王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]//需求:找出集合中带"李"字的姓名,并从集合中删除
Iterator it = list.iterator();
while(it.hasNext()){String name = it.next();if(name.contains("李")){list.remove(name);}
}
System.out.println(list);

这个时候编译是没有任何问题的,但是当你运行的时候,你就会发现控制台出现如下的情况:

在这里插入图片描述

这就是并发修改异常

为什么会出现这个问题?是因为迭代器遍历机制,规定迭代器遍历集合的同时,不允许集合自己去增删元素,否则就会出现这个异常。

该如何解决?

答案是不使用集合的删除方法,使用迭代器的删除方法。

List list = new ArrayList<>();
list.add("王麻子");
list.add("小李子");
list.add("李爱花");
list.add("张全蛋");
list.add("晓李");
list.add("李玉刚");
System.out.println(list); // [王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]//需求:找出集合中带"李"字的姓名,并从集合中删除
Iterator it = list.iterator();
while(it.hasNext()){String name = it.next();if(name.contains("李")){//list.remove(name);it.remove(); //当前迭代器指向谁,就删除谁}
}
System.out.println(list);

(☆) Collection集合总结

在这里插入图片描述

相关内容

热门资讯

为本土育才 助产业升级:赣州稀... 原标题:为本土育才 助产业升级:赣州稀有金属市域产教联合体探索人才培养与产业升级双赢路径 ...
淄博:“银龄乐学”激活社区治理... 转自:淄博发布12月9日上午9点刚过,淄博经开区南宿舍社区老年大学舞蹈训练厅内,音乐声刚刚响起,学员...
绿金新闻 | 联合国贸发会议:...   Hehson财经ESG评级中心提供包括资讯、报告、培训、咨询等在内的14项ESG服务,助力上市公...
青春志愿红 扫雪暖民心 (来源:河北青年报)转自:河北青年报近日,河北省多地迎来降雪天气,为保障群众安全出行,共青团河北省委...
两名学生摆摊卖冰糖葫芦遭隔壁摊... 转自:扬子晚报近日,一则“男子卖冰糖葫芦被掀摊”的视频在社交媒体平台引发热议。据了解,12月12日,...