引言
- 程序通常在运行时才会根据给定的条件去创建对象,但在此之前程序是无法确定所需对象的数量和确切类型。因此要解决在任意时刻和任意位置创建对象,不能仅依靠创建命名的引用来持有每一个对象,因为我们无法确定它的数量。
数组也许是保存对象(实际是对象的引用)的一种最有效的方式,但是数组局限在于它是固定大小的。而java使用类库提供了一套完整的集合类来解决这个问题。
下面给出java集合类通常会碰到的接口和类的关系图:
图中的虚线框表示接口,实线框表示类。带有空心箭头的虚线表示一个类实现某个特定接口,如:ArrayList实现了List接口;实心箭头虚线表示某个类可以生成箭头所指类的对象,如:任意的Collection可以生成Iterator,List可以生成ListIterator。
粗线框所圈出来的是我们常用四个集合类
集合类的划分
1) Collection
一组”对立”的元素,通常这些元素都服从某种规则
1.1) List必须保持元素特定的顺序
1.2) Set不能有重复元素
2) Map
一组成对的”键值对”对象
Collection和Map的区别:
1) Collection 每个位置只能保存一个元素(对象)
2) Map保存的是”键值对”,就像一个小型数据库。我们可以通过”键”找到该键对应的”值”。
第一部分 Collection
1、List
1)创建对象
List本身作为一个接口不能直接创建对象的,但是可以利用实现它的ArrayList类和LinkedList类来创建对象。另外在使用集合时,我们必须要用到泛型来指定存入集合类当中的参数类型。如果不用泛型指定,那么返回的元素类型将是Object类型,必须要进行类型强转才能得到存入集合之前的类型的元素
1.1)直接创建
//向上转型,它的弊端就是对象引用不能调用ArrayList当中特有的方法
List<String> alist = new ArrayList<String>();
List<String> llist = new LinkedList<String>();
ArrayList<String> al = new ArrayList<String>();
LinkedList<String> ll = new LinkedList<String>();
1.2)利用方法创建
在java,util包中,Arrays类的静态方法asList() 接受一个数组或者逗号分开的元素列表(使用可变参数)并将其转换为一个List对象,但由于他的底层是数组实现,所以由它产生的List对象具有固定大小。
List<String> list = Arrays.asList("java collection".split(" "));
List<Integer> listInt = Arrays.asList(1,2,3);
1.3)ArrayList和LinkedList的构造器
1.3.1)ArrayList有三种构造器:
ArrayList()//无参构造器
ArrayList(Collection<? extends E> c)//该构造器接受一个指定元素类型的Collection对象
ArrayList(int initialCapacity)//int型的参数指定ArrayList对象的初始大小
1.3.2)LinkedList的两种构造器:
LinkedList()
LinkedList(Collection<? extends E> c)
2)两种类型的List区别
ArrayList:长于随机访问,但在List中间插入和移除元素比较慢
LinkedList:快速地在List中间进行插入和删除操作,但在随机访问方面比较慢,但它的特性集较ArrayList更大。
3)List常用的方法
整个集合类的包含的方法比较多,但幸运的是仅从这些方法的名称中我们就很容易判断出它们各自的功能,下面将列举一些在List中常用的一些方法,具体使用请参见官方的API。
4)LinkedList特有方法
LinkedList当中还添加了一些方法,可以使其用作Queue或Stack
- 4.1)Stack
栈是一种后进先出的容器,在java的util包中提供了Stack类。
由于LinkedList也提供了相应的方法来支持栈的行为,因此我们可以通过创建LinkedList对象,调用它的一些方法来实现自定义的Stack类
//用LinkedList实现
import java.util.LinkedList;
public class Stack<T> {
private LinkedList<T> storage = new LinkedList<T>();
public void push(T v){storage.addFirst(v);}
public T peek(){return storage.getFirst();}
public T pop(){return storage.removeFirst();}
public boolean empty(){return storage.isEmpty();}
public String toString(){return storage.toString();}
}
在上示代码当中,push()将对象压入栈。peek()和pop()都是返回栈顶元素,但不同的是,peek()并不会删除栈顶元素,而pop()将栈顶元素删除。
需要特别注意的是,在调用自定义栈时,一定要导入该自定义栈所在的包(若调用类和自定义栈在同一包内则不需要),否则编译器报错。
4.2)Queue
队列是一种先进先出的容器。它的存入顺序和取出顺序是一样的。由于它可以安全的将对象从一个任务传输到另一个任务当中,因此常常被用在在并发编程中。LinkedList实现了Queue接口,并且提供了方法支持队列的行为:
返回类型 | 方法名及参数 | 功能描述 |
---|---|---|
boolean | offer(T t) | 将元素插入队尾 |
T | element() | 取回但不移除表头元素 |
4.2.1)典型队列
下面给出实现典型的Queue的代码实例:
import java.util.*;
public class QueueDemo {
public static void main(String[] args){
//由于LinkedList实现了Queue接口,所以能完成向上转型
Queue<String> queue = new LinkedList<String>();
String[] arr = "the test of QueueDemo".split(" ");
for(String s:arr)
queue.offer(s);
System.out.println(queue);
String getHead = queue.element();
System.out.println(getHead);
System.out.println(queue);
}
}/*output:
[the, test, of, QueueDemo]
the
[the, test, of, QueueDemo]
*/
4.2.1)PriorityQueue(优先级队列)
典型的队列讲究的是先进先出,但是优先级队列是根据对象的优先级顺序(从高到底)来返回元素。 这里所说的优先级实际上是一种排序规则: 当调用PriorityQueue中的offer()来插入对象时,默认的排序将使用对象在队列中的自然顺序,就如下面例子所示一样: import java.util.PriorityQueue; public class PriorityQueueDemo { public static void main(String[] args){ PriorityQueue<String> pri = new PriorityQueue<>(); String[] arr = "the test of queuedemo".split(" "); for(String s:arr) pri.offer(s); System.out.println(pri); } } /*output: * [of, queueDemo, test, the] */ 之所以产生自然排序的情况是因为PriorityQueue类中的offer()在添加对象时,会调用一个siftUp()方法,该方法内部调用默认的比较方法来进行比较排序。 当然PriorityQueue构造器`PriorityQueue(Comparator<? super E> comparator)`允许我们来修改默认的比较顺序。
5)Set
Set最大的特点就是它不保存重复的元素,并且它与Collection具有完全相同的接口,区别仅在于它们的行为不同。Set常被用来测试对象的归属性,即当前对象是否包含在内。
实现Set的常用容器类分别有HashSet,LinkedHashSet,TreeSet
类名称 | 区别描述 |
---|---|
HashSet | 使用散列函数优化了查找速度,元素输出无规律 |
LinkedHashSet | 使用散列函数优化了查找速度,但维持了插入顺序 |
TreeSet | 将元素存储在红-黑树数据结构当中 |
由于Set除了Collection中所有的方法法外没有额外的功能,因此对于它的方法调用此处不再赘述,请读者亲试。
第二部分 Map
Map实际上实现的是不同对象之间的一种映射关系。这种映射关系是通过键-值思想来维护。因此我们可以通过键(一个对象)查找对应的值(另一个对象)。
1、Map的实现
同Collection一样,Map是个接口,通过它本身是无法创建对象的。但是我们可以通过实现该接口的类来创建Map型对象。
1)HashMap
hashCode()是根类Object当中的方法,因此对于所有java对象来说都能产生哈希码来标明自己独一无二的身份。HashMap正是根据每个对象的哈希码(散列码)进行快速查询的,这种方式能够显著地提高性能。
HashMap当中常用的一些方法
返回类型 | 方法名及参数 | 功能描述 |
---|---|---|
void | clear() | 清除Map中所有键值对 |
boolean | containsKey(Object key) | 判断当前Map对象中是否存在参数所传入的键 |
boolean | containsValue(Object value) | 判断当前Map对象中是否存在参数所传入的值 |
Set<Map.Entry<K,V>> | entrySet() | 返回一个Map的视图 |
V | get(Object key) | 根据键查找值 |
Set<K> | keySet() | 返回由当前Map中的键所组成的Set |
V | put(K key, V value) | 向Map中添加键值对 |
V | replace(K key, V value) | 只替换指定键所关联的值 |
下面给出简单的代码示例:
import java.util.*;
public class Maps {
public static void main(String[] args){
HashMap<Integer,String> map = new HashMap<Integer,String>();
map.put(1, "dog");
map.put(2, "sheep");
map.put(3, "duck");
System.out.println("hashmap:"+map);
System.out.println("clone():"+map.clone());
System.out.println(map.isEmpty());
Set<Map.Entry<Integer, String>> set = map.entrySet();
Iterator<Map.Entry<Integer, String>> it = set.iterator();
while(it.hasNext())
System.out.print(it.next()+" ,");
System.out.println(map.replace(1,"chicken"));
System.out.println("HashMap:"+map);
map.clear();
System.out.println(map.isEmpty());
- 2)LinkedHashMap
LinkedHashMap为了提高速度,LinkedHashMap散列化了所有元素。但是在遍历键值对的时候,却又以元素的插入顺序返回键值对(HashMap不能保证这个顺序)
由于LinkedHashMap继承自HashMap,所以其中很多方法的使用都和HashMap的用法类似,这里就不再赘述。
在LinkedHashMap有这样一个构造器:
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
其中initialCapacity是指定LinkedHashMap的初始容量,一般默认构造器是16;loadFactor是负载因子(负载因子是在容量自动增加之前允许哈希表得到满足的度量。 当哈希表中的条目数超过负载因子和当前容量的乘积时,重新排列哈希表(即,内部数据结构被重新构建),以使散列表具有大约两倍的容量。),默认构造器为0.75;accessOrder,该属性为 boolean 型变量,对于访问顺序,为 true;对于插入顺序,则为 false。一般情况下,不必指定排序模式,其迭代顺序即为默认为插入顺序。
使用该构造器,我们可以实现LRU算法(最近最少用算法),因为构造器本身已经实现了按照访问顺序的存储,也就是说,最近读取的会放在最后端,最近未读取到的放在最前端,以便定期清除。
下面给出简单实例:
import java.util.*;
public class Maps_2 {
public static void main(String[] args) {
LinkedHashMap<Integer, String> linkedMap =
new LinkedHashMap<>(16, 0.75f, true);
linkedMap.put(0, "dog");
linkedMap.put(1, "sheep");
linkedMap.put(2, "duck");
linkedMap.put(3, "fish");
//原存储顺序
System.out.println("before:"+linkedMap);
//元素访问
linkedMap.get(0);
//访问后的存储顺序
System.out.println("after:"+linkedMap);
}
}
/*output:
before:{0=dog, 1=sheep, 2=duck, 3=fish}
after:{1=sheep, 2=duck, 3=fish, 0=dog}
*/
3)TreeMap
实际TreeMap是基于数据结构当中的红黑树实现的,它的特点在于经它所得到的结果是有序的(这个次序取决于Comparable或Comparator) 如果说某个自定义类要被用作TreeMap的键,则必须要实现Comparable接口,该接口下仅有一个方法,即:compareTO()
import java.util.*;
class Student implements Comparable<Student> {
public String name;
public int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
// 覆盖Comparable仅有的compareTo方法(告诉 TreeMap 如何排序)
public int compareTo(Student o) {
if (o.score < this.score) {
return 1;
} else if (o.score > this.score) {
return -1;
}
return 0;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("name:");
sb.append(name);
sb.append(" ");
sb.append("score:");
sb.append(score);
return sb.toString();
}
public static void main(String[] args) {
TreeMap<Student,StudentDetailInfo> map = new TreeMap<>();
Student s1 = new Student("1", 100);
Student s2 = new Student("2", 99);
Student s3 = new Student("3", 97);
Student s4 = new Student("4", 91);
map.put(s1, new StudentDetailInfo(s1));
map.put(s2, new StudentDetailInfo(s2));
map.put(s3, new StudentDetailInfo(s3));
map.put(s4, new StudentDetailInfo(s4));
// 打印分数位于 S4 和 S2 之间的人
Map<Student,StudentDetailInfo> map1 = ((TreeMap<Student,StudentDetailInfo>) map).subMap(s4, s2);
for (Iterator<Student> iterator = map1.keySet().iterator(); iterator.hasNext();) {
Student key = (Student) iterator.next();
System.out.println(key + "->" + map.get(key));
}
System.out.println("subMap end");
// 打印分数比 s1 低的人
map1 = ((TreeMap<Student,StudentDetailInfo>) map).headMap(s1);
for (Iterator<Student> iterator = map1.keySet().iterator(); iterator.hasNext();) {
Student key = (Student) iterator.next();
System.out.println(key + "->" + map.get(key));
}
System.out.println("subMap end");
// 打印分数比 s1 高的人
map1 = ((TreeMap<Student,StudentDetailInfo>) map).tailMap(s1);
for (Iterator<Student> iterator = map1.keySet().iterator(); iterator.hasNext();) {
Student key = (Student) iterator.next();
System.out.println(key + "->" + map.get(key));
}
System.out.println("subMap end");
}
}
class StudentDetailInfo {
Student s;
public StudentDetailInfo(Student s) {
this.s = s;
}
public String toString() {
return s.name + "'s detail information";
}
}
上示代码中涉及到TreeMap的几个方法:
返回类型 | 方法名及参数 | 功能描述 |
---|---|---|
SortedMap | subMap(fromKey, toKey) | 生成当前Map的子集,其范围由fromKey(包含)到toKey(不包含)的键确定 |
SortedMap | headMap(toKey) | 生成当前Map子集,由键值小于toKey的所有键值对组成 |
SortedMap | tailMap(fromKey) | 生成当前Map的子集,由键值大于或等于fromKey的所有键值对组成 |
SortedMap是一个接口(TreeMap实现了该接口),它可以确保键处于排序状态。
第三部分 Foreach与迭代器
无论是foreach语法还是迭代器,它们的使用一定建立在Collection对象上,而不适用于Map对象。
在本篇开头的关系图中,我们可以看到Collection对象可以生Iterator,这是由于Collection继承了Iterable接口,由iterator方法产生Iterator对象调用对应的方法,从而实现迭代。
import java.util.*;
public class List_1 {
public static void main(String[] args){
List<String> list =
Arrays.asList("The test for iterator".split(" "));
//方法一
Iterator<String> it = list.iterator();
While(it.hasNext())
System.out.print(it.next());
//方法二
for(Iterator<String> it = list.iterator();it.next();)
System.out.print(it.next());
}
}
foreach语法被广泛地应用于数组的遍历,但是它同时也能应用于任何的Collection对象。准确地说,所有实现了Iterable的类都能被用于foreach语法。
import java.util.*;
public class List_1 {
public static void main(String[] args){
List<String> list =
Arrays.asList("The test for iterator".split(" "));
for(String s: list)
System.out.println(s);
}
}
总结
1、Collection保存单一对象,而Map则保存相关联的键值对;
2、List和数组一样都建立对象跟数字索引的关联,List能自动扩充容量而数组具有固定大小;
3、如果要进行大量的随机访问用ArrayList,需要在表中间插入或者删除元素则用LinkedList;
4、Set不接受重复元素。HashSet提供最快的查询速度,LinkedHashSet在提高查询速度的同时,以插入顺序保存元素。TreeSet保持元素处于排序状态。
5、Map将对象与对象之间形成关联关系。HashMap设计用来快速方访问,TreeMap使得键始终处于排序状态。LinkedHashMap除了保持元素的插入顺序外,也和HashMap一样通过散列提高了访问速度。