什么是集合
通俗来讲集合就是用来存放数据的容器。Java的集合中只能存储
对象的引用
(即引用类型),在集合中每一个元素都是一个引用变量,实际内容是存放在堆内或方法区里的;不能存储Java中8种基本数据类型,因为基本数据类型是在栈内存上分配空间的,而栈上的数据随时会被收回,但是因为8种基本数据类型都有对应的包装类(对象),所以当我们将基本数据类型的数据存入集合中,Java会将基本数据类型自动装箱为对应的包装类(如int变为Integer),然后再将引用类型存入到集合中。
集合和数组的区别
数组存放的类型只能是基本数据类型或引用数据类型,集合中存放的数据类型只能是引用数据类型。
数组是静态的,一个数组实例具有固定的大小,一旦创建了不能改变容量。而集合是可以动态扩展容量,可以根据需要动态改变大小。
初始化数组时需要声明存放在数组中的数据类型,而集合可以声明或不声明,当不声明时默认为Object类型,Object是Java中的超类,这就意味着一个集合中可以存放混合类型的数据(既存放String类型的数据又存放Integer类型的数据)
Collection和Collections的区别
java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
Java集合框架的基础接口
Collection
,Collection是集合框架的基础接口之一,在Java中
不提供该接口的任何直接实现。
Map
,Map是一个将key映射到value的对象,一个Map不能包含重复的key,每个key最多映射到一个value。
List
,List是一个可以包含重复元素的集合
Set
,Set是一个不能包含重复元素的集合
Queue
,Queue接口是数据结构中队列结构的实现
Iterable
,Iterable接口的实现类可以对集合进行遍历(如Iterator)
Collection接口相关类图
Map接口相关类图
Java集合框架常用集合类
List接口
实现List接口的集合是有序的,List接口主要有四个实现类:
分别是ArrayList、Vector、LinkedList和CopyOnWriteArrayList
如何遍历一个List集合
for循环遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
Iterator迭代器遍历
//获取迭代器
Iterator iterator = list.iterator();
//判断是否还有元素
while(iterator.hasNext()){
System.out.println(iterator.next());
forEach循环遍历。forEach内部也是采用Iterator迭代器实现集合遍历的,在使用时无需显式地声明Iterator迭代器,但是使用forEach遍历List集合时,不允许在遍历的过程中对集合元素进行删除或者修改。
for (Object o : list) {
System.out.println(o);
在Java集合中提供了一个RandomAccess
(随机存取)接口,如果一个集合实现了该接口,那么该集合就支持随机快速存取,例如ArrayList就实现了该接口,那么它就支持随机快速存取,其底层遍历集合元素时采用的是for循环,遍历或者查询速度都非常快。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
在集合工具类Collections中,binarySearch()二分查找方法,就运用到这一点,在采用该方法进行元素查找是,会先判断集合是否实现了RandomAccess接口从而判断是使用indexedBinarySearch()[索引] 方法还是iteratorBinarySearch()[迭代器] 方法。
public class Collections {
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
return Collections.iteratorBinarySearch(list, key);
Iterator与ListIterator的区别
ArrayList使用迭代器遍历集合时,获取迭代器的iterator()方法是List接口中的 iterator()方法,调用该方法返回的是 Iterator对象
,而LinkedList获取迭代器的iterator()方法是AbstractSequentialList抽象类中的 iterator()方法,在该方法中又调用了AbstractList抽象类中的listIterator()方法返回一个ListIterator对象
。
Iterator和ListIterator区别
Iterator可以遍历 Set 和 List 集合,而ListIterator只能用于遍历List。
Iterator和ListIterator都可实现删除元素,但是ListIterator可以实现遍历时对元素的修改,用set()方法实现。Iterator仅能遍历,不能修改。
Iterator只能单向遍历 [ hasNext()、next()方法)],而ListIterator可以双向遍历(向前/后遍历)[ 从后往前 hasPrevious()、previous()方法 ]。
ListIterator接口继承于Iterator接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。
获取ListIterator方式:List.listIterator()、List.listIterator(int index),index为指定游标的所在的位置。
获取Iterator方式:List.iterator()
ListIterator继承Iterator接口后,自己新增的方法
void hasPrevious() 判断游标前面是否有元素;
Object previous() 返回游标前面的元素,同时游标向前移动一位。
int nextIndex() 返回游标后边元素的索引位置,初始为 0,遍历 N 个元素结束时为 N;
int previousIndex() 返回游标前面元素的位置,初始时为 -1。
void add(E) 在游标前面插入一个元素
void set(E) 更新迭代器最后一次操作的元素为 E,也就是更新最后一次调用 next() 或者 previous() 返回的元素。
void remove()删除迭代器最后一次操作的元素
List<Integer> list = Arrays.asList(1,2,3,4);
ListIterator<Integer> listItr = list.listIterator();
System.out.print("从前向后:");
while(listItr.hasNext())
System.out.print(listItr.next()+ "");
System.out.print("从后向前:");
while(listItr.hasPrevious())
System.out.print(listItr.previous());
fast-fail(快速失败)迭代器
Iterator就是一种fast-fail迭代器,当一个线程通过迭代器遍历List中的元素时,另一个线程修改或删除List中的元素是不允许的(),迭代器会通ConcurrentModificationException
异常阻止这种情况发生,这种迭代器就叫做fast-fail迭代器。
使用forEach循环边遍历边移除元素出现异常
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
for (Integer ele : list) {
list.remove(ele);
System.out.println(list.size());
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
at com.gjy.demo.collection.list.main(list.java:47)
编译后如下:
//编译后:
ArrayList<Integer> list = new ArrayList()
list.add(1)
list.add(2)
list.add(3)
Iterator var2 = list.iterator()
while(var2.hasNext()) {
Integer ele = (Integer)var2.next()
list.remove(ele)
System.out.println(list.size())
从编译后的代码中可以看出,当我们使用forEach循环一边遍历一边移除元素时,底层其实是用iterator迭代器来遍历的,而移除元素用的却是ArrayList的remove方法,这样迭代器无法感知ArrayList中元素的变化,所以,当用ArrayList的remove()方法移除掉一个元素后,下一次迭代器调用next()方法时会调用checkForComodification()方法用于检测迭代器执行过程中是否有并发修改
(判断modCount 和 expectedModCount是否相等 [集合中有一个modCount属性,在初始化迭代器时,modCount的值会赋给expectedModCount,在迭代的过程中,只要modCount改变了,expectedModCount = modCount等式就不成立] ),如果有则抛出ConcurrentModificationException异常,阻止并发修改ArrayList对象。因此,正确的边遍历边移除元素应该使用Iterator迭代器来实现。
Set接口
实现Set接口的集合主要用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。对象的相等本质是根据对象的 hashCode值 ( 在没有重写 hashCode()方法时,调用Object的 hashCode()方法是依据对象的内存地址计算进行计算的 ) 判断的,如果想要让两个不同的对象视为相等的,就必须重写 Object的 hashCode()方法和 equals()方法。
hashCode()与equals()
如果两个对象相等,则hashcode一定也是相同的,Object类中的hashCode默认是根据对象的内存地址计算算出来的int类型的数值。
两个对象相等,对两个equals方法返回true
两个对象有相同的hashcode值,它们也不一定是相等的
综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
Set和List的区别
List , Set 都是继承自Collection 接口。
List 特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。
Set 特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及TreeSet。List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为它是无序,无法用下标来取得想要的值。
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长(自动扩容),查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变(如ArrayList、Vector)。
Map接口
Map接口与List和Set接口不同,没有继承Collection,是一个双列集合,它是由一系列键值对组成的集合,提供了key到Value的映射。在Map接口中保证了key与value之间的一一对应关系,即一个key对应一个value,它不能存在相同的key值(唯一)且不要求key是有序的,但是value值是可以相同的。常用的Map实现类有HashMap、HashTable、ConcurrentHashMap.
以上就是对于Java集合的基本介绍,在接下来的文章中会逐个详细介绍List、Set、Map接口的一些常用实现类,如ArrayList、LinkedList、HashSet、HashMap、ConcurrentHashMap等,欢迎大家关注留意后续内容........
线程池详解
synchronized简介
ThreadLocal,你掌握了吗?
Java异常机制
JDK8新特性之Stream流
同学,你对volatile熟悉么?