1、Collection接口概述
Collection接口是Java集合里的两大根接口之一,且Collection接口仅继承了Iterable接口。Java将存放一系列元素(非k-v值)的数据结构应具备的通用方法提炼出来,在公共接口Collection里声明这些方法,供不同的集合类去实现Collection接口里的这些方法。
Collection接口的通用方法如下:
package java.util;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public interface Collection<E> extends Iterable<E> {
/**
*返回集合大小,也就是集合中元素的数量
*/
int size();
/**
* 如果集合不包含元素则返回true,即判断集合是否为空
*/
boolean isEmpty();
/**
* 判断集合是否存在某个对象,注意这里参数是个Object,并没有限制为E或其子类
*/
boolean contains(Object o);
/**
* 返回一个迭代器iterator。并没有说明元素的迭代顺序,除非特别的集合有这个要求。
*/
Iterator<E> iterator();
/**
*将集合转为对象数组,注意这里不是元素数组而是一个Object数组。
*如果集合保证是有序的,那么通过迭代器返回数组有相同顺序
*返回的数组是安全的,也就是说集合有自己的引用,数组开辟新的堆内存,也有自己的引用。所以调
*用者可以随意操作返回的数组。
*这个方法是数组和列表之间的桥梁
*/
Object[] toArray();
/**
* 返回一个集合元素类型的数组。如果集合满足指定的数组并且有足够的空间,则在其中返回此集合
* 否则返回此集合大小的新数组。
* 如果集合有序,那么返回此集合迭代器遍历顺序的数组
* 如果数组大小比集合元素多,那么在数组满足集合元素后在末尾设置为null
*
* 如果在这个集合中指定数组运行时类型不是运行时元素类型的超类,那么抛ArrayStoreException异常
* 如果指定数组为空,则抛出NullPointerException
*/
<T> T[] toArray(T[] a);
//修改操作
/**
* 确保此集合包含特定的元素类型。
* 如果此集合增加元素成功返回true。
* 如果此集合不允许有重复元素并且已经包含所传参数,那么返回false
*
* 支持此操作的集合可能会限制向该集合添加哪些元素。特别的,有些集合会拒绝null元素,有些
* 会对要增加的元素强加一些限制。
* Collection实现类应该在文档中明确指出所有的限制。
*
* 如果集合以除已经包含元素之外的任何原因拒绝添加特定元素,则必须抛出异常
*(而不是返回false)。这保留了集合在此调用返回后始终包含指定元素的不变式。
*/
boolean add(E e);
/**
* 如果此集合中存在此元素,那么移除一个特定元素类型的实例。更正式的说,如果集合中包含一个或多个这样的元素,
* 那么删除这样的元素(o==null?e==null:o.equals(e))。如果集合包含指定的元素(或集合因调用而发生改变),那么返回true。
*
* 如果指定元素的类型和集合不相容,抛出ClassCastException异常(可选的限制条件)
* 如果指定元素是null并且这个集合不允许null元素存在,那么抛出NullPointerException异常(可选的限制条件)
* 如果此集合不支持remove操作那么抛出UnsupportedOperationException异常(可选的限制条件)
*/
boolean remove(Object o);
//容量操作
/**
* 如果this集合包含指定集合的所有元素,返回true
* c集合必须要检查是否被包含在this集合
* 如果指定元素的类型和集合不相容,抛出ClassCastException异常(可选的限制条件)
* 如果指定元素是null并且这个集合不允许null元素存在,那么抛出NullPointerException异常(可选的限制条件)
*/
boolean containsAll(Collection<?> c);
/**
* 将指定集合的所有元素到this集合中(可选的操作)。
* 如果指定的集合在操作进行时被修改了,那么此操作行为未定义。
* (这意味着如果指定的集合是这个集合,并且这个集合是非空的,那么这个调用的行为是未定义的。)
*
* @参数:c集合包含了要被添加到这个集合的元素
* @返回:如果调用结果改变了this集合返回true
* @throws:如果 addAll操作不被此集合支持,那么抛出UnsupportedOpertaionException异常
* @throws: 如果指定集合包含了空元素而this集合不允许有空元素,那么抛出NullPointerException异常
* @throws:如果this集合阻止hiding集合元素类型添加,那么抛出ClassCastException异常
* @throws:如果指定集合的元素的某些属性阻止将其添加到此集合,则抛出IllegalArgumentException
* @throws:由于插入限制,如果不是所有元素都可以在此时添加,则抛出IllegalStateException异常
*/
boolean addAll(Collection<? extends E> c);
/**
* 移除此集合中所有的包含在指定集合中的元素(可选的操作)。调用过此函数之后,那么此集合和指定集合将不再包含相同元素。
*
* @param:包含要从该集合中删除的元素的c集合
* @return:如果此集合因调用而更改那么返回true
* @throws:如果此集合不支持removeAll方法,则抛出UnsupportedOperationException
* @throws:如果集合中一个或多个元素的类型与指定集合不兼容,则抛出ClassCastException(可选的操作)
* @throws:如果该集合包含一个或多个空元素,且指定的集合不支持空元素(optional),或者指定的集合为空,
* 则抛出NullPointerException异常
*/
boolean removeAll(Collection<?> c);
/**
* 移除此集合中所有符合给定Predicate的元素。在迭代期间或由Predicate引发的错误或运行时异常将被转发给调用方
* @implSpec
* 默认实现使用其迭代器遍历集合的所有元素。每一个匹配的元素使用iterator.remove()来移除。
* 如果集合的iterator不支持移除将会抛出UnsupportedOperationException异常在匹匹厄到
* 第一个元素时。
* @param 过滤一个predicate
* @param 筛选要删除的元素返回true的Predicate
* @return 如果任何一个元素被删除返回true
* @throws 指定过滤器为空,抛出NullPointerException
* @throws 如果元素没有被删除,或者移除操作不支持,
* 则立即抛出UnsupportedOperationException异常
* @since 1.8
*/
default boolean removeIf(Predicate<? super E> filter) {
//如果filter为null抛出NullPointerException
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
//通过迭代器遍历删除,只要删除一个removed就为true
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
/**
* 只保留此集合中包含在指定集合中的元素(可选的操作)。
* 也就是说,此集合中不包含在指定集合中的所有元素。
* @param:要保留的元素的集合c
* @return:如果此集合改变了返回true
* @throws:如果此集合不支持retainAll,则抛出UnsupportedOperationException异常
* @throws:如果集合中一个或多个元素的类型与指定集合不兼容,则抛出ClassCastException(可选的操作)
* @throws:如果该集合包含一个或多个空元素,且指定的集合不支持空元素(optional),或者指定的集合为空
*/
boolean retainAll(Collection<?> c);
/**
* 移除此集合中所有元素(可选操作),调用此方法后集合里将为空。
* @throws: 如果此集合clear操作不支持将会抛出UnsupportOperationException异常。
*/
void clear();
//比较和hash
/**
* 比较指定的对象与此集合是否相等
*
*/
boolean equals(Object o);
/**
*返回这个集合的hashCode。当集合接口没有对Object.hashCode方法的一般协议做任何规定,编程
*人员应该注意在重写equals方法时必须重写hashCode方法,以便满足一般协议对这个
*Object.hashCode方法。特别的,c1.equals(c2)表明c1.hashCode()==c2.hashCode()。
*/
int hashCode();
/**
*一下1.8新增的Spliterator,暂不研究
*/
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
}
AbstractCollection是一个实现了Collection接口的抽象类,Collection接口中声明的方法,比如contains、isEmpty等方法在AbstractCollection抽象类中均已实现,其他集合类继承该抽象类复用里面已实现的方法即可。
Collection接口具体的派生接口和实现类如下图:
下面分别介绍List接口分支和Set接口分支。
2、List接口
Collection接口有两大派生接口:List和Set。List接口实现类的集合,元素有序且可以重复,集合中的每个元素都可以通过索引(index)一一对应。List接口继承了Collection接口,并新添加了一些根据索引操作集合里元素方法:
// 在index位置插入元素,后面的元素都往后移一个元素
add(int index, Object ele);
// 在index位置插入集合
boolean addAll(int index, Collection eles);
// 返回index位置的元素
Object get(int index);
// 返回list集合中第一次出现o对象的索引位置,如果list集合中没有o对象,那么就返回-1
int indexOf(Object obj);
// 返回list集合中最后一次出现o对象的索引位置,如果list集合中没有o对象,那么就返回-1
int lastIndexOf(Object obj);
// 删除index位置的元素
Object remove(int index);
// 设置index位置的元素
Object set(int index, Object ele);
// 获取集合的子集
List subList(int fromIndex, int toIndex);
// 获取list迭代器
ListIterator<E> listIterator();
// 初始化一个指向list中索引为index的迭代器
ListIterator<E> listIterator(int index);
上面新增的方法和接口,重点介绍一下List接口特有的listIterator(),它与Iterator接口最大的不同之处在于,iterator只能正向遍历元素,listIterator()可以返回前面的元素,且listIterator支持边遍历,边修改(set和add)list,listIterator接口有两种形式:
ListIterator<E> listIterator();
// 初始化一个指向list中索引为index的迭代器
ListIterator<E> listIterator(int index);
listIterator接口的API如下:
// 正序迭代查看是否有下一个元素有返回true没有false
boolean hasNext();
// 返回当前光标位置的下一个元素(注意,当迭代器位置处于列表最后一个元素的右面的时候,调用next()方法会出现异常,因为光标后面已经没有元素了)
E next();
// 反序迭代
boolean hasPrevious();
// 反序返回当前光标位置的上一个元素
E previous();
// 正序获取最后一次执行next()方法返回的元素的下标
int nextIndex();
// 反序获取最后一次执行previous()方法返回的元素的下标
int previousIndex();
// 从列表中删除最后一次执行next()或previous()方法返回的元素。
// (注意:1.调用remove()方法之前,一定要有next()或previous()方法执行,否则报错:java.lang.IllegalStateException
// 2.执行next() 或 previous()之后与执行remove()之前,不能执行add(E)方法,否则报错:java.lang.IllegalStateException)
void remove();
// 用参数元素代替next() 或 previous()返回的最后一个元素
// (注意:在调用set(E)方法前,不能调用remove() 和 add(E) 方法,否则会报错:java.lang.IllegalStateException
//因为调用remove()或add(E)方法之后,指针并没有指向哪个元素,只是处于了一个位置)
void set(E e);
//将指定元素插入到previous()返回的元素与当前光标位置之间(注意:此时调用next()方法,仍然返回的是下一个元素,调用previous()返回的就是新插入的元素了。 值得注意的是,当光标位置处于0(第一个元素左边)时,调用add(E)方法会出现异常,因为现在光标左边还没有元素)
void add(E e);
ListIterator和Iterator的区别:
- ListIterator初始化时可以指向list集合中的特定index元素,而Iterator不支持;
- Iterator仅能正向遍历,ListIterator既能正向遍历,又能反向遍历;
- ListIterator相比Iterator,多了add、set和remove方法,支持在遍历list集合的同时,对集合进行修改;
- ListIterator可以定位当前的索引位置,通过nextIndex()和previousIndex()实现,Iterator没有此功能;
List接口的实现类,常用的有两个: ArrayList和 LinkedList,以及不常用的Vector,下面分别介绍。
2.1 ArrayList实现类
请参考我这篇博客:https://www.yuque.com/docs/share/1853b631-5f53-477e-9004-fd8a81bd0014?# 《ArrayList》
2.2 LinkedList实现类
请参考我这篇博客:https://www.yuque.com/docs/share/b5938b66-7155-4fbd-9750-14d795cb1f90?# 《LinkedList》
2.3 Vector实现类
Vector目前基本不会使用,如果一定要使用线程安全的Collection接口的实现类,还是推荐使用CopyOnWriteArrayList类。
有关Vector实现类:
- Vector的底层实现是动态数组;
- Vector是线程安全的,涉及到多线程的方法都加了synchronized锁;
- Vector的默认容量大小是10,当Vector容量不足以容纳全部元素时,Vector的容量会增加。若容量增加系数 >0,则将容量的值增加“容量增加系数”;否则,将容量大小增加一倍。
3、Set接口
实现Set接口的集合类是一个不包含重复元素的Collection,最多包含一个null元素。实现Set接口的类主要有HashSet和TreeSet两个,下面分别介绍这两个类。3.1 HashSet实现类
HashSet底层的数据结构是一个HashMap,如下:
HashSet的构造函数中也是new了一个这样的HashMap,以HashSet的无参构造器为例:private transient HashMap<E,Object> map; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object();
HashSet里的元素,其实就是这个底层的HashMap的key的集合,val是上面的PRESENT ,HashSet里的方法,比如add、contains和clear方法,底层都是对这个HashMap进行操作的,如下:public HashSet() { map = new HashMap<>(); }
需要注意的是,HashSet判断两个对象是否相同的方法时继承自Object类的equals方法,这点与TreeSet不同。public boolean contains(Object o) { return map.containsKey(o); } public boolean add(E e) { return map.put(e, PRESENT)==null; } public void clear() { map.clear(); }
3.2 TreeSet实现类
相比HashSet中的元素是无序的,TreeSet可以实现元素的排序,分为自然排序和定制化排序两种。自然排序比如TreeSet里存入Integer对象,会自动从小往大排序。但如果我们自定义一个Entity类,向TreeSet集合里存放Entity类的对象时,Entity类必须实现Comparable接口中的compareTo()方法,该方法不仅可以比较是否相等,还可以比较大小,如果相等返回0,调用者大于参数则返回正数,否则返回负数。
实现了Comparable接口的类的自然序是按类的对象从小到大的顺序排的 ,至于你定义对象怎么比较大小,就是在compareTo()方法里实现的,compareTo方法返回正数,代表this对象大于入参对象,排自然序时会把这个this对象排在入参对象后面。
关于自定义排序,可以参考我这篇博客:https://www.yuque.com/docs/share/aa5c1e15-f929-4010-9dfb-e389715e428e?# 《自定义排序》
举例说明一下:
(1)自然排序
public class Demo {
public static void main(String[] args) {
Set set = new TreeSet<Integer>();
set.add(2);
set.add(8);
set.add(0);
set.add(-1);
System.out.println(set);
}
}
执行结果如下:
[-1, 0, 2, 8]
可见由于Integer对象有自然顺序特性,存入TreeSet后会按从小打到的顺序排序。
(2)自定义排序
以上面的Student类为例,将Student对象存入TreeSet集合中,main方法如下:
public class Demo {
public static void main(String[] args) {
Set set = new TreeSet<Student>();
Student student1 = new Student("Jerry", 1);
Student student2 = new Student("Cissie", 3);
Student student3 = new Student("Bill", 2);
set.add(student1);
set.add(student2);
set.add(student3);
System.out.println(set);
}
}
执行结果如下:
Exception in thread "main" java.lang.ClassCastException: class Basic.EuqalAndHashcode.Student cannot be cast to class java.lang.Comparable (Basic.EuqalAndHashcode.Student is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
at java.base/java.util.TreeMap.compare(TreeMap.java:1291)
at java.base/java.util.TreeMap.put(TreeMap.java:536)
at java.base/java.util.TreeSet.add(TreeSet.java:255)
at Collection.Demo.main(Demo.java:23)
抛了异常,提示Student类需要实现Comparable接口,改造后的Student类如下:
@EqualsAndHashCode
@ToString
public class Student implements Comparable<Student> {
private String name;
private int id;
public Student(String name, int id)
{
this.name = name;
this.id = id;
}
public int compareTo(Student s) {
return this.id - s.id;
}
}
改造后的Student类的比较规则是:Student对象的id值越小,则Student对象对应的排序越靠前(从小到大),执行main方法的结果如下:
[Student(name=Jerry, id=1), Student(name=Bill, id=2), Student(name=Cissie, id=3)]
注意是因为在Student类中引入lombok.ToString才允许我们直接打印Student对象,否则需要覆写Object类里的toString()方法。结果是按照Student对象中id属性从小到大排序的,如果想实现id属性从大到小排序,只需改变Student类里的compareTo方法:
public int compareTo(Student s) {
return s.id - this.id;
}
参考
Java集合(二)、Collection接口介绍 - 黎先生 - 博客园 www.cnblogs.com
Java集合(三)、继承自Collection接口的List接口 - 黎先生 - 博客园 www.cnblogs.com
Java集合(四)、继承自Collection接口的Set接口 - 黎先生 - 博客园 www.cnblogs.com
Java 集合中关于Iterator 和ListIterator的详解 blog.csdn.net
https://www.cnblogs.com/kuoAT/p/6771653.html www.cnblogs.com
Iterator和ListIterator区别以及详细的API解析 - 李玉杰 - 博客园 www.cnblogs.com