体系概述
Java平台提供了一个全新的集合框架,集合框架主要由一组用来操作对象的接口组成,不同的接口描述一组不同的数据类型。下面描述的6个接口表示不同的集合类型,是Java集合框架的基础。
Collection接口:
List接口:
ArrayList具体类*
LinkedList具体类*
Set接口:
HashSet具体类*
TreeSet具体类*
Map接口:
HashMap具体类*
TreeMap具体类 *
Iterator接口:
ListIterator接口
这是简化的一张Java集合框架图,也一并复制上来
Collection接口
Collection是最基本的集合接口,它定义了一组允许重复的对象;也是List和Set集合的根接口,其中定义了有关集合操作的普遍性方法。
--新增
boolean add(E e); // 将指定对象添加到集合中
boolean addAll(Collection c); // 将指定的集合中的所有元素添加到集合中
--删除
boolean remove(Object o); // 移除指定元素的单个实例
boolean removeAll(Collection c); // 移除包含Collection中的元素实例
viod clear(); // 移除Collection中的所有元素
--查找
boolean contains(Object o); // 如果包含指定元素,则返回true
boolean isEmpty(); // 如果集合不包含元素,则返回true
int size(); // 返回集合中元素个数
Iterator iterator(); // 获取集合的迭代器
--其它
boolean retainAll(Collenction c); // 对集合取交集
<T> toArray(); // 返回集合的数组
迭代器
迭代器是一种模式,它可以使得遍历与对象相分离,即我们无需关心对象序列的底层结构,只有拿到这个对象的迭代器就可以遍历对象内部。Java提供了一个专门的迭代器<interface> Iterator,我们可以对某个序列实现该接口,来提供标准的Java迭代器。Collection接口中的子类就是通过内部类实现Iterator接口,创建各自的迭代器,再重写iterator()方法返回迭代器,调用者只需拿到迭代器便可按照Iterator提供的标准来遍历对象。
Iterator接口:
boolean hasNext(); // 如果还有元素可迭代,则返回true
E next(); // 返回迭代的下一个元素
void remove(); // 删除迭代返回的最后的一个元素
import java.util.ArrayList;
import java.util.Iterator;
public class IteratorDemo {
public static void main(String args[]) {
ArrayList al = new ArrayList();
al.add("java 01");
al.add("java 02");
al.add("java 03");
Iterator it = al.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
Iterable接口:
这是Java 5新特性,Iteratable接口实现后的功能是返回一个迭代器,实现Iterable接口允许对象成为foreach语句目标。我们常用的实现Iterable接口的类有:List<E>, Set<E>。
import java.util.ArrayList;
public class IteratorDemo {
public static void main(String args[]) {
ArrayList al = new ArrayList();
al.add("java 01");
al.add("java 02");
al.add("java 03");
for(Object obj: al) {
System.out.println(obj);
}
}
}
可以看出for each循环语句更简洁,将迭代器的显示调用改为隐式调用。但这样也有局限性,隐藏了迭代器后就无法使用迭代器的方法,比如该例遍历中就无法对集合进行删除。
List集合
List继承自Collection接口。List是有序的Collection,使用List接口能够精确的控制每个元素插入的位置。用户能够使用索引来访问元素,这类似于Java的数据。
跟Set集合不同的是,List允许有重复的元素。对于满足e1.equals(e2)条件的两个元素,可以同时存在于List集合中。
除了具有Collection接口必备的Iterator()方法外,List还提供了一个ListIterator()方法,返回一个ListIterator接口。ListIterator和Iterator相比添加对元素的增删改的方法。
实现List接口的子类有LinkedList,ArrayList,Vector,Stack。
LinkedList类
LinkedList实现了List接口,允许null元素。是以指针链表结构来实现的。此外LinkedList还提供了一些额外的方法。
void addFirst(E e); // 将指定的元素插入到列表的开头
void addLast(E e); // 将指定的元素插入到列表的结尾
// 获取元素,但不删除元素。如果集合中没有元素,则会抛出NoSuchElementException异常
E getFirst(); // 获取列表中的第一个元素
E getLast(); // 获取列表中的最后一个元素
// 获取元素,并且删除元素。如果没有元素,则会抛出NoSuchElementException异常
E removeFirst(); // 获取并移除列表中的第一个元素
E removeLast(); // 获取并移除列表中的最后一个元素
在JDK1.6中出现了替代方法。
boolean offerFirst(E e); // 将指定的元素插入到列表的开头
boolean offerLast(E e); // 将指定的元素插入到列表的结尾
// 获取元素,但不删除元素。如果集合中没有元素,则返回null
E peerFirst(); // 获取列表中的第一个元素
E peerLast(); // 获取列表中的最后一个元素
// 获取元素,并且删除元素。如果没有元素,则返回null
E pollFirst(); // 获取并移除列表中的第一个元素
E pollLast(); // 获取并移除列表中的最后一个元素
这些操作使得LinkedList可被用作为堆栈、队列等。另外LinkedList是非线程安全的,如果多线程同时访问一个List,需要自己实现同步。
ArrayList类
ArrayList实现了List接口,允许null元素。是以数组结构来实现的。也提供了自己的get(), set() 方法,和LinkedList一样也是非线程安全的。ArrayList和LinkedList同作为List接口下非常重要的子类,就此对二者做简单的对比。
LinkedList使用的是链表结构,优点是增、删、改速度较快;不足的是查找需要逐个遍历链表,速度较慢。(链表结构增删改都是通过修改相关元素的指针来实现,不需要挪动元素,也不受长度限制;但在查找时需要逐个查询)
ArrayList使用的数组结构,优点是查找较快,不足的是新增和删除很慢。(数组结构查询可以按角标随机访问,但在新增或删除需要去挪动数组里的元素,因此较慢;如果当长度不够时,就需要重新开辟空间复制数据,比较影响性能)
Vector类
Vector的功能和实现原理和ArrayList类似,Vector是在ArrayList之前出现的,后来基本上被ArrayList替代了。但Vector和ArrayList还是有稍微的不同。Vector是线程安全的;Vector还有自己独有的枚举(elements(), hasMoreElements(), nextElements()),后来被迭代器取代了。
Stack类
Stack继承自Vector类,实现一个后进先出的堆栈,提供了额外的5个方法来操作堆栈。
E push(E e); // 向栈顶添加元素
E pop(); // 移除栈顶元素
E peek(); // 查看堆栈顶部对象
boolean empty(); // 测试堆栈是否为空
int search(Object o); // 返回对象在堆栈中的位置
Set集合
Set继承自Collection接口。Set是一种不能包含有重复,且元素无序的集合,即对于满足e1.equals(e2)条件的e1和e2对象元素不能同时存于一个Set集合里,Set中也是可以有null元素的。因为Set特性,使用Set时应该:为Set集合里的元素的实现类实现一个有效的equals()方法。实现了Set接口的主要有HashSet、TreeSet、LinkedHashSet。
HashSet类
HashSet使用的是哈希表来存储元素的,使用HashSet能够快速的获取集合中的元素,效率非常高。会根据hashcode和equals来判断是否为同一个对象,如果hashcode值一样,并且equals返回true,则是同一个对象,不能重复存放。
import java.util.HashSet;
import java.util.Iterator;
public class HashSetDemo {
public static void main(String args[]) {
HashSet hs = new HashSet();
Person p1 = new Person("java01", 11);
Person p2 = new Person("java02", 12);
Person p3 = new Person("java03", 13);
Person p4 = new Person("java02", 12);
hs.add(p1);
hs.add(p2);
hs.add(p3);
hs.add(p4);
Iterator it = hs.iterator();
while(it.hasNext()) {
Person p = (Person)it.next();
System.out.println("name: " + p.name + "; " + "age: " + p.age);
}
}
}
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public int hashCode() {
return this.name.hashCode() + this.age;
}
public boolean equals(Object obj) {
if (!(obj instanceof Person)) {
return false;
}
Person p = (Person) obj;
return this.name.equals(p.name) && (this.age == p.age);
}
}
上段代码重写了hashCode()和equals()方法,如果不重写则会默认使用父类中的hashCode()和equals()方法;注意不同的类生成hashCode的方法不同,equals的比较方法也不同。哈希表的存储原理是:对每个对象按照指定的规则生成一个int型值,int值不同的对象肯定是不同对象,将该对象的地址保存到该哈希值下的链表中;int值如果相同,在比较equals方法将该对象和哈希值链表下的对象比较,如果返回false,则将该对象也存放到该哈希值下的链表中,如果返回true则认为以后包含该对象不再存储。
TreeSet类
TreeSet描述的是一种可以实现排序功能的Set集合,存储元素的数据结构是二叉树,如果存放的对象不能排序则会报错,所以存放的对象必须指定排序规则,排序规则包含自然排序和客户排序。
自然排序:在TreeSet要添加的那个对象类上实现java.lang.Comparable接口,并重写compareTo()方法,返回0表示是同一个对象,否则不是同一个对象,TreeSet默认是自然排序。
客户排序:建立一个第三方类并实现java.util.Comparator接口,并重写compare()方法。定义集合形式为TreeSet ts = new TreeSet(new 第三方类);
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String args[]) {
TreeSet hs = new TreeSet(); // 自然排序
// TreeSet hs = new TreeSet(); // 客户排序
hs.add(new Person("java01", 11));
hs.add(new Person("java02", 12));
hs.add(new Person("java03", 13));
hs.add(new Person("java04", 12));
Iterator it = hs.iterator();
while(it.hasNext()) {
Person p = (Person)it.next();
System.out.println("name: " + p.name + "; " + "age: " + p.age);
}
}
}
class MyCompare implements Comparator{
// 客户排序,对指定对象先按名称排序,如果名称相同则再按年龄排序
// 原则是先比主要条件,再比次要条件,如果都一样则认为这两个对象是同一个对象;
public int compare(Object obj1, Object obj2) {
Person p1 = (Person) obj1;
Person p2 = (Person) obj2;
int num = p1.name.compareTo(p2.name);
if (num == 0) {
return new Integer(p1.age).compareTo(new Integer(p2.age));
}
return num;
}
}
class Person implements Comparable{
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
// 自然排序,对本对象先按年龄升序,如果年龄相同再按名称排序;
// 原则是先比主要条件,再比次要条件,如果都一样则认为这两个对象是同一个对象;
public int compareTo(Object obj) {
Person p = (Person) obj;
System.out.println(this.name + "和" + p.name + "比较了!!!");
// 返回正数表示当前对象大于比较对象
if (this.age > p.age)
return 1;
// 返回0表示两个对象相同
if (this.age == p.age) {
// String类已经实现了Comparable接口,直接调用它的排序方法即可
return this.name.compareTo(p.name);
}
// 返回负数表示当前对象小于比较对象
return -1;
}
}
要想用TreeSet集合就必须指定排序规则,而指定排序规则的方式就是重写比较方法;当集合有指定的客户排序规则时,优先使用客户排在,如果没有才会使用对象提供的默认排序规则,都没有则编译失败。下面以默认排序为例来简述TreeSet的排序情况。TreeSet类在添加对象时会自动调用对象的compareTo()方法来实现TreeSet集合中数据的排序。在重写compareTo()方法时,当返回值为0时一定要注意,此时新增对象会被认为和已有的对象相同,而不被添加到集合中来。TreeSet新增、删除、查找元素都是通过比较compareTo()的返回值来确定的,如果比较的返回值为0则认为是相同的,如果比较的返回值不为0则认为不是同一个元素。
Map集合
HashMap类
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class HashMapDemo {
public static void main(String args[]) {
// 使用HashMap时,要注意key对象的hashCode()和equals()方法
// 该方法用来判断键值是否相同
HashMap<Student, String> map = new HashMap<Student, String>();
map.put(new Student("zhangsan01", 11), "beijing");
map.put(new Student("zhangsan02", 21), "shanghai");
map.put(new Student("zhangsan03", 31), "nanjing");
map.put(new Student("zhangsan04", 11), "beijing");
map.put(new Student("zhangsan02", 21), "wuhan");
// 用keySet视图来迭代map
Set<Student> keySet = map.keySet();
Iterator<Student> it = keySet.iterator();
while(it.hasNext()) {
Student stu = it.next();
String addr = map.get(stu);
System.out.println("学生:" + stu + "; 地址:" +addr);
}
// 用entrySet视图迭代map
Set<Map.Entry<Student, String>> entrySet = map.entrySet();
Iterator<Map.Entry<Student, String>> iter = entrySet.iterator();
while(iter.hasNext()) {
Map.Entry<Student, String> entry = iter.next();
Student stu = entry.getKey();
String addr = entry.getValue();
System.out.println("学生:" + stu + "; 地址:" +addr);
}
}
}
/**
* 学生测试类
* 学生属性:姓名,年龄。
* 这里假设当姓名和年龄都相同时视为同一个学生。
*/
class Student implements Comparable<Student> {
private String name;
private Integer age;
Student(String name, Integer age) {
this.name = name;
this.age = age;
}
// 因为该对象可能要存入到HashMap中,并作为key键
// 所以需要重写哈希比较中用到的两个方法,来保证学生的唯一性
public int hashCode() {
return name.hashCode() + age.hashCode();
}
public boolean equals(Object obj) {
// 注意参数必须是Object对象,因为是重写,那么参数类型必须一致
Student s = (Student) obj;
return this.name.equals(s.name) && this.age.equals(s.age);
}
// 因为该对象可能要存入到TreeMap中,并作为key键
// 所以需要实现Comparable接口,并重写compare方法,让该类具有可比性
public int compareTo(Student s) {
// 注意这里在Comparable接口上使用了泛型,并且compareTo方法参数类型也是泛型
// 所以这里重写compareTo方法传入的参数类型时和接口上指定的类型一致为Student
int num = this.age.compareTo(s.age);
if (num == 0) {
return this.name.compareTo(s.name);
}
return num;
}
//重写toString方法
public String toString() {
return name + ", " + age;
}
}
上面对HashMap的使用做了演示,更对Set<Key>和Set<Map.Entry<K, V>>视图的使用做了较详细的描述。只是要注意:使用到哈希数据结构的,判断对象是否相同,都是通过hashCode()和equals()方法,在使用时要注意对象的这两个方法。
TreeMap类
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class TreeMapDemo {
public static void main(String args[]) {
// 使用TreeMap时,要注意key对象的compareTo()方法或者比较器中的compare()方法
// 该方法用来判断键值是否相同
TreeMap<Student, String> map = new TreeMap<Student, String>(new StudentComparator());
map.put(new Student("zhangsan01", 11), "beijing");
map.put(new Student("zhangsan02", 21), "shanghai");
map.put(new Student("zhangsan03", 31), "nanjing");
map.put(new Student("zhangsan02", 21), "beijing");
map.put(new Student("zhangsan02", 20), "wuhan");
// 用keySet视图来迭代map
Set<Student> keySet = map.keySet();
Iterator<Student> it = keySet.iterator();
while(it.hasNext()) {
Student stu = it.next();
String addr = map.get(stu);
System.out.println("学生:" + stu + "; 地址:" +addr);
}
// 用entrySet视图迭代map
Set<Map.Entry<Student, String>> entrySet = map.entrySet();
Iterator<Map.Entry<Student, String>> iter = entrySet.iterator();
while(iter.hasNext()) {
Map.Entry<Student, String> entry = iter.next();
Student stu = entry.getKey();
String addr = entry.getValue();
System.out.println("学生:" + stu + "; 地址:" +addr);
}
}
}
/**
* 构造一个比较器
* 比较类型为通过泛型设置为Student
* 先比姓名,如果相同再比较年龄,都相同则认为是同一个Student对象
*/
class StudentComparator implements Comparator<Student> {
// 实现compare方法,参数的类型和泛型上指定的类型相同
public int compare(Student s1, Student s2) {
int num = s1.getName().compareTo(s2.getName());
if (num == 0) {
return s1.getAge().compareTo(s2.getAge());
}
return num;
}
}
/**
* 学生测试类
* 学生属性:姓名,年龄。
* 这里假设当姓名和年龄都相同时视为同一个学生。
*/
class Student implements Comparable<Student> {
private String name;
private Integer age;
Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
// 因为该对象可能要存入到HashMap中,并作为key键
// 所以需要重写哈希比较中用到的两个方法,来保证学生的唯一性
public int hashCode() {
return name.hashCode() + age.hashCode();
}
public boolean equals(Object obj) {
// 注意参数必须是Object对象,因为是重写,那么参数类型必须一致
Student s = (Student) obj;
return this.name.equals(s.name) && this.age.equals(s.age);
}
// 因为该对象可能要存入到TreeMap中,并作为key键
// 所以需要实现Comparable接口,并重写compare方法,让该类具有可比性
public int compareTo(Student s) {
// 注意这里在Comparable接口上使用了泛型,并且compareTo方法参数类型也是泛型
// 所以这里重写compareTo方法传入的参数类型时和接口上指定的类型一致为Student
int num = this.age.compareTo(s.age);
if (num == 0) {
return this.name.compareTo(s.name);
}
return num;
}
//重写toString方法
public String toString() {
return name + ", " + age;
}
}
上面对TreeMap的使用做了演示;对怎么重写对象中的比较方法,和构造一个比较器去对key键进行排序也做了比较详细的描述。注意:TreeMap和HashMap比较key值是否相同的方式不一样,是否正确实现了Map接口,要看equals方法是否和compare或者compareTo方法一致。