集合框架学习
## Collection体系结构图:

泛型
由JDK1.5新引入的特性,本质是参数化类型,把类型作为参数进行传递,可以理解为,将一个类传递到其他的容器当中
语法:
- <T , ...> T称为类型占位符,表示一种引用类型
这里有一些注意点:
-
泛型只能是引用类型,如String,包装类,自定义类型等等
-
不同泛型对象不能相互赋值
//这里说的不同泛型对象是指创建对象的类相同但泛型不同的对象: GenericLearning<String> genericLearning = new GenericLearning<>(); GenericLearning<Integer> genericLearning1 = genericLearning;//这就不行,会报错
泛型的好处:
- 提高代码的重用性
- 防止类型转换异常,提高代码的安全性
泛型类
我们可以在一般类的基础上创建泛型类,写法如下:
public class GenericLearning<T>{}
可以看到,只需要在类名后面加尖括号里面加类名即可实现
调用也比较简单:
GenericLearning<String> genericLearning = new GenericLearning<>();
//JDK1.7之后,构造器尖括号内可以不用带上类名了
泛型接口
接口与类的定义类似:
public interface GenericInterface<T>
但是注意,在接口内不能创建泛型的对象,可以类比为在接口内创建属性,接口内的属性都是public static final,也就是静态常量。而泛型要是创建为静态常量,就必须要初始化:
T t = new T();
问题来了,引入的泛型我们并不知道它的构造器究竟是什么样子,换个说法来说,我们不知道它有没有无参构造器,就不一定能够这样创建,这样就报错了,这就与之前的说法矛盾了。因此,注意一点,泛型接口内不能创建泛型的对象。
然后就是,接口在被类继承的时候,需要在继承时表明泛型的确切内容:
public class GenericLearning implements GenericInterface<String> {}
//若继承时类是泛型类,则可以与泛型类共用泛型而不需指明:
public class GenericLearning<T> implements GenericInterface<T> {}
泛型方法
方法的定义:
public <T> void genericMethod(){}
但是调用方法的时候就不用加<>,按照正常的方法调用即可
所以一般在方法的形参内加入T的对象:
public <T> void genericMethod(T t){}
使得在调用方法时,用实参来确定T的类型:
test.genericMethod("abc");//T为String
test.genericMethod(123);//T为Integer
泛型集合
是一种参数化类型,是类型安全的集合,这与之前的不同,泛型能使集合元素的类型一致。
eg.
ArrayList<String> arrayList = new ArrayList<>();
当此时定义String为泛型,此时集合内只能有String类型的集合元素。
Collection父接口
代表一组任意类型的对象,无序,无下标,不能重复
但是:由于Collection为父接口,不能直接创建对应的对象,只能通过继承的子类来创建,所以当使用子类构造方法初始化,且子类具有可重复性质的时候,其内部的元素也是可重复的
操作如下:
添加元素:
//一般添加元素
collection.add("pear");
collection.add("strawberry");
//添加指定集合的元素
collection.addAll(collection_2);
删除元素:
//一般删除元素
collection.remove("pear");
//删完元素
collection.clear();
遍历:
-
增强for循环遍历:
//for循环需要用局部变量衡量遍历者的长度 //增强for就免除了这一点 for (Object object: collection) { System.out.println(object); } -
迭代器遍历
迭代器Iterator,专门面向Collection集合的,但这只是一个接口
Iterator it = collection.iterator(); while(it.hasNext()){ System.out.println(it.next()); } //hasNext(); 判断是否有元素,如果有则移动指针到下一个元素区域 //next(); 读取元素内容 //remove(Object o); 移除Collection集合内的某元素 /* 注意,当迭代器使用途中,不可使用Collection接口的remove()以及add()等方法直接改变集合的内容,否则会报错 只能使用迭代器自带的remove()方法 */
判断:
System.out.println(collection.contains("pear"));//有没有
System.out.println(collection.isEmpty());//是否空
List子接口
特点:有序,有下标,元素可以重复,也就是数据结构中的线性表
操作与Collection集合大致相同,其中有一些不同:
-
遍历操作中,由于List集合有下标,所以使用for循环也可以遍历:
for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } -
遍历操作使用迭代器时,可以使用Collection对应的Iterator迭代器,也可以使用列表迭代器ListIterator
//列表迭代器不像Collection迭代器,它可以从前往后,也可以从后往前迭代 //使用next()或者previous()进行遍历 //且可以添加,删除,修改元素 ListIterator listIterator = list.listIterator(); while (listIterator.hasNext()){ System.out.println(listIterator.hasNext()); }//正向遍历 //正向遍历后,指针指到最后了,所以可以直接从后往前遍历 while (listIterator.hasPrevious()){ System.out.println(listIterator.hasPrevious()); }//逆向遍历 -
由于List集合元素有下标,可以获取元素对应的下标位置:
System.out.println(list.indexOf("apple"));
由结构图可知,List下实现类有三个:
ArrayList
特点:
- 数组结构实现,查询快,增删慢,需要开辟连续的空间
- 运行效率快,线程不安全
与上面说的相同,它与List的常用实现方法基本一致。
深入分析一下,从源码入手,注意到一些重要的参量:
- elementData 集合容量
- DEFAULT_CAPACITY = 10 默认容量
- size 实际元素个数
注:容量和实际元素个数不一样,容量指集合能装多少元素,size指实际装多少;而对于容量,当ArrayList集合内没有任何元素的时候,容量为0,一旦有添加元素,容量则为默认容量10
因此,这里提一下add()方法,这个是添加元素的方法,查看源码:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
简单而言,就是容量变化,size加一,将元素加入集合,完毕
那么容量变化是怎么做到的?点进ensureCapacityInternal(size + 1):
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}//3
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}//1
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}//2
呃啊,三个方法,看起来有点晕,我标了一下记号,简单来说,1调用了2,而2的参数是3的计算结果。
3方法的意思是,如果容量为空容量,那么那么取该参数为(默认容量)和(实际元素个数+1)两者的最大值,否则就直接取实际元素个数+1。
2方法直接看下面,若实际元素个数+1后大于容量,就执行一个grow()方法,这个方法才是关键,他会改变集合的容量:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
简单来说,创建一个新的容量值,这个新的容量值通过一系列计算判断而得到,最终令elementData的大小改变。(计算过程会有很多的情况,这里不过赘述)
Vector
特点:
- 数组结构实现,查询快,增删慢
- 运行效率慢,线程安全
与上面说的相同,它与List的常用实现方法基本一致。
这里演示一下elements()方法以及Enumeration类,这个是枚举器,一般在JDK1.2之前使用这个遍历,写法和迭代器相似:
Vector vector = new Vector();
Enumeration en = vector.elements();//枚举器
while(en.hasMoreElements()){
String s = (String) en.nextElement();
System.out.println(s);
}
LinkedList
特点:
- 链表结构实现,增删快,查询慢,不需空间连续
- 是双向链表结构
Set子接口
特点:无序,无下标,元素不可重复
所谓元素不可重复,是指对象是否一致,包括元素内容和地址等完全一致。
HashSet
-
基于HashCode计算元素存放的位置
-
当存入元素HashCode相同时,会调用equals进行确认,如果结果为true,即重复元素,则拒绝后者存入;如果false,则引出链表接上该元素
注:存入过程为:先执行hashCode()函数,再执行equals()方法。这里用到hash查找的技术,根据内容设计hash函数计算元素存放的位置,使用的处理冲突的办法为拉链法,每个位置引出一串链表,用于存储hashcode相同但实际内容不同的对象
hashCode()函数的重写一般会以这个形式:
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
至于prime为什么设计为31,有观点说:
- 31为质数,可以减少散列冲突
- 31能够提高执行效率——31*i = (i<<5)-i
TreeSet
基于红黑树来进行元素存储,特征:
- 基于排列顺序实现元素不重复
- 实现了SortedSet接口,对集合元素自动排序。
- 元素对象的类型必须实现Comparable接口,指定排序的规则。
- 通过CompareTo方法确定是否为重复元素。
补充:
Comparator接口:比较器,可以实现定制比较
//创建集合后直接指定比较规则
TreeSet<Student> treeSet1 = new TreeSet<>(new Comparator<Student>() {//需要实现compare方法的方法体
@Override
public int compare(Student o1, Student o2) {
int n1 = o1.getAge() - o2.getAge();
int n2 = o1.getName().compareTo(o2.getName());
return n1==0?n2:n1;
}//这里是指比较规则为,先比较年龄,在比较名字
});
Map体系结构图

Map接口
特点:
- 用于存储键值对(Key - Value)
- 键:无序无下标,不允许重复
- 值:无序无下标,允许重复
常用的操作方法:
添加元素
map.put("1","你好");
map.put("2","谢谢");
map.put("3","小笼包");
map.put("4","再见");
删除元素
map.remove("2");
遍历
//1.使用keySet()
Set<String> keySet = map.keySet();
for (String key:
keySet) {
System.out.println(key + " " + map.get(key));
}
//2.entrySet()方法
Set<Map.Entry<String,String>> entries = map.entrySet();
for (Map.Entry<String,String> entry:
entries) {
System.out.println(entry.getKey()+" " + entry.getValue());
}
/*
下者效率高于上者,entry表示一个映射对,它调取value数值会更快
这里下面的Set<Map.Entry<String,String>>表示将整个entry对保存在Set集合中
*/
判断
System.out.println(map.containsKey("1"));
HashMap实现类
特点:线程不安全,运行效率快,允许用null作为key或者value
注:使用hashMap()构造方法初始化HashMap对象,构造出的对象默认初始容量为16,默认加载因子0.75。——加载因子就是数量到容量的0.75时就会扩容
源码分析:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//hashMap初始容量大小
static final int MAXIMUM_CAPACITY = 1 << 30;//hashmap的数组最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子
static final int TREEIFY_THRESHOLD = 8;//在链表长度大于8时。调整成红黑树
static final int UNTREEIFY_THRESHOLD = 6;//链表长度小于6,红黑树调整成链表
static final int MIN_TREEIFY_CAPACITY = 64;//链表长度大于8且hash桶元素个数大于64调整成红黑树
transient Node<K,V>[] table;//大小
transient int size;//个数
HashTable实现类(了解)
JDK1.0版本,线程安全,运行效率慢,不许用null作为key或是value,初始容量是11,默认加载因子0.75
它有一个子类Porperties:它要求key,value均为String类型,通常用于配置文件的读取
TreeMap
实现了SortedMap接口,可以对key自动进行排序
TreeMap与TreeSet一样,也可以进行定制比较:
TreeMap<String,String> treeMap = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int n1 = o1.compareTo(o2);
return n1;
}
});
TreeSet的添加,创建操作,本质上是调用的TreeMap的操作:
private transient NavigableMap<E,Object> m;
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeSet() {
this(new TreeMap<E,Object>());
}//构造函数
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}//add方法
Collections工具类
- Collections.sort(List
list)——排序,默认顺序为升序 - Collections.binarySearch((List
list , Object key)——二分查找 - Collections.copy(List
dest , List src)——复制,将src的内容粘贴到dest里面 - Collections.reverse(List
list);——反转 - Collections.shuffle();——打乱元素
补充:
-
list转成数组:
ArrayList<String> arrayList = new ArrayList(); String[] arr = arrayList.toArray(new String[0]); //注,要用泛型规定集合元素的种类,否则数组的类型一定为Object -
数组转成集合
List list = Arrays.asList(arr);
//这个集合是一个受限static集合,不能添加和删除操作,只能进行修改
//不要用基本类型创建的数组转集合,请使用引用类型,如包装类

浙公网安备 33010602011771号