Java 类集
摘要:Java中的类集实际上是动态对象数组,没有长度限制,可以任意扩充和丰富。从JDK1.5后,类集中添加了泛型声明,保证了类集使用的安全,所有的类集都位于java.util包下,类集有其继承结构,掌握好类集的使用对我们的程序开发有极大地帮助。
类集概述
Java中的类集,可分为单值操作集合,一对值的操作集合,输出集合。
其中Collection是最大的单值集合操作接口,Map是最大的一对值集合操作接口,Iterator是最大的输出接口。
Collection接口
观察以下Collection类型的定义:public interface Collection <E> extends Iterable <E>
,说明其支持泛型操作。
Colletion接口包含以下操作方法。
方法 | 描述 |
---|---|
boolean add(E e) | 添加元素 |
boolean addAll(Collection<? extends E> c) | 添加一组元素,子集合 |
void clear() | 清空集合内容 |
boolean contains(Object o) | 判断集合中是否包含对象o |
boolean containsAll(Collection<?> c) | 判断集合中是否包含子集合 |
boolean isEmpty() | 判断集合是否为空 |
Iterator |
为Iterator接口实例化对象,以访问该集合 |
boolean remove(Object o) | 删除指定对象 |
boolean removeAll(Collection<?> c) | 删除一组元素,子集合 |
int size() | 返回集合大小,集合当前元素个数 |
Object[] toArray() | 返回该集合的静态对象数组形式 |
在使用集合工具的时候,一般不直接使用Colletion接口,而多使用其子接口List和Set,虽然Collection接口还有两个子接口Queue和SortedSet但不常用。
其中List和Set最大区别就是List中允许重复元素,Set中不允许元素重复。
List接口
List接口对Collection接口有了一定的扩充,添加的方法如下:
方法 | 描述 |
---|---|
void add(int index,E element) | 在集合指定位置插入元素 |
boolean addAll(int index, Collection<? extends E> c) | 在集合指定位置插入子集合 |
E get(int index) | 取得索引位置集合内容 |
int indexOf(Object o) | 返回指定对象在集合中的索引,-1为不存在 |
int lastIndexOf(Object o) | 返回指定对象最后一次出现在集合中的位置 |
ListIterator |
判实例化ListIterator对象,便于List集合输出 |
E remove(int index) | 删除指定索引位置的元素 |
E set(int index, E element) | 修改指定索引位置的元素 |
List |
截取子集合,从指定起始位置到结束位置 |
在使用List接口的时候,我们通常使用其子类ArrayList对其进行实例化,下面是ArrayList的使用示例:
import java.util.*;
public class TestCollection{
public static void main(String[] args) {
List<String> ls = new ArrayList<String>();
ls.add("hello");
ls.add("world");
ls.add("hello");
ls.add(0,"yes");
ls.remove("hello");
System.out.println(ls.contains("world"));
System.out.println(ls);
}
}
程序输出:
true [yes, world, hello]
说明集合类已经覆写了toString方法。ArrayList类是JDK1.2引进的,List接口还有一个较老的子类Vector也可以对其进行实例化,主要操作方法addElement,另外可以返回枚举对其进行输出。
ArrayList和Vector主要区别在于:
- 时间上,ArrayList产生于JDK1.2,而Vector产生于JDK1.0;
- ArrayList是异步操作,因此性能较高,Vector属于同步操作,性能较低;
- ArrayList是非线程安全的操作类,Vector是线程安全的操作类;
- ArrayList当集合内容加之初始容量时,容量会自动扩充0.5倍,而Vector容量会自动扩充1倍。
LinkedList是扩展自List接口的链表操作类,使用方法与ArrayList类似,存储上有所差异,这里就不过多介绍了。
Set接口
Set接口也属于Collection的子接口,但它和List接口的最大区别在于,Set集合中不能包含重复元素,Set接口有两大子类,TreeSet和HashSet,其中TreeSet支持有序存放,HashSet属于散列存放。
Set接口并没有对Collection接口进行扩充。
首先使用HashSet对其进行实例化:
Set<String> st = new HashSet<String>();
st.add("A");
st.add("D");
st.add("C");//repeat element
st.add("C");
st.add("B");
st.add("B");//repeat element
st.add("E");
System.out.println(st);
输出:[D, E, A, B, C]
,从输出内容,可以发现Set不允许有重复元素,会自动屏蔽,HashSet对元素散列存放,没有任何顺序,将HashSet更改为TreeSet进行实例化得到输出结果如下:[A, B, C, D, E],说明,使用TreeSet对存放的元素进行顺序排放。
那么它是如何排序的呢,现在不存放String对象,改存放我们自定义的Person对象,其结果又如何呢?定义Person类如下:
class Person{
private String name = null;
private int age = 0;
public Person(){
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){
return "name=" + this.name + " age=" + this.age;
}
}
在主函数定义Set集合,用于存放Person对象,
Set<Person> pr = new TreeSet<Person>();
pr.add(new Person("zhangsan",20));
pr.add(new Person("lisi",21));
pr.add(new Person("wangwu",15));
pr.add(new Person("zhaoliu",20));
System.out.println(pr);
主函数输出集合:
Exception in thread “main” java.lang.ClassCastException: Person cannot be cast to java.lang.Comparable at java.util.TreeMap.compare(Unknown Source) at java.util.TreeMap.put(Unknown Source) at java.util.TreeSet.add(Unknown Source) at TestCollection.main(TestCollection.java:27)
程序产生上述异常,可以发现,这时候Person类由于没有实现Comparable接口,因此不支持排序功能,而String默认是实现了Comparable接口的,现在修改Person类实现Comparable接口,覆盖compareTo方法,使其按年龄排序。
class Person implements Comparable<Person> {
private String name = null;
private int age = 0;
public Person(){
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
public int compareTo(Person per){
if(this.age < per.age){
return -1;
}else if(this.age > per.age){
return 1;
}else{
return 0;
}
}
public String toString(){
return "name=" + this.name + " age=" + this.age;
}
}
修改之后,正常输出并按年龄排序如下:
[name=wangwu age=15, name=zhangsan age=20, name=lisi age=21]
但是发现zhaoliu不见了,那是因为对年龄排序,由于zhangsan和zhaoliu的年龄一样,被当做重复元素处理掉了,现在修改compareTo方法
public int compareTo(Person per){
if(this.age < per.age){
return -1;
}else if(this.age > per.age){
return 1;
}else{
return this.name.compareTo(per.name);
}
}
输出如下:
[name=wangwu age=15, name=zhangsan age=20, name=zhaoliu age=20, name=lisi age=21]
发现一切正常了,即使加上一个20岁的zhangsan,重复元素也会被剔除掉。
但这时将主函数中的TreeSet类改成HashSet类,输出:
[name=zhaoliu age=20, name=zhangsan age=20, name=zhangsan age=20, name=lisi age=21, name=wangwu age=15]
发现加上的重复元素zhangsan没有被剔除,是因为我们刚刚是对Person类借助于compareTo方法实现了比较剔除,而并没有从类本身考虑重复的情况,一旦比较不需要了,重复元素便不能判断出来。
那么Set集合又是如何排除重复元素的呢?答案是通过Obejct的equals和hashCode方法,来判断重复元素的,现在修改Person类如下:
class Person implements Comparable<Person> {
private String name = null;
private int age = 0;
public Person(){
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
public int compareTo(Person per){
if(this.age < per.age){
return -1;
}else if(this.age > per.age){
return 1;
}else{
return this.name.compareTo(per.name);
}
}
public int hashCode(){
return this.name.hashCode()*this.age;
}
public boolean equals(Object o){
if(this == o)
return true;
if(!(o instanceof Person))
return false;
Person per = (Person)o;
if(this.name.equals(per.name) && this.age == per.age)
return true;
else
return false;
}
public String toString(){
return "name=" + this.name + " age=" + this.age;
}
}
主函数再次进行调用:
Set<Person> pr = new TreeSet<Person>();
pr.add(new Person("zhangsan",20));
pr.add(new Person("lisi",21));
pr.add(new Person("wangwu",15));
pr.add(new Person("zhaoliu",20));
pr.add(new Person("zhangsan",20));
System.out.println(pr);
此时输出内容不会再出现重复元素了:
[name=wangwu age=15, name=zhangsan age=20, name=zhaoliu age=20, name=lisi age=21]
集合输出
集合的输出有四种方式:Iterator;ListIterator;foreach;Enumeration。
- Iterator是推荐使用方式:只要碰到集合输出问题,就一定首先考虑使用Iterator接口进行输出。
- ListIterator只要List接口的子类对象才可以使用,可以进行双向输出。
- 也可以使用一般的for循环输出List集合内容。
- foreach是JDK1.5版本新增的for循环增强的内容,不仅可以输出数组,也可以输出集合内容。
- Enumeration接口是一种古老的操作接口,只有Vector类elements方法可以返回Enumeration接口实例,并进一步进行输出操作。
下面介绍这几种输出方法:
System.out.println("1.use iterator to print collection");
Iterator<Person> iter = pr.iterator();
while(iter.hasNext()){
Person per = iter.next();
System.out.println(per);
}
System.out.println("2.use listiterator to print List");
ListIterator<String> listIter = ls.listIterator();
System.out.println("2.1.forward print");
while(listIter.hasNext()){
String str = listIter.next();
System.out.println(str);
}
System.out.println("2.2.backward print");
while(listIter.hasPrevious()){
String str = listIter.previous();
System.out.println(str);
}
System.out.println("3.use general for to print List");
for(int i=0;i<ls.size();i++){
String str = ls.get(i);
System.out.println(str);
}
System.out.println("4.use foreach to print collection");
for(Person per : pr){
System.out.println(per);
}
System.out.println("5.use enumeration to print Vector");
Enumeration<String> e = vec.elements();
while(e.hasMoreElements()){
String str = e.nextElement();
System.out.println(str);
}
注意,ListIterator对List集合进行双向输出时,必须先进行next输出之后,才能进行previous输出。至此已经介绍完了单一值接口和输出接口,还剩下一对值接口即Map接口。
Map 接口
Map作为一对值的最大接口,其值的存储是以键值对的形式存放的,同样我们也不直接操作Map接口,而是使用其子类进行实例化。
Map接口三个常用的子类是:HashMap,Hashtable以及TreeMap。
先来看一下Map接口定义的常用方法。Map接口定义:public interface Map<K,V>
常用方法:
方法 | 描述 |
---|---|
V put(K key, V value) | 添加键值对 |
V get(Object key) | 根据指定键取值 |
boolean containsKey(Object key) | 判断是否包含指定键 |
boolean containsValue(Object value) | 判断是否包含指定值 |
Set |
将所有的key转为Set集合 |
Collection |
将所有的value转化为Collection集合 |
Set<Map.Entry<K,V» entrySet() | 将Map转化为Set集合,此为Map输出方式 |
void clear() | 清空Map集合 |
void size() | 返回集合大小 |
以下代码使用HashMap为Map接口进行实例化,并调用相关方法
Map<Integer,String> map = new HashMap<Integer,String>();
map.put(1,"hello");
map.put(2,"world");
map.put(3,"yes");
System.out.println(map);
Set<Integer> keys = map.keySet();
Iterator<Integer> keyIter = keys.iterator();
while(keyIter.hasNext()){
Integer key = keyIter.next();
String value = map.get(key);
System.out.println(key+"---->"+value);
}
Collection<String> vals = map.values();
Iterator<String> valIter = vals.iterator();
while(valIter.hasNext()){
String val = valIter.next();
System.out.println(val);
}
运行结果:
{1=hello, 2=world, 3=yes} 1—->hello 2—->world 3—->yes hello world yes
HashMap本身也属于一种无序的操作。而Hashtable实际上与Vector产生的时代一样,属于最早集合操作类,之后只是扩展了其应用,实现了Map接口而已,,把上面HashMap声明部分,换成Hashtable,结果一模一样。
那么HashMap与Hashtale的区别与ArrayList和Vector区别一样:
- HashMap集合比较晚,异步处理,性能较高;
- Hashtable较早,同步处理,性能较差。
从实际开发来看,最常用的子类是HashMap.注意Map接口由于以键值对的形式出现,无法直接使用Ierator接口进行输出。下面介绍一种Map接口常用输出方法。
Set<Map.Entry<Integer,String>> entrys = map.entrySet();
Iterator<Map.Entry<Integer,String>> entryIter = entrys.iterator();
while(entryIter.hasNext()){
Map.Entry<Integer,String> me = entryIter.next();
System.out.println(me.getKey()+"---->"+me.getValue());
}