所谓容器,就是橙装数据的东西,Java容器的用途就是保存对象,Java容器类被划分为两个不同的分支,也就相对应的存在两个主接口:
Collection接口
Collection是一个接口,它规范了存储对象的一个规则:一个独立的元素的序列,类似于数组,在它的内部,会存储若干个相同类型的元素(对象):
Collection接口规定了以下方法:
1. int size():
2. boolean isEmpty():
3. void clear():
4. boolean contains(Object obj): 是否包含对象obj(使用equals方法判断)
5. boolean add(Object obj):
6. boolean remove(Object obj):移除参数对象(首先判断是否包含这个对象,依然使用equals方法)。
7. Iterator iterator():
8. boolean containAll(Collection c): 集合是否包含参数集合(只要存在一个不属于本集合的对象返回就是false)
9. boolean addAll(Collection c):
10. boolean removeAll(Collection c):
11. boolean retainAll():求取两个集合的交集,赋值给集合本身
12. Object[] toArray(): 将集合转换成一个数组
这里有一点需要注意就是equals方法,因为当判断一个元素是否属于这个集合、获取这个元素的索引以及移除一个元素、求取两个集合的交集等方法都需要调用equals方法,并且,通常情况下,重写equals方法之后尽量也重写这个haseCode方法。
在实现编程中,我们当然不会去拿一个接口来创建对象,在Collection接口下,又有两个实现接口,分别是List、Set,这个两个接口有分别细化了存储对象的规则:
List接口
List接口可以将元素维护在特定的序列中,即List内部的对象是有序的,并且,List存储的对象允许重复,这里需要注意的是判断对象的重复十通过equals方法,我们可以重写对象的equals方法来规定对象相等的条件。
在List集合中的元素都会被分配一个整数型的序号,记载了元素在容器中的位置。
List接口规定了如下方法:
1. Object get(int index):
2. Object set(Onject obj):
3. void add(int index , Object obj):
4. Object remove(int index): List的remove方法是重写父接口的remove方法,List根据元素下角标来移除元素,这样不用担心equals方法带来的影响。
5. int indexOf(Object obj): 返回这个元素处于集合的位置(下角标),也是需要执行equals方法,如果判断集合并不包含这个参数元素,则返回-1.
6. int lastIndexOf(Object o):
在List接口下面,有两个实现类:
ArrayList实现类
ArrayList是List接口的一个实现类,他的底层是使用数组实现的,因此他在内存中元素的位置是连续的;
LinkedList实现类
LinkedList也是List的 一个实现类,他的底层是使用链表实现的,他装的元素在内存中是离散分布的;
LinkedList有一些独特的方法:
1. removeFirst():
2. removeLast():
3. addFirst(Object o):
4. addLast(Object o):
5. getLast():
6. getFirst():
对于ArrayList,由于他的元素是连续排列的,因此它是读快写慢,因此它适用于频繁访问而很少修改的场景中;而LinkedList则相反,它是读慢写快,他在执行某些操作(修改、从中间插入或移除等)比ArrayList效率要高。
对于List集合java.util.Collections封装了一些算法方便我们的使用:
1. void shuffle(List list):随机排列
2. void reverse(List list):逆序排列(LinkedList效率较高)
3. void sort(List list):从低到高排序
4. int binarySearch(List list , Object obj):二分法查找元素
需要注意的是:既然是排序,就需要定义一个对象大小的规则,我们需要让集合元素类实现Comparable接口,重写里面的compareTo方法定义排序规则!
Set接口
set接口是Collection接口的另一个子接口,他不同于List,Set接口所定义的容器规则是:它内部的元素不可以重复(这里的重复是使用equals方法判定的),并且,元素是无序的!
Set中最常被使用的是测试归属性,你可以很容易的询问某个对象是否在这个Set中,正因如此,查找就成为了Set中最重要的操作,因此最常用的Set实现类就是HashSet,它专门对快速查询进行了优化。
Set具有与Collection一样的接口实现,因此它并没有定义任何新方法。相对于Collection,它只是实现不同。
Set接口一共有三个实现类:
HashSet
处于速度考虑,它使用的是散列。查询速度最快,最常用
TreeSet
LinkedHashSet
补充Collection接口:
1. Iterator迭代器
Collection接口拥有一个规范的遍历方式:就是使用Iterator迭代器。
任何容器类,都必须有某种方式进行插入和获取元素,毕竟持有元素是容器最基本的工作,比如,对于List,add()和get()方法就是实现这个功能。但是,容器的种类众多,原本对List编码,但是后来想要同时作用于Set,那么要怎么办呢?总不能从头编写代码吧。迭代器就解决了这个问题,迭代器使用容器却不关心容器的类型,迭代器是一个对象,他的作用就是遍历并选择序列中的对象,我们不必关心容器的底层结构。Iterator的使用方法:
首先我们需要调用Collection对象的iterator方法获取一个Iterator实例,此时iterator处于序列的最开始,它准备好返回序列的第一个元素
然后我们就要开始获取元素了,每次获取元素都需要判断iterator指向的下一个元素是否存在,我们使用hasNext方法,如果返回true,则表示后面还有元素,那么我们就可以调用next方法返回下一个元素,需要注意的是,调用next方法之后iterator游标就移动了一个元素长度,如图所示:
基本操作方式如下代码:
Collection<String> c = new ArrayList<String>();
c.add("a");
c.add("b");
c.add("c");
Iterator<String> i = c.iterator();
while(i.hasNext()) {
System.out.println(i.next());
}
除了上面的三个方法,Iterator还有一个remove方法,它的作用基于是移除由Iterator对象调用的next方法所产生的最后一个对象,这也就以为这在执行remove之前必须先执行next方法,确保确实存在这样的一个元素。
Iterator的remove方法是迭代过程中为以安全的删除元素的方法,不能使用容器自身的remove方法。
组要注意的是,Java中的Iterator迭代器只支持单向移动。
2. 增强的Iterator:ListIterator
ListIterator是Iterator的子类,它只使用与List类型的集合对象,它主要有两个增强点:
首先,相对于Iterator,ListIterator是支持双向移动的,他可通过next和hasNext方法来判断并获取下一个对象,通过hasPrevious和previours方法来判断并获取上一个对象
其次,他可以获取当前位置的前一个和后一个元素的索引,使用的是nextIndex方法和previousIndex。
我们举个例子:
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
ListIterator<String> i = list.listIterator();
while(i.hasNext()) {
System.out.println("获取下一个元素:"+i.next()+
";获取元素的下一个索引:"+i.nextIndex()+
";获取元素的上一个索引:"+i.previousIndex());
if(!i.hasNext()) {
while(i.hasPrevious()) {
System.out.println("获取上一个元素:"+i.previous());
}
break;
}
}
3. 增强For循环
增强for循环也是一种遍历方式,它的作用对象是 数组和Collection实现类的对象,示例:
Collection<String> collection = new ArrayList<String>();
collection.add("a");
collection.add("b");
collection.add("c");
collection.add("d");
for(String s : collection) {
System.out.println(s);
}
增强for循环有各种缺陷,比如对于访问数组,无法精确的访问下标,对于访问集合,不能方便的删除集合中的内容,因为它的底层依然是使用Iterator遍历的,而Iterator提供了便利过程中唯一安全的remove方法,而增强for循环却并没有提供给我们Iterator的remove方法,因此,准确来讲,在使用增强for循环的时候,我们不能移除一个元素。
4. 其他
Array的静态方法asList实现快速的创建集合,asList方法接收数组或者是逗号分隔的元素,并将他们封装成一个List返回,
Map接口
Map是用来存储键值对的容器。
Map中的元素(一对对象)同过键来标识值的,因此键是不能重复的,此处的重复依然是根据euqals方法来确定,但是实际上却是使用hashcode,因此,我们必须同时重写equals方法和hashCode方法。
Map中规范了如下几个方法:
1. Object put(Object key , Object value):若存在相同的键,那么移除这个键上面的值并返回,将新的值赋给这个键
2. Object get (Object key):
3. Object remove(Object key):
4. boolean containsKey(Object key);
5. boolean containsValue(Object value):
6. int size():
7. boolean isEmpty():
8. void putAll(Map m):
9. void clear():
我们可以看到,Map中的键值都是Object类型的(准确是泛型),但是我们依然可以传入基础类型,这是因为Java中的自动打包机制:当我们传入基础类型之后,系统会将这个值打包成对应的包装类型,并传递给这个Map;
Map有三个实现类:
HashMap
TreeMap
LinkedHashMap
Map最经典的HelloWorld是统计Random随机数的分布:
public static void main(String[] args) {
Map<Integer,Integer> map = new HashMap<>();
for(int i=0;i<50;i++) {
int n = (int)(1+Math.random()*(10-1+1));
Integer v = map.get(n);
map.put(n, v == null ? 1 : v+1);
}
System.out.println(map);
}
获取50个1~10的随机数,判断每个数字产生的次数。
容器的数据结构
队列
队列是一种常见的数据模型,队列规则是在给定一组队列中的元素的情况下,规范一个弹出队列元素的规则。
1. 先进先出队列(FIFO):
Queue是一个先进先出的队列,即从容器的一端放入事物,从容器的另一端获取事物,并且,事物的获取与放入的顺序是相同的,它常被当作一种可靠的将对象从程序的某个区域传输到另一个区域的途径。LinkedList实现了这个接口,这就意味着你可以用一个Queue引用来指向一个LinkedList实例。
Queue q = new LinkedList<Integer>();
for(int i=0;i<10;i++) {
q.offer(i);
}
while(q.peek() != null) {
System.out.println(q.poll());
}
Queue常用的方法有5个:
1.1. offer方法是向队列尾输入一个元素;
1.2. peek方法类似于一个在队头的游标,判断是否存在可以被弹出的元素,如果没有了,则返回null(注意:相似的方法是element,不过,如果使用element,那么当队列为空的时候会抛异常)
1.3. poll方法是用于从队列头弹出元素,如果没有元素了,则返回null(相似方法是remove,不过,在使用remove的时候,如果队列已经空了,那么会抛异常)
2. 优先级队列
除了先进先出原则,还有一种队列是通过优先级来判定弹出元素的顺序的,也就是说,队列会根据每个元素的重要度来弹出元素。我们使用的是PriorityQueue类来实现这个功能。
在使用offer方法放入一个元素的时候,对象在队列中就会被排序,优先级队列默认的排序是使用对象在队列中的自然顺序,排序方式是最小值拥有最大优先级(我们可以通过自己实现Comparable来设置这个规则):
List<Integer> list = Arrays.asList(60,90,30,40,80,10,70,50,20,100,20);
PriorityQueue qu1 = new PriorityQueue(list);
System.out.println();
System.out.println("输出顺序:");
while(qu1.peek()!= null) {
System.out.print(qu1.poll()+" ");
}
输出:
输出顺序:
10 20 20 30 40 50 60 70 80 90 100
栈
栈通常指的是后进先出(LIFO)的容器,最典型的例子就是网球桶,最后塞入的球肯定是在最上面,所以会被第一个拿出来,LinkedList已经实现了这种功能:
LinkedList list = new LinkedList<Integer>();
list.push(1);
list.push(2);
list.push(3);
list.push(4);
list.push(5);
list.push(6);
list.push(7);
list.push(8);
while(list.peek() != null) {
System.out.print(list.pop()+" ");
}
注意,peek和pop方法都会返回栈顶T类型的对象,但是peek不会移除这个栈顶对象,pop是移除并返回这个栈顶对象。