集合
1 课程名称:集合
2 知识点概述
2.1、上次课程
2.2、作业讲解
2.3、本次预备讲解的知识点
1、 Collection接口、List接口、Set接口的作用及关系
2、 Map接口的作用
3、 集合的四种输出方式及使用区别
4、 集合的常用操作子类
3、具体内容
3.1、认识类集
在编程时,常常需要集中存放多个数据,当然我们可以使用数组来保存多个对象。但数组的长度不可变化,一旦在初始化数组是指定了数组的长度,则这个数组长度是不可变的,如果需要保存个数变化的数据,数组就有点无能为力了;而数组也无法保存具有映射关系的数据,如成绩表:语文--79, 数学—80,这种数据看上去像两个数组,但这两个数组直接的元素具有一定的关联关系。
为了保存数量不确定的数据,以及保存具有映射关系的数据(也称为关联数组),java提供了集合类,集合类主要负责保存,盛放其他数据,因此集合类也被称为容器类。所有的集合类都位于java.util包下。
Java的集合类主要由以下接口派生而出:
1. Collection系类接口——单值操作接口
a) Collection接口派生出Set、Queue、List三个子接口
2、 Map系类接口——对值操作接口,由key-value对组成
常用实现类为:HashMap、properties、SortedMap
此外还经常用到如下接口:
1、SortedSet、SortedMap排序的操作接口
2、Interator、ListInterator输出集合中元素的接口
结构如图:
三种常用集合特点
|- Set集合无序不能重复
|- List集合有序可重复
|- Map集合Key不可重复、value可重复
3.2、Collection接口
3.2.1、collection接口的定义
Colletion接口的定义
public interface Collection<E> extends Iterable<E> |
在JDK1.5之后,collection接口使用了泛型的定义,在操作时必须指定具体的操作类型,这样可以保证类操作的安全性,避免发生ClassCastException异常
Collection接口是单值存放的最大父接口,可以向其中保存多个单值(单个对象)数据,Collection接口里定义了如下操作集合元素的方法:(详见API)
No | 方法 | 类型 | 描述 |
1 | public boolean add(E e) | 普通 | 向集合中添加元素 |
2 | public boolean addAll(Collection<? extends E> c) | 普通 | 向集合中添加一组数据,泛型指定了操作上限 |
3 | public void clear() | 普通 | 清空所有集合中的所有元素 |
4 | public boolean contains(Object o) | 普通 | 判断是否有指定的内容,查找 |
5 | public boolean containsAll(Collection<?> c) | 普通 | 查找一组数据是否存在 |
6 | public boolean equals(Object o) | 普通 | 比较对象是否相等 |
7 | public int hashCode() | 普通 | 返回hash码 |
8 | public boolean isEmpty() | 普通 | 判断集合是否为空 |
9 | 普通 | 为interator接口实例化,迭代输出 | |
10 | public boolean remove(Object o) | 普通 | 从集合中删除指定的对象 |
12 | public boolean removeAll(Collection<?> c) | 普通 | 从集合中删除一组对象 |
13 | public boolean retainAll(Collection<?> c) | 普通 | 从集合中保留指定的集合 |
14 | public int size() | 普通 | 获取集合中元素的个数 |
15 | public Object[] toArray() | 普通 | 取得集合中全部的元素,以数组的形式返回 |
16 | public <T> T[] toArray(T[] a) | 普通 | 返回包含此 collection 中所有元素的数组,可指定返回数组的类型 |
在一般开发中,往往很少直接直接使用Collection接口进行开发,基本上都是是其使用子接口,子接口主要有List、set、Query和StortedSet
3.2.2、collection子接口的定义
Collection接口虽然是集合的最大接口,但如果直接使用Collection接口进行操作,则表示操作意义不明确,所以在java开发中不提倡直接使用collection接口,主要子接口介绍如下:
|- List:可以存放重复的内容
|- Set:不能存放重复内容
|- Queue:队列接口
|- SortedSet:可以对集合中的数据进行排序
3.3、List接口
List是Collection的子接口,其中可以保存各个重复的内容,此接口定义如下:
public interface List<E> extends Collection<E> |
但与Collection不同的是,在List接口中大量的扩充了Collection接口,拥有了比Collection接口中更多的方法定义,其中有些方法还比较常用。
List对Collection接口的扩展方法如下:详见API
No | 方法 | 类型 | 描述 |
1 | 普通 | 将元素element放入在List集合的index处 | |
2 | public addAll | 普通 | 将集合c所包含的所有元素都放入在List集合的index出,泛型指定了操作上限 |
3 | public E get(int index) | 普通 | |
4 | 普通 | 返回对象o在集合中第一次出现的位置索引,如果此集合不包含该元素,则返回 -1。 | |
5 | public Boolean containsAll(Collection<?> c) | 普通 | 查找集合C中的数据是否存在指定集合中存在 |
6 | public int lastIndexOf(Object o) | 普通 | 返回此集合中最后出现的指定元素的索引;如果集合不包含此元素,则返回 -1 |
7 | public ListIterator<E> listIterator() | 普通 | 返回listIterator实例 |
8 | public ListIterator<E> listIterator(int index) | 普通 | 返回从指定位置开始的listiterator实例 |
9 | public E remove(int index) | 普通 | 从集合中删除指定位置的元素 |
10 | 普通 | 用指定元素替换列表中指定位置的元素 | |
12 | 普通 | 返回集合中指定的 下标fromIndex(包括 )和 toIndex(不包括)之间的子集合, |
如果使用想要使用此接口,则需要通过其子类进行实例化
3.3.1、List接口的常用子类
1、子类ArrayList
ArrayList是List的子类,可直接通过对象的多态性为List接口实例化,此类的定义如下:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable |
可看出ArrayList继承了AbstractList,AbstractList类的定义如下:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> |
此接口实现了List接口,所以可以直接使用ArrayList为List接口实例化
示例代码:(向集合中添加元素)
ArrayList<Integer> obj = new ArrayList<Integer>(); obj.add(23); obj.add(33); obj.add(44); obj.add(55); |
说明:
从程序运行中可以发现,使用List中的add(int index,E element)方法可以在集合中指定位置增加元素,而其他两个add()方法只能在集合的最后进行内容的追加
示例代码:(删除元素)
array.remove(new Integer(33)); |
说明:
可同下标和对象的方式直接对集合进行删除
示例代码:(输出List中的内容)
Iterator<Integer> iter = array.iterator();
while (iter.hasNext()) System.out.println(iter.next()); |
说明:
List集合增加到的顺序就是输出后的顺序,本身顺序不会改变
实例代码:(将集合变为对象数组)
Object[] intarr; intarr = array.toArray() |
示例代码:(集合中的其他操作)
for (Object obj: intarr) System.out.println(obj); } |
2、LinkedList子类、Queue接口
LinkedList表示链表的操作类,即java中已经为开发者提供好了一个链表程序,开发者直接使用即可,无需重复开发,LinkedList类的定义如下:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable |
此类实现了List接口,但同时也实现了Queue接口。Queue表示的是队列操作接口,采用FIFO(先进先出)的操作方法,就好像一队人排队那样,队列中有对头和队尾,队头永远指向新加入的对象,Queue是Collection的子接口,其定义如下:
public interface Queue<E> extends Collection<E> |
Queue接口定义的方法如下:详见API
NO | 方法 |
|
1 |
| |
2 | element | 获取但不移除此列表的头(第一个元素)。 |
3 |
| |
4 | peek | 获取但不移除此列表的头(第一个元素)。 |
5 | poll | 获取并移除此列表的头(第一个元素) |
6 | remove |
|
在LinkedList类中除了实现以上的方法外,还提供了如下方法:详见API
NO | 方法 |
|
1 | 将指定元素插入此列表的开头。 | |
2 | 将指定元素添加到此列表的结尾。 | |
3 |
| |
|
| |
|
|
示例代码:(链表的开头和结尾增加数据)
LinkedList<position> link = new LinkedList<position>(); position p1 = new position(1,1); position p2 = new position(2,2); position p3 = new position(3,3); position p4 = new position(4,4); link.add(p3); link.add(p4); link.add(p1); link.add(p2); System.out.println(link); link.addFirst(new position(2,2)); link.addLast(p4); |
示例代码 :(找到链表头)
System.out.println(link.pollFirst()); |
示例代码:(以先进先出的方式取出全部的数据)
System.out.println(link); |
3.4、Set接口
Set接口也是Collection的子接口,但是与Collection和List接口不同的是,Set接口中不能加入重复的元素。Set接口定义如下:
public interface Set<E> extends Collection<E> |
从定义可以发现set接口和List接口没有头太大差异,但是Set接口的主要方法和Collection一致的,也就是说set接口并没有对Collection接口进行扩充,只是比Collection接口的要求更加严格了,不能增加重复元素
Set接口的实例不能像List接口那样进行双向输出,因为此接口没有提供像list接口定义的get(int index)方法
3.4.1、Set接口的常用子类
1、散列的存放:HashSet
HashSet是set接口的一个子类,主要的特点是:里面不能存放重复元素,而且采用散列的存储方式,所以没有顺序
实例代码:(HashSet类)
HashSet<Integer> hash = new HashSet<Integer>(); |
说明:
从程序运行结果可以看出,重复元素只能增加一次,而且程序运行时向集合加入元素的顺序并不是集合中保存的顺序,证明HashSet类中的元素是无序排列的
2、有序存放: TreeSet
如果想对输入的数据进行排序,则要使用TreeSet子类,TreeSet类定义如下:
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable |
TreeSet继承了AbstractSet类,此类定义如下:
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> |
示例代码:(验证TreeSet)
TreeSet<Integer> tree = new TreeSet<Integer>(); tree.add(1); tree.add(34); tree.add(56); tree.add(667); System.out.println(tree); tree.clear(); System.out.println(tree); System.out.println(tree.contains(34)); System.out.println(tree.first()); System.out.println(tree.floor(2)); System.out.println(tree.hashCode()); System.out.println(tree.higher(70)); System.out.println(tree.last()); System.out.println(tree.remove(34)); System.out.println(tree);
|
说明:
程序在向集合中添加数据时是没有顺序的,但输出之后是有序的,所以TreeSet是可以排序的子类
3、关于TreeSet排序的说明
既然TressSet本身是可以排序的,那么现在定义一个自己的类,是否也可要进行排序操作呢?
示例代码:(自定义类排序)
TreeSet<position> tree = new TreeSet<position>(); tree.add(p4); tree.add(p3); tree.add(p2); tree.add(p1); tree.add(new position(1,1)); System.out.println(tree); System.out.println(tree.contains(p2)); System.out.println(tree.first()); System.out.println(tree.lower(p3)); System.out.println(tree.pollFirst()); System.out.println(tree); System.out.println(tree.pollLast()); System.out.println(tree); System.out.println(tree.toArray()); |
说明:
出现类型转换异常,因为TreeSet中的元素是有序存放的,所以对于一个对象必须指定好其排列规则,且TressSet中的每个对象所在的类都必须实现Comparable接口才可正常是使用
示例代码:(指定排列规则)
class position implements Comparable<position>{ int x; int y; position(int x, int y){ this.x = x; this.y = y; } public String toString(){ return "x="+x+" y="+y; } public int compareTo(position obj){ if(obj.x >x) return 1; else if(obj.x < x) return -1; else{ if(obj.y> y) return 1; else if(obj.y < y) return -1; else return 0 ; } } } |
说明:
从程序运行结果中发现,重复的“吕子乔”没有加入,但年龄重复的“美嘉“没有添加到集合中,这是由于采用了比较器造成的,因为比较器操作时如果某个属性没有进行比较的指定,则也会认为是同一个对象,所以此时应该在Person类中compareTo方法中增加姓名比较
示例代码:(修改Person比较器)
|
说明:
此时,运行结果中出现了年龄重复的“美嘉”,而且去掉了重复内容,但此时重复内容去掉,并不是真正意义上的去掉重复元素。因为此时靠的是Comparable完成的,而如果换成HashSet则也会出现重复内容,所以要想真正的去掉重复元素,则必须深入研究Object类
4、关于重复元素的说明
观察以下代码:
说明:
从程序运行结果可以发现“吕子乔”元素重复了,也就是说,此时的程序并没有像Set接口规定的那样是不允许有重复元素的,而如果此时想要去掉重复元素,则必须首先进行对象是否重复的判断,而要想进行这样的判断则一个类就必须重写Object类中的equals方法,才能完成对象是否相等的判断,但是只重写equals()方法是不够的,还需要重写hashCode()方法,此方法表示一个哈希编码,可以简单的理解为表示一个对象的编码。一般的哈希吗是通过公式进行计算的,可以将类中的全部属性进行适当的计算,以获取不能重复的哈希码
说明:
集合中的重复元素消失了,就是因为equals()和hashCode()共同作用的结果
3.5、SortedSet接口
从TreeSet类的定义中可以发现,TreeSet类中实现了SortedSet接口,此接口主要用于排序操作,即实现此接口的子类都属于排序的子类,SortedSet接口定义如下:
public interface SortedSet<E> extends Set<E> |
此接口也继承了Set接口,此接口中定义了如下方法:
NO | 方法 |
|
1 |
| |
2 | first |
|
3 |
| |
4 | last |
|
5 |
| |
6 |
|
示例代码:(验证SortedSet接口)
|
3.6、集合的输出
之前我们知道,如果输出Collection,set集合中的内容,可以将其转换为数组输出,而是用list则可直接通过get()方法输出,但这些不是集合的标准输出方式,在类集合中提供了如下4中常见的输出方式:
1、 Iterator:迭代输出,是用最多的方式
2、 ListIterator:是Iterator的子接口,功能与Iterator类似
3、 Enumeration:是一个旧接口,功能与Iterator类似
4、 foreach:JDK1.5以后的新功能,可以输出输出或集合
3.6.1、Iterator迭代输出
1、Iterator接口
在使用集合输出时必须形成一个思路,“只要碰到了集合输出的操作,就一定使用Iterator接口”,因为这是一个最为标准的做法
Iterator是专门迭代输出接口,所谓迭代输出就是将元素一个一个进行判断,判断其是否有内容,如果有内容则把内容取出,Iterator接口的定义如下:
public interface Iterator<E> |
Iterator接口在使用时也要使用泛型,当然此处的泛型最好与集合中的泛型类型一致,此此接口定义的方法如下:(详见API)
NO | 方法 | 说明 |
1 | hasNext |
|
2 | next |
|
3 | remove |
|
2、Iterator接口的相关操作
Iterator接口,可以直接使用Collection接口中定义的Iterator()方法为其实例化,既然Collecting接口中存在了此方法,则List和Set接口中也一定存在此方法,所有也可以通用使用Iterator接口输出。
示例代码:(输出Collection中的全部内容)
|
说明:
集合类的标准输出形式,(重点掌握),将集合中的元素一个一个输出
示例代码:(使用Iterator删除指定内容)
|
示例代码:(迭代输出时删除元素的注意点)
正常情况下,一个集合要把内容交给iterator输出,但集合操作也存在一个remove()方法,如果在使用iterator输出集合时自己调用了删除的方法,则会出现运行错误。
示例代码:(不正确的删除方法)
|
说明:
从程序运行结果中可以发现,内容却是被删除了,但迭代输出在内容被删除之后就终止了。因为集合本身内容被破换掉,所有迭代输出将出现错误,会停止输出。
3.6.2、ListIterator双向迭代输出
1、ListIterator接口简介
Iterator接口的主要功能是由前向后单向输出,而此时如果实现由后向前或是由前向后的双向输出,则必须使用Iterator的子接口 ---- ListIterator,定义如下:
public interface ListIterator<E> extends Iterator<E> |
此接口比Iterator接口中定义了更多的方法,具体如下:(详见API)
|
|
|
NO | 方法 | 说明 |
1 |
| |
2 | hasNext |
|
3 |
| |
4 | next |
|
5 |
| |
6 | previous |
|
7 |
| |
8 | remove |
|
9 |
|
与Iterator接口不同的是,ListIterator接口只能通过List接口实例化,即只能输出list接口的内容,在List接口中定义可以为ListIterator接口实例化的方法ListIterator().
2、ListIterator接口的相关操作
示例代码:(进行双向迭代)
|
说明:
1、 此输出方式只有list接口才可以做到
2、 如果想完成由后向前的输出,则一定要先进行由前向后的输出
示例代码:(增加即替换元素)
|
说明:
虽然add()或set()方法可以增加和替换集合中的元素,但在开发中不建议使用。
3.6.3、java的新支持:foreach
foreach除了可以完成数组输出,对于集合同样支持,语法如下:
for(类 对象:集合) { //集合操作 } |
示例代码:(使用foreach输出)
for (Object obj: intarr) System.out.println(obj);
|
说明:
虽然foreach使用较简单,在实际的开发中iterator还是较多
3.7、Map接口
3.7.1、Map接口简介
Collection、List、set接口都是单值的操作,即每次只能操作一个对象,而map与他们不同的是,每次操作是一对对象,即二元偶对象,map中的每个元素都使用key—value对的形式存储在集合中。Map接口的定义如下:
public interface Map<K,V> |
map也使用了泛型,必须同时设置还key或value的类型,在map中每一对key—value都表示一个值,map接口提供的如下方法:(详见API)
NO | 方法 |
|
| clear |
|
| containsKey |
|
| containsValue |
|
| entrySet |
|
|
| |
|
| |
| hashCode |
|
| isEmpty |
|
| keySet |
|
|
| |
|
| |
|
| |
| size |
|
| values |
|
3.7.2、Map.Entry接口简介
Map.Entry是Map内部定义的一个接口,专门用来保存key---value的内容,Map.Entry定义如下:
public static interface Map.Entry<K,V> |
Map.Entry是使用static关键字声明的内部接口,此接口可以通过“外部类.内部类”的形式直接调用,本接口定义方法如下:
NO | 方法 | 说明 |
1 |
| |
2 | getKey |
|
3 | getValue |
|
4 | hashCode |
|
5 |
|
我们可以把Map理解成一个特殊的Set,只是该set里包含的集合元素时Entry对象。
3.7.3、Map接口的常用子类
Map接口常用的子类如下:
1、 HashMap:无序存放,是新的操作类,key不允许重复
2、 Hashtable:无序存放,是旧的操作类,key不允许重复
3、 TreeMap:可以排序的map集合,按集合的key排序,key不允许重复
1、 新的子类:HashMap
HashMap本身是Map子类,直接使用此类为map接口实例化即可。Hashmap定义如下:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable |
HashMap是AbstractMap的子类,abstractMap类的定义如下:
public abstract class AbstractMap<K,V> extends Object implements Map<K,V> |
2、 相关操作实例
所有的操作都是以Map接口作为标准,且HashMap最为常见
实例代码:(向集合中增加或取出内容)
HashMap<String,Integer> hashmap =new HashMap<String,Integer>(); hashmap.put("math", 100); hashmap.put("chainese", 96); hashmap.put("english", 80); hashmap.put("java", 132); |
示例代码:(判断指定内容是否存在)
System.out.println(hashmap.containsKey("java")); |
示例代码:(输出全部key)
Collection<String> obj =hashmap.keySet(); System.out.println(obj); |
示例代码:(输出全部value)
Collection<Integer> tmp = hashmap.values(); System.out.println(tmp); |
3、 排序的子类:TreeMap
TreeMap的主要功能是按照Key排序
示例代码:(TreeMap的排序)
public static void main(String[] args) { TreeMap<position, Integer> treemap = new TreeMap<position,Integer>(); treemap.put(new position(1,1),1); treemap.put(new position(2,2),2); treemap.put(new position(3,3),3); treemap.put(new position(4,4),4); treemap.put(new position(5,5),5); System.out.println(treemap); System.out.println(treemap.hashCode()); System.out.println(treemap.get(new position(1,1))); System.out.println(treemap.lastEntry()); System.out.println(treemap.remove(new position(3,3),3)); System.out.println(treemap); System.out.println(treemap.size()); System.out.println(treemap.keySet()); System.out.println(treemap.values());
System.out.println("+++++++++++++++++++++++++++++"); Collection<Integer> val = treemap.values();
Integer [] arr = new Integer [1]; arr = val.toArray(arr); for(Integer a: arr){ System.out.println(a); }
|
说明;
此代码使用的是string类作为key,因为string类本身已经实现了Comparable接口,所以程序执行不会有任何问题,如使用自定义类作为key则必须实现comparable接口,否则将出现异常
3.7.4、Map接口的注意事项
1、注意事项:不能直接使用迭代直接输出map中的全部内容
对于Map接口来说,本身不能直接使用迭代输出(Iterator,foreach)进行输出的,因为map中每个位置存放的是一对值(key---value),而Iterator中每次只能找到一个值。所如果非要使用迭代进行输出,则必须按如下步骤完成:
a) 将Map的实例通过entrySet()方法变为set接口对象
b) 通过set接口实例为Iterator实例化
c) 通过iterator迭代输出,每个内容都是Map.Entry对象
d) 通过Map.Entry进行key----value的分离
Map中的每对数据集都是通过Map.Entry保存的,所以如果要最终进行输出也应该使用Map.Entry完成,Map集合在实际开发中基本上作为查询应用的较多,全部输出内容的操作较少。
示例代码:(Iterator输出Map)
|
示例代码;(使用foreach输出Map)
for(Integer a: arr){ System.out.println(a); } |
2、注意事项:使用非系统类所为key
我们使用自定义的类Person替换Map集合中的value
说明:
以上查找对象的方式是通过字符串匿名对象的方式查找的,从结果看,是能够查找到对应的内容。那么现在两个类型交换一下位置?即key用自定义类Person替换
说明:
以上的程序和之前的结构差不多,只是使用了Person的匿名对象作为Key,将字符串的匿名对象做了value,唯一不同的是改变了之前的key和value的位置,但此时无法查到对应的内容,为什么一开始使用string可以,而现在使用Person对象不可以,为了研究这个问题,继续观察下面代码:
说明:
根据Key能够找到value,但为什此时可以找到,而之前不可以。因为之前的操作对象是以Person匿名对象完成的,但现在进行查找操作时使用的并不是匿名对象,这样程序中设置和取得时都使用了Person的实例化对象,地址没有改变,所以可以找到。
但是所有的程序不可能这样去完成查找功能,用户在真正操作时不可能明确的知道其中引用地址,都应该像String那样可以通过匿名对象找到对应的value,要实现这样的功能,就靠Object类中hashCode()和equeals()方法的帮助,以方便区分是否是同一个对象。
示例代码:(重写hashCode、equals方法)
public int hashCode(){ return x*y; } public boolean equals(Object o){ position obj = (position)o; if(obj.x == x && obj.y == y) return true; else return false; } |
说明:
如果要使用自定类作为Map中的key,则对象所在的类中一定要重写equals、hashCode方法;否则无法找到对应的value
3.8、SortedMap接口
SortedMap接口是排序接口,只要是实现了此接口的子类,都属于排序的子类,TreeMap也是此接口的一个子类,SortedMap接口的定义如下:
public interface SortedMap<K,V> extends Map<K,V> |
SortedMap接口扩展了Map中的方法,常用如下:(详见API)
NO | 方法 |
|
1 |
| |
2 | entrySet |
|
3 | firstKey |
|
|
| |
| keySet |
|
| lastKey |
|
|
| |
|
| |
| values |
|
说明;
以上使用了Map接口中没有的方法,但如果操作以上的方法,则对象所在的类必须实现Comparable接口。