目录
并发修改异常ConcurrentModificationException
集合是啥?!
Java 集合是 JDK 中为解决数据存储问题而设计的一系列接口及其实现类。它们主要用于管理元素的集合并支持多种操作,如添加、删除、遍历等。
Java 集合框架提供了三个基本接口:
Collection、List 和 Set,以及一个映射接口 Map。
1.Collection集合
Collection接口是单例集合的顶层接口,表示一组对象,这些对象也称为元素。Collection 接口没有直接实现类,而是提供两个子接口:List 和 Set。所以在实例化Collection时只能利用多态的形式,用其子接口的实现类进行实例化。例如:
public class CollectionDemo01 {
public static void main(String[] args) {
//创建Collection集合的对象
Collection<String> c = new ArrayList<String>();
//添加元素:boolean add(E e)
c.add("hello");
c.add("world");
c.add("java");
//输出集合对象
System.out.println(c);
}
}
Collection集合的常用方法
方法名 | 说明 |
---|---|
boolean add(E e) | 添加元素 |
boolean remove(Object o) | 从集合中移除指定的元素 |
void clear() | 清空集合中的元素 |
boolean contains(Object o) | 判断集合中是否存在指定的元素 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中元素的个数 |
迭代器 Iterator
迭代器是一种集合专用的遍历方式,在遍历Collection集合时,我们需要用到迭代器,迭代器是通过集合的iterator()方法得到的,所以依赖于集合存在。
public class IteratorDemo {
public static void main(String[] args) {
//创建集合对象
Collection<String> c = new ArrayList<>();
//添加元素
c.add("hello");
c.add("world");
c.add("java");
c.add("javaee");
//Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
Iterator<String> it = c.iterator();
//用while循环改进元素的判断和获取
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
}
}
1.1 List集合
List是有序的、元素可重复的集合接口,最常见的 List 实现类有 ArrayList 和 LinkedList。
(1) ArrayList
基于数组实现,支持快速随机访问元素和尾部添加元素,但在插入或删除元素时,需要移动大量元素,影响性能。
(2) LinkedList
基于链表实现,支持快速插入和删除元素,但对于随机访问元素的效率较低。
List集合特有方法
方法名 | 描述 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
List集合遍历
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
for (String str : list) {
System.out.println(str);
}
列表迭代器ListIterator
通过List集合的listIterator()方法得到,用于允许程序员沿任一方向遍历的列表迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置。
public class ListIteratorDemo {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
//获取列表迭代器
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
String s = lit.next();
if(s.equals("world")) {
lit.add("javaee");
}
}
System.out.println(list);
}
}
//执行结果:[hello, world, javaee, java]
并发修改异常ConcurrentModificationException
异常原因:迭代器遍历的过程中,修改了集合中的元素,造成了迭代器获取元素中判断预期修改值和实际修改值不一致。简单理解就是,我们获取迭代器时相当于是获取了原有集合的一个副本进行遍历,如果在遍历过程中原有集合发生了变化,迭代器就失效了。
解决办法:在不更改其他地方的情况下,可以改用普通for循环遍历。
ArrayList 和 LinkedList 的区别
ArrayList底层是数组结构实现,特点是查询快、增删慢;
LinkedList底层是链表结构实现,特点是查询慢、增删快;
可以根据实际应用场景结合需求选择相应的实现方式。
LinkedList特有方法
方法名 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
1.2 Set集合
Set是无序、元素不可重复的集合接口,同时,由于没有索引,所以只能通过迭代器和增强for循环进行遍历,Set接口最常用的实现类为HashSet 和 TreeSet。
(1) HashSet
基于散列表实现,在查找元素时具有较高的效率,但按照一定规则存储元素可能导致散列表过度扩张,影响性能。
特点
底层实现结构是哈希表
无重复元素且不保证添加顺序和输出顺序一致
没有带索引的方法,所以不能使用普通for循环遍历
public class SetDemo {
public static void main(String[] args) {
//创建集合对象 多态创建
Set<String> set = new HashSet<String>();
//添加元素
set.add("hello");
set.add("world");
set.add("java");
//不包含重复元素的集合,添加已存在元素时不会再次存储
set.add("world");
//遍历
for(String s : set) {
System.out.println(s);
}
}
}
/*执行结果只输出一次
world、java、hello
*/
HashSet集合保证元素唯一性的存储过程:
LinkedHashSet
是在HashSet的基础上结合链表的一个补充实现类
特点
由链表保证元素有序,即说元素的存储和取出顺序是一致的。
由哈希表保证元素唯一,没有重复的元素。
HashSet 和 LinkedHashSet 在使用上并没有明显区别,LinkedHashSet解决了HashSet在遍历时顺序不确定的问题,但相应的,LinkedHashSet的空间占用也会略多一些。
(2) TreeSet
基于红黑树实现,保证集合中的元素顺序排列,支持范围查询和排序操作,但插入和删除元素的平均时间复杂度较高。
特点
由于是Set集合,所以不包含重复元素。
没有带索引的方法,所以不能使用普通for循环遍历。
元素有序,可以按照一定的规则进行排序,具体排序方式取决于构造方法。
1) TreeSet():根据其元素的自然排序进行排序。
2) TreeSet(Comparator comparator) :根据指定的比较器进行排序.
public class TreeSetDemo01 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Integer> ts = new TreeSet<Integer>();
//添加元素
ts.add(10);
ts.add(40);
ts.add(30);
ts.add(50);
ts.add(20);
ts.add(30);
//遍历集合
for(Integer i : ts) {
System.out.println(i);
}
}
}
/*
执行结果:
10
20
30
40
50
*/
TreeSet中的元素排序
当向 TreeSet 集合中添加元素时,集合内部会按照特定的顺序进行排序。默认情况下,TreeSet 对象使用 Comparable 接口提供的自然排序规则来对元素进行排序。如果你想使用其他比较标准来排序元素,则可以通过实现 Comparator 接口并将其传递给 TreeSet 构造函数中的参数来指定非默认排序规则。
Comparable 接口是一个定义了 compareTo()
方法的 Java 接口,只有实现该接口的类才能够在 TreeSet 中进行自然排序。compareTo()
方法将当前对象与另一个同类型对象进行比较,并返回一个整数值,表示它们之间的大小关系:
如果当前对象小于另一个对象,则返回 -1
如果当前对象等于另一个对象,则返回 0
如果当前对象大于另一个对象,则返回 1
案例一
如何实现 Comparable 接口来对自定义的 Student 类型进行排序
public class Student implements Comparable<Student> {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
@Override
public int compareTo(Student o) {
// 按成绩从高到低排序
return o.getScore() - this.score;
}
}
如果需要根据排序规则不同来对同一个类进行排序,则可以通过 Comparator 接口来自定义比较器。Comparator 接口定义了 compare()
方法,该方法接受两个同类型的参数并返回一个整数值表示它们之间的大小关系:
如果第一个参数小于第二个参数,则返回 -1
如果第一个参数等于第二个参数,则返回 0
如果第一个参数大于第二个参数,则返回 1
注意这里的-1和1并不是固定值,只是代表两个比较对象的大小关系,a-b小于0说明a<b,a-b大于0则说明a>b,a-b=0则说明a=b。
案例二
假设我们需要对学生表进行排序,如果成绩一样,那在成绩排序的基础上按照年龄由小到大排序, 成绩和年龄都一样,则按照姓名的字典顺序排序。
//学生类
public class Student {
//implements Comparable<Student>
public String name;
public int age;
public int score;
public Student() {
}
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public 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 (score != student.score) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
result = 31 * result + score;
return result;
}
@Override
public String toString() {
return "姓名:'" + name + '\'' +
", 年龄:" + age +
", 成绩:" + score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
//测试类
import java.util.Comparator;
import java.util.TreeSet;
public class SetStudentDemo {
public static void main(String[] args) {
TreeSet<Student> tStudents = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//先按成绩由高到底排序
int n1 = s2.score - s1.score;
//成绩相同时按年龄由小到大排序
int n2 = n1 == 0 ? s1.age - s2.age : n1;
//年龄也相同时,按姓名字典顺序排序
int n3 = n2 == 0 ? s1.name.compareTo(s2.name) : n2;
return n3;
}
});
Student s1 = new Student("Tom", 20, 90);
Student s2 = new Student("Jerry", 22, 95);
Student s3 = new Student("John", 20, 100);
Student s4 = new Student("Lily", 22, 100);
Student s5 = new Student("Lucy", 22, 90);
Student s6 = new Student("Kevin", 22, 90);
tStudents.add(s1);
tStudents.add(s2);
tStudents.add(s3);
tStudents.add(s4);
tStudents.add(s5);
tStudents.add(s6);
for (Student s : tStudents) {
System.out.println(s.toString());
}
}
}
//执行结果:
姓名:'John', 年龄:20, 成绩:100
姓名:'Lily', 年龄:22, 成绩:100
姓名:'Jerry', 年龄:22, 成绩:95
姓名:'Tom', 年龄:20, 成绩:90
姓名:'Kevin', 年龄:22, 成绩:90
姓名:'Lucy', 年龄:22, 成绩:90
2.Map集合
Map 是键值对映射的集合接口,其中最常用的实现类为 HashMap 和 TreeMap。
interface Map<K,V> K:键的类型;V:值的类型
特点
键值对映射关系
一个键对应一个值
键不能重复,值可以重复
元素存取无序
Map集合常用方法
方法名 | 说明 |
---|---|
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
V get(Object key) | 根据键获取值 |
Set<K> keySet() | 获取所有键的集合 |
Collection<V> values() | 获取所有值的集合 |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
Map集合遍历
在遍历Map集合时,通常有两种方式,一是使用 keySet 方法,二是 entrySet方法。其中,keySet 返回 Map 中所有的键的集合,entrySet 返回 Map 中所有键值对的 Set 集合。
//keySet先获取所有键的方式
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
// 使用 keySet 遍历
for(String key : map.keySet()) {
int value = map.get(key);
System.out.println(key + " = " + value);
}
//entrySet直接获取所有键值对的方式
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
// 使用 entrySet 遍历
for(Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
(1) HashMap
基于散列表实现,查找键值对的效率较高,但在并发环境下可能出现哈希冲突等问题。
(2) TreeMap
基于红黑树实现,能够保证映射关系中的元素按照键进行排序,但插入和删除键值对的时间复杂度较高。
Collections集合工具类
Collections类常用方法
方法名 | 说明 |
---|---|
public static void sort(List<T> list) | 将指定的列表按升序排序 |
public static void reverse(List<?> list) | 反转指定列表中元素的顺序 |
public static void shuffle(List<?> list) | 使用默认的随机源随机排列指定的列表 |
public class CollectionsDemo01 {
public static void main(String[] args) {
//创建集合对象
List<Integer> list = new ArrayList<Integer>();
//添加元素
list.add(30);
list.add(20);
list.add(50);
list.add(10);
list.add(40);
//public static <T extends Comparable<? super T>> void sort(List<T> list):将指定的列表按升序排序
// Collections.sort(list);
//public static void reverse(List<?> list):反转指定列表中元素的顺序
// Collections.reverse(list);
//public static void shuffle(List<?> list):使用默认的随机源随机排列指定的列表
Collections.shuffle(list);
System.out.println(list);
}
}