Java 集合
集合是什么
Java 集合类存放于java.util 包中,是一个用于存放对象的容器
- 集合中只能存放对象,如果存入一个简单类型,实际上进行了自动转换【装箱】
- 集合中存放的是多个对象的引用,对象本身还是存放在堆内存
- 集合中可以存放不同类型、不限数量的数据
集合接口:
规范所能够访问的方法
集合抽象类:
提供公共方法的实现
集合具体实现类:
提供独有的方法实现
集合工具类:
针对集合的通用算法的封装
Collection 接口
一般来说Collection 接口是集合框架的顶级接口,但是事实上不是
public interface Collection<E> extends Iterable<E>
特征: 集合中的数据无序,允许重复
Hello Collection 接口
// Collection collection=new ArrayList();
Collection collection=new HashSet();
for(int i=0;i<50;i++)
collection.add(i%10); //集合中并不能直接存储简单类型数据,这里存放时会自动装箱
System.out.println(collection.size()); //获取存放的元素个数
//Collection 接口中并没有提供按照序号遍历元素的方法
for(Object tmp:collection)
System.out.println(tmp);
Collection 接口定义的方法预览
public interface Collection<E> extends Iterable<E> {
//获取集合中的元素个数
int size();
//判断集合中的元素个数是否为0,不能使用这个方法判断null
boolean isEmpty();
//判断集合中是否包含指定元素o,有true 没有false。不能判断集合中是否包含null 元素
boolean contains(Object o);
//Iterable 接口中定义的方法,获取迭代器对象用于遍历集合中的所有元素
Iterator<E> iterator();
//将集合转换为Object 类型的数组
Object[] toArray();
//使用泛型将集合转换为特定类型的数组
<T> T[] toArray(T[] a);
//向集合中添加元素e
boolean add(E e);
//从集合中删除指定的元素o,如果删除成功true 否则false
boolean remove(Object o);
}
操作一组元素的方法
//通过lambda 表达式元素需要删除的元素
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
default Stream<E> stream() { //支持流式编程
return StreamSupport.stream(spliterator(), false);
}
Iterable 可迭代的接口
Iterator 迭代器:走访器,可以理解为集合中元素的指针
public interface Iterable<T> {
//通过iterator 方法获取迭代器
Iterator<T> iterator();
//支持使用Lambda 表达式遍历每个元素
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
}
之所以所有的集合对象都可以使用foreach 接口,例如for(Object tmp:collection),正是因为Iterable 接口中提供了默认实现,如果具体实现类中不使用default 实现,则必须进行覆盖定义Iterator 迭代器
它是Java 集合的顶层接口(不包括map 系列的集合,Map 接口是map 系列集合的顶层接口)
public interface Iterator<E> {
//判断容器内是否还有可供访问的元素
boolean hasNext();
//返回迭代器刚越过的元素的引用,返回值是Object,需要强制转换成自己需要的类型
E next();
}
对于大多数实现了Iterable 接口的集合可以多次调用forEach,并将通过元素进行多次传递
list.forEach((obj)->{System.out.println(obj);});
list.forEach(System.out::println);
forEachRemaining 方法获得对应集合的迭代器Iterator,然后可以开始迭代,next()直到达到某个条件,然后使用forEachRemaining()操作该Iterator 上的其余部分
Iterator it=list.iterator();
it.forEachRemaining((obj)->{
System.out.println(obj);
});
foreach 写法(java: for-each 不适用于表达式类型)
所谓的foreach 结构是Iterator 迭代访问的简化写法
public class Test4 {
public static void main(String[] args) {
Person p=new Person();
for(Object obj:p){
System.out .println(obj);
}
}
}
class Person implements Iterable{
@Override
public Iterator iterator() {
return new Iterator() {
@Override
public boolean hasNext() {
return false;
}
@Override
public Object next() {
return null;
}
}; //实际上foreach 结构是通过Iterator 实现遍历集合的
}
@Override
public void forEach(Consumer action) {
Iterable.super.forEach(action);
}
@Override
public Spliterator spliterator() {
return Iterable.super.spliterator();
}
}
Iterator 使用问题
Iterator 的安全失败是基于对底层集合做拷贝,因此它不受源集合上修改的影响。java.util 包下面的所有的集合类都是快速失败fail-fast 的,而java.util.concurrent 包下面的所有的类都是安全失败fail-safe 的。快速失败fail-fast 的迭代器会抛出ConcurrentModificationException 异常,而安全失败fail-safe 的迭代器永远不会抛出这样的异常。
- fast-fail 事件产生的条件:
当多个线程对Collection 进行操作时,若其中某一个线程通过iterator 去遍历集合时,
该集合的内容被其他线程所改变;则会抛出ConcurrentModificationException 异常。 - fast-fail 解决办法:
通过java.util.concurrent 集合包下的相应类去处理,则不会产生fast-fail 事件。
Collection co=new ArrayList();
for(int i=0;i<20;i++)co.add(i);
new Thread(()->{
Iterator it=co.iterator();
while(it.hasNext()){
System.out.println(it.next());
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.currentThread().sleep(110);
co.add(100L);
- fail-safe 安全失败:
不抛出异常,不会中断程序的执行
Collection co = new CopyOnWriteArrayList();
for(int i=0;i<20;i++) co.add(i);
new Thread(()->{
Iterator it=co.iterator();
while(it.hasNext()){
System.out.println(it.next());
Thread.currentThread().sleep(100);
}
}).start();;
Thread.currentThread().sleep(110);
co.add(100L);
Collection 接口方法
Collection 接口用于表示无序、允许重复的集合
构建Collection 对象
使用Collection 接口定义变量,使用new 创建具体的实现类
Collection co=new ArrayList();
由于使用接口定义变量,所以可以new 任何Collection 接口的实现类,例如HashSet、Vector、LinkedList 等【多态性】
//获取集合中的存储元素个数。size 表示实际存储的元素个数
int size();
//向集合中添加元素e
boolean add(E e);
//判断集合中的元素个数是否为0,不能使用这个方法判断引用对象是否为null
boolean isEmpty();
注意:只判断集合中是否没有元素,并不会判断集合是否为null,如果需要判断集合变量是否为null,需要自行编程实现,例如
Objects.nonNull(co)或者co!=null
判断集合中是否包含指定元素o,有true 没有false。不能判断集合中是否包含null 元素。如果编程实现添加add(null)则可以判断正确,默认添加的null 并不能进行判断
boolean contains(Object o);
//Iterable 接口中定义的方法,获取迭代器对象用于遍历集合中的所有元素
Iterator<E> iterator();
遍历Collection 集合中元素的写法
public class Test3 {
public static void main(String[] args) {
Collection co=new ArrayList();
ThreadLocalRandom r=ThreadLocalRandom.current();
for(int i=0;i<10;i++)
co.add(r.nextInt(100));
- 可以使用Iterable 接口提供的获取Iterator 对象进行遍历访问
Iterator it=co.iterator(); //可以将迭代器理解为一个指向集合元素的指针
while(it.hasNext()){ // 判断当前指针所指向的位置后续是否有元素,有true 没有false
Object ele = it.next();//位置指针后移,并获取位置指针所指向的元素
System.out.println(ele);
ele=33L; // 通过迭代器直接修改集合元素无效,除非是原地修改
it.remove();//删除当前位置指针所指向的元素
}
- 使用Iterator 的forEachRemaining 方法遍历集合中的所有元素
it=co.iterator();
it.forEachRemaining((Object obj) ->{
System.out.println(obj);
obj=999; // 通过迭代器直接修改集合元素无效,除非是原地修改
});
- 使用Collection 接口的父接口Iterable 中的方法forEach
co.forEach((Object obj)-> {
System.out.println(obj);
obj=666; // 直接修改集合元素无效,除非是原地修改
});
- foreach 结构
for(Object tmp:co){
System.out.println(tmp);
tmp=888; // 直接修改集合元素无效,除非是原地修改
}
}
}
List 接口
List 接口是Collection 接口的子接口,用于定义有序、允许重复的集合
特征:数据有序【索引编号】允许重复。
- 在Collection 接口的基础上添加了每个元素的索引编号,编号从0 开始到size-1 结束
public interfaceList<E> extends Collection<E>
- 继承Collection 接口,所以Collection接口中的所有方法List都有
新增的方法: 都是和位置有关的方法
//向指定下标位置上添加元素,原始位置上的元素后移
void add(int index, E element);
//删除指定位置上的元素,并且返回被删除的元素
E remove(int index);
//覆盖修改指定位置上的元素
E set(int index, E element);
//获取指定位置上的元素,index 取值返回[0,list.size()),超出范围则越界异常
E get(int index);
//从前向后查找第一个指定的元素,依赖于对象中的equals 进行比较,如果不存在则返回-1
int indexOf(Object o);
//从后向前查找第一个指定的元素,依赖于对象中的equals 进行比较,如果不存在则返回-1
int lastIndexOf(Object o);
List 接口方法的具体使用
List 接口用于表示有序、允许重复的集合构建List 对象
使用List 接口定义变量,使用new 创建具体的实现类
由于使用接口定义变量,所以可以new 任何List 接口的实现类,例如ArrayList、Vector、LinkedList 等【多态性】
List co=new ArrayList();
特殊的迭代器ListIterator
Collection 接口的迭代器为Iterator 类型,只能单向迭代
ListIterator<E> listIterator();
public interface ListIterator<E> extends Iterator<E> {
//判断位置指针之前是否有元素,有true 没有false
boolean hasPrevious();
//获取前一个元素
E previous();
//获取下一个元素的下标序号值
int nextIndex();
//获取前一个元素的下表序号值
int previousIndex();
//修改当前位置指针指向的元素,新值e 替换原始数据
void set(E e);
//在当前位置上添加一个新元素,原始位置上的数据后移
void add(E e);
}
Iterator 接口中的修改方法只有remove,但是ListIterator 接口中有修改set、新增add 和删除remove
数组和集合的比较
针对Java 中的数组定长,Java 提出了集合框架,实现了一种变长存储数据的容器———集合
数组不是面向对象的,存在明显的缺陷,集合弥补了数组的缺点,比数组更灵活更实用,而且不同的集合框架。
类可适用不同场合:
- 数组能存放基本数据类型和对象,而集合类存放的都是对象的引用,而非对象本身
- 数组容量固定无法动态改变,集合类容量动态改变
- 数组无法判断其中实际存有多少元素,length 只告诉了数组的容量,而集合的size()可以确切知道元素的个数
- 集合有多种实现方式和不同适用场合,不像数组仅采用顺序表方式
- 集合以类的形式存在,具有封装、继承、多态等类的特性,通过简单的方法和属性即可实现各种复杂操作,大大提高了软件的开发效率。