类集JDK1.2
概念:用来保存数据的集合
作用:可以解决数组的定长问题
顶层接口:Collection,可以理解为一个动态数组,只要长度不够了就能扩容。
Collection接口
Collection接口的继承关系:
Collection接口是java中保存单个对象的最顶层接口,其下有两个子接口:List和Set;
List接口:允许数据重复
Set接口:不允许数据重复
一般不推荐直接使用Collection接口,因为他不能区分数据到底是否重复,而且方法很局限。
链表:方便元素的插入和删除
数组:方便元素的查看和修改
Collection接口下的常用操作方法:
先来看看都有些什么方法:
然后着重列一下常用的方法:
- 添加一个元素:boolean add(E e);
- 删除一个元素:boolean remove(Object o);
- 查找一个元素:boolean contains(Object o);
- 取得集合大小: int size();
- 将集合类转为数组:Object[] toArray();
- 取得类集的迭代器:Iterator iterator();
Collection接口下的输出:
- 使用Iterator迭代器输出(Collection的迭代器接口,专门用来输出Collection接口内容)
Iterator iterator();取得该Collection接口对象的迭代器
boolean hasNext();判断当前集合是否还有未遍历的元素
E next();取得当前正在遍历的内容
标准格式:
while(iterator.hasNext()) {
E e = iterator.next();
}
-
foreach,实际上是Iterator的迭代输出
Collection接口能使用foreach输出的本质在于,所有子类都实现了Iterator接口 -
ListIterator(双向迭代输出-只有List接口有)
boolean hasPrevious();从后向前,前提:必须先从前到后输出一次
E previous();取得前一个元素 -
Enumeration(枚举输出-只有Vector及其子类Stack才有)
boolean hasMoreElements();
E nextElement(); -
快速失败策略(fail-fast);在迭代输出过程中删除时抛出异常:java.util.ConcurrentModificationException
-
什么是快速失败:优先考虑异常情况,当异常情况发生时,直接向用户抛出异常,程序终止。
-
fail-fast作用:保证集合在多线程场景下读到正确的值;在java.util.包下的集合类大多都会采用此策略报出异常(ArrayList,Vector,LinkedList,HashSet,这些类读写都在同一个副本中)
-
fail-safe:读写不在一个副本里;java.util.concurrent(CopyOnWriteArrayList采用读写分离,所有的读为异步操作,写为同步操作,且读写不在一个副本/concurrentHashMap)下的都是并发安全集合,不会出异常。
-
ConcurrentModificationException怎么产生的:由于在迭代输出集合时,并发的修改了集合的结构
protected transient int modCount = 0;//记录当前集合被修改(add,remove等方法)的次数
在迭代器的内部:取得集合迭代器的时候modCount值: int expectedModCount = modCount;
当每次调用next方法取得集合的下一个元素的时候,要先判断一下modCount有没有被修改,被修改了就要报错,代码:
final void checkForComodification() {
if(modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
如何避免ConcurrentModificationException:
1.迭代输出时,不要修改集合的内容
2.使用迭代器的修改方法(迭代器的修改方法里会重新赋值expectedModCount = modCount)
看一下List接口:80%会用到List
继承关系:
除了Collection里的方法外还有两个重要方法:
- 根据下标返回元素:E get(int index);
- 根据下标修改元素,返回修改前的元素值:E set(int index, E element);
import java.util.ArrayList;
import java.util.List;
public class TestList {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(1);
System.out.println(list);
System.out.println(list.size());
System.out.println(list.contains(3));
System.out.println(list.get(1));
System.out.println(list.set(1,7));
System.out.println(list.get(1));
System.out.println(list.remove(2));
System.out.println(list);
System.out.println(list.remove(new Integer(4)));
System.out.println(list);
}
}
结果:
[1, 2, 3, 4, 1]
5
true
2
2
7
3
[1, 7, 4, 1]
true
[1, 7, 1]
删除元素值的对象:boolean remove(Object o);
删除对应下标的元素:E remove(int index);
支持元素重复;
index和数字下标相同,从0开始编号;
- List集合中添加自定义的类的时候:
import java.util.LinkedList;
import java.util.List;
class Person{
private String name;
private Integer age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
}
public class PersonTest {
public static void main(String[] args) {
List<Person> people = new LinkedList<>();
Person person1 = new Person("张三",25);
Person person2 = new Person("李四",35);
Person person3 = new Person("王五",45);
people.add(person1);
people.add(person2);
people.add(person3);
System.out.println(people.contains(new Person("张三",25)));
}
}
结果:
false
contains和remove方法需要equals方法的支持,在自定义的类中加入下面覆写的equals代码;
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}
if(!(obj instanceof Person)){
return false;
}
Person per = (Person)obj;
return this.age.equals(per.age)&&this.name.equals(per.name);
}
结果:
true
如果在idea中写代码时导入lombok.Data包,在自定义类前面使用@Data注解,就可以不用自己写geter,serter和equals方法,maven依赖的下载网址:https://mvnrepository.com/tags/maven
List接口下三个常用的子类
这三个子类的关系与区别(源码):
使用上完全一样,具体的实现细节上有什么不同。
动态数组:数据放不下的时候自动扩容。
基于链表的动态数组
- ArrayList:使用场景:只查(数组随机访问效率高)
-
版本:1.2
-
底层:数组实现
-
构造方法:
- 无参构造:
DEFAULTCAPACITY_EMPTY_ELEMENTDATA是什么:
可以看到在构造方法中并没有开辟数组空间,呢在什么时候开辟的数组空间呢,有没有可能在添加的方法中开辟呢,我们来看看添加的方法:
ensureCapacityInternal可能是用来确保数组的内存的函数:
里面调用了ensureExplicitCapacity函数:
里面又调用了grow方法:是扩容函数
新容量 = 就旧容量+一半旧容量;
- 无参构造:
-
ArrayList在初始化的时候采用Lazy-Load(懒加载策略),数组不会在new的时候初始化,只有在new的对象第一次被使用时(add方法),内部的数组才会初始化为长度为10的数组。
扩容策略:每次扩容为原先数组的1.5倍。
线程安全性(多线程并发修改内容时,不会产生数据异常):没有锁,所以是线程不安全集合;
-
Vector
Vector中有一个特殊的子类:Stack:jdk内置的栈;
以后写栈用Stack;-
版本:1.0
-
底层:数组
-
初始化策略:当产生Vector对象时,内部数组就初始化为大小为10的数组;
无参构造:
-
扩容机制:每次扩容为原先数组的2倍
add方法:
ensureCapacityHelper函数:
grow:
新空间 = 旧空间+旧空间(一般不会传capacityIncrement) -
线程安全性
首先如果他是线程安全的,版本是1.0说明了不可能用lock,lock是1.5才出来的;
看到上面的add方法是使用synchronized的同步方法来确保线程安全的。
Vector效率低:
为什么效率低:用了synchronized的同步方法,锁住了对象下的所有方法,使其他的线程全部阻塞。
-
-
LinkedList:使用场景为:有频繁的插入和删除
特殊:LinkedList是jdk内置的队列接口(Queue)的子类
以后写队列用LinkedList
- 版本:1.2
- 底层:基于双向链表的动态数组
- 线程安全:线程不安全集合