持有对象——Java容器
数组是保存一组基本数据类型的最优先选择的数据结构,但是在某些情况下需要保存一系列的自定义对象,Java提供了一套相当完整的容器类来结局这个问题(List、Set、Queue、Map)。这些容器均有自己的特色,如Set可以保证保存的对象互异,Map保存k-v对(关联数组)。容器类可以自动调整自己的容量,因此在编程时,不用担心容器的容量问题(前提是你有足够大的内存)。
一、泛型
容器类在默认情况下是接收Object类的,换种说法,就是接收任何类型的类。泛型的作用就是在创建一个容器对象时,指定该容器对象接收的对象类型。例如:
package test; import java.util.ArrayList; /* * ArrayList不使用泛型,add()接收任何非法的类型。 * orange可以添加到apples中,编译时无错误,运行时出现“类型转换错误”。 * */ public class AppleOrangeWithoutGeneric { public static void main(String[] args){ ArrayList apples = new ArrayList(); Apple apple = new Apple(); Orange orange = new Orange(); apples.add(apple); apples.add(orange); for (int i = 0; i<apples.size(); i++){ ((Apple)apples.get(i)).eat(); } } } class Apple{ public void eat(){} } class Orange{ public void eat(){ } }
在编译时,以上示例代码并无问题,但是会出现运行时错误,因为强制类型转换是非法的。
比较安全一些的做法是在定义apples时指定该ArrayList只能接受Apple的对象。例如:
package test; import java.util.ArrayList; public class AppleOrangeWithGeneric { public static void main(String[] args){ ArrayList<Apple> apples = new ArrayList<Apple>(); Apple apple = new Apple(); Orange orange = new Orange(); apples.add(apple);
//Compile error //apples.add(orange); for (int i = 0; i<apples.size(); i++){ ((Apple)apples.get(i)).eat(); } } } class Apple{ public void eat(){} } class Orange{ public void eat(){ } }
二、分类
- Collection 保存独立对象
- List:必须保持插入的顺序保存数组
- LinkedList
- ArrayList
- Stack
- Set:不能有重复的元组
- HashSet
- TreeSet
- Queue
- List:必须保持插入的顺序保存数组
- Map:保存一组相关对象
- HashMap
- TreeMap
List
List有两种基本的类型,LinkedList在随机访问方面先对比较慢,ArrayList适用于随机访问,但是中间插入和移除元素时比较慢。
基本操作:add() get() equals() remove() retainAll() subList() containsAll() toArray()
迭代器可以提高代码复用程度。迭代器是一个队形,他的工作原理是遍历并选择序列中的对象,使用迭代器可以不用关心List的类型;
1) 使用iterator()方法返回一个Iterator,准备返回序列中的第一个元素。
2)使用next()方法获得当前位置的下一个元素。
3)使用hasNext()方法判断是否到达序列尾部。
4)使用remove()方法将当前元素删除。
迭代器统一了对容器的访问方式,使得遍历序列的操作与程序底层的结构分离。
ArrayList
LinkedList
使用方法较ArrayList更丰富,移除和插入效率更高。LinkedList添加了可以使其用作栈、队列或双端队列的方法。
Stack
先进后出,一种使用了LinkedList的实现:
package test; import java.util.LinkedList; public class StackTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Stack<String> stack = new Stack<String>(); stack.push("str1"); stack.push("str2"); System.out.println(stack.pop()); System.out.println(stack.pop()); } } class Stack<T>{ private LinkedList<T> storage = new LinkedList<T>(); public void push(T t){storage.addFirst(t);} public T pop() {return storage.removeFirst();} public boolean isEmpty() {return storage.isEmpty();} public String toString() {return storage.toString();} }
Set
Set不会保存重复的元素。Set最常被使用的场景是判断归属性,使用Set可以轻易地查询某个对象是否存在某个Set中。HashSet专门对快速查找进行了优化。Set具有与Collection完全一样的接口,除此之外无其他功能。
package test; import java.util.*; public class SetOfInteger { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Random rand = new Random(1); Set<Integer> set = new HashSet<Integer>(); for(int i = 0; i<1000; i++){ set.add(rand.nextInt(30)); } System.out.println(set.toString()); } }
输出
[15, 8, 23, 16, 7, 22, 9, 21, 6, 1, 29, 14, 24, 4, 19, 26, 11, 18, 3, 12, 27, 17, 2, 13, 28, 20, 25, 10, 5, 0]
即使在代码中修改rand的seed值,输出仍然不发生变化,但输出的顺序无规律可言。这是因为set在内部对所存储的值进行了散列。TreeSet将元素维护在一颗红黑树中,因此想要获得有序的元素的话,最好使用TreeSet或者LinkedHaskSet:
package test; import java.util.*; public class TreeSetOfInteger { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Random rand = new Random(1); Set<Integer> set = new TreeSet<Integer>(); for(int i = 0; i<1000; i++){ set.add(rand.nextInt(30)); } System.out.println(set.toString()); } }
输出:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
Queue
队列,典型的先进先出容器。LinkedList实现了Queue的接口,可以向上转型为Queue。offer()方法将一个元素添加到队尾。peek()和element()方法都在不移除的情况下,返回队头,但是peek()在队为空时,返回null,后者在同样情况下返回nosuchelementexception。
Map
存储k-v对。如下面例子,key为某个数字,value是使用random产生的该数字的次数。
package test; import java.util.*; public class Statistics { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Random rand = new Random(100); Map<Integer,Integer> map = new HashMap<Integer,Integer>(); for(int i = 0; i<1000;i++){ int tmp = rand.nextInt(10); Integer count = map.get(tmp); map.put(tmp, count==null ? 1 : ++count); } System.out.println(map.toString()); } }
输出:
{2=96, 4=92, 9=102, 8=108, 6=99, 1=95, 3=105, 7=95, 5=99, 0=109}
Map中的元素也可以是Map,类似于多维数组。
总结
- Collection保存单一的元素,Map保存k-v对。
- 有了泛型,可以指定容器存放数据的类型,避免强制类型转换。
- Collection和Map都可以自动调整内存。
- List(Collection的导出类)有序,类似于数组,可用数字脚标访问元素。
- 大量随机访问使用ArrayList,大量增/删操作使用LinkedList。
- Queue和Stack的行为时有LinkedList提供。
- HashMap访问速度快,TreeMap使用红黑树保证键的顺序。LinkedHashMap提供了快速访问并保留了有序。
- Set中元素互异,HashSet提供最快的查询速度,而TreeSet保持元素处于排序状态。LinkedHashSet以插入顺序保存元素。
- 不建议使用Vector、HashTable、Stack