集合类
集合概述
1、类集设置的目的(重点)
对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构, 所以引入了类集的概念,有时候就可以把集合称为 java 对数据结构的实现。 在整个类集中的,这个概念是从 JDK1.2(Java 2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完 整的提出类集的完整概念。 类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。 所有的类集操作的接口或类都在 java.util 包中。
Collection、Map基础体系:
Collection接口
1、Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。 此接口定义在 java.util 包中。
2、Collection 接口使用了泛型技术,在 JDK1.5之后为了使类集操作的更加安全,所以引入了泛型。
此接口定义如下:
public interface Collection<E> extends Iterable<E>
Collection 通用方法:
本接口中一共定义了 15个方法,那么此接口的全部子类或子接口就将全部继承以上接口中的方法。
但是,在开发中不会直接使用 Collection 接口。而使用其操作的子接口:List、Set。
之所以有这样的明文规定,也是在 JDK1.2 之后才有的。一开始在 EJB 中的最早模型中全部都是使用 Collection 操作 的,所以很早之前开发代码都是以 Collection 为准,但是后来为了更加清楚的区分,集合中是否允许有重复元素所以 SUN 在其开源项目 —— PetShop(宠物商店)中就开始推广 List 和 Set 的使用。
List接口
在整个集合中 List 是 Collection 的子接口,里面的所有内容都是允许重复的。
List 子接口的定义:
public interface List<E> extends Collection<E>
此接口上依然使用了泛型技术。此接口对于 Collection 接口来讲有如下的扩充方法:
ArrayList
ArrayList 是 List 接口的子类,此类的定义如下:
public class ArrayList<E>
extends AbstractList<E>
implements List<E>,RandomAccess,Cloneable,Serializable
此类继承了 AbstractList 类。AbstractList 是 List 接口的子类。AbstractList 是个抽象类,适配器设计模式。
/**
范例:增加及取得元素
*/
package org.listdemo.arraylistdemo;
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo01 {
public static void main(String[] args) {
List<String> all = new ArrayList<String>();
// 实例化List对象,并指定泛型类型
all.add("hello ");
// 增加内容,此方法从Collection接口继承而来
all.add(0, "LAMP ");
// 增加内容,此方法是List接口单独定义的
all.add("world");
// 增加内容,此方法从Collection接口继承而来
System.out.println(all);
// 打印all对象调用toString()方法
}
}
可以发现,此时的对象数组并没有长度的限制,长度可以任意长,只要是内存够大就行。
/**
使用 remove()方法删除若干个元素,并且使用循环的方式输出。
根据指定位置取的内容的方法,只有 List 接口才有定义,
其他的任何接口都没有任何的定义。
*/
package org.listdemo.arraylistdemo;
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo02 {
public static void main(String[] args) {
List<String> all = new ArrayList<String>();
// 实例化List对象,并指定泛型类型
all.add("hello ");
// 增加内容,此方法从Collection接口继承而来
all.add(0, "LAMP ");
// 增加内容,此方法是List接口单独定义的
all.add("world");
// 增加内容,此方法从Collection接口继承而来
all.remove(1);
// 根据索引删除内容,此方法是List接口单独定义的
all.remove("world");
// 删除指定的对象
System.out.print("集合中的内容是:");
for (int x = 0; x < all.size(); x++) {
// size()方法从Collection接口继承而来
System.out.print(all.get(x) + "、");
// 此方法是List接口单独定义的
}
}
}
ArrayList底层是用数组实现的存储,而数组的特点就是查询效率高,增删效率低,线程不安全。所以日常需要使用List集合存储并且存储的数据需要频繁的查询时,可以选择ArrayList进行存储。
Vector
与 ArrayList 一样,Vector 本身也属于 List 接口的子类,此类的定义如下:
public class Vector<E>
extends AbstractList<E>
implements List<E>,RandomAccess,Cloneable,Serializable
此类与 ArrayList 类一样,都是 AbstractList 的子类。所以,此时的操作只要是 List 接口的子类就都按照 List 进行操作。
package org.listdemo.vectordemo;
import java.util.List;
import java.util.Vector;
public class VectorDemo01 {
public static void main(String[] args) {
List<String> all = new Vector<String>();
// 实例化List对象,并指定泛型类型
all.add("hello ");
// 增加内容,此方法从Collection接口继承而来
all.add(0, "LAMP ");
// 增加内容,此方法是List接口单独定义的
all.add("world");
// 增加内容,此方法从Collection接口继承而来
all.remove(1);
// 根据索引删除内容,此方法是List接口单独定义的
all.remove("world");
// 删除指定的对象 System.out.print("集合中的内容是:");
for (int x = 0; x < all.size(); x++) {
// size()方法从Collection接口继承而来
System.out.print(all.get(x) + "、");
// 此方法是List接口单独定义的
}
}
}
以上的操作结果与使用 ArrayList 本身并没有任何的区别。因为操作的时候是以接口为操作的标准。
但是 Vector 属于 Java 元老级的操作类,是最早的提供了动态对象数组的操作类,在 JDK1.0 的时候就已经推出了此 类的使用,只是后来在 JDK1.2 之后引入了 Java 类集合框架。但是为了照顾很多已经习惯于使用 Vector 的用户,所以在 JDK1.2 之后将 Vector 类进行了升级了,让其多实现了一个 List 接口,这样才将这个类继续保留了下来。
Vector 类和 ArrayList 类的区别
这两个类虽然都是 List 接口的子类,但是使用起来有如下的区别,列出此内容:
LinkedList
LinkedList是 List 接口的子类,此类的定义如下:
public class LinkedList<E>
extends Abstract SequentialList<E>
implements List<E>,Deque<E>,Cloneable,Serializable
此类继承了 AbstractList,所以是 List 的子类。但是此类也是 Queue 接口(队列)的子类,Queue 接口定义了如下的方法:
import java.util.LinkedList;
import java.util.Queue;
public class TestDemo {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<String>();
queue.add("A");
queue.add("B");
queue.add("C");
int len=queue.size();
//把queue的大小先取出来,否则每循环一次,移除一个元素,就少 一个元素,那么queue.size()在变小,就不能循环queue.size()次了。
for (int x = 0; x <len; x++) {
System.out.println(queue.poll());
}
System.out.println(queue);
}
}
LinkedLis该集合底层维护了一个双向循环链表,链表中每一个元素都使用引用的方式来记住它的前一个元素和后一个元素,从而可以将所有的元素彼此链接起来。当插入一个新的元素时,只需要修改元素之间的这种引用关系即可,删除一个节点也是如此。
正是因为这样的存储结构,所以LinkedList集合对于元素的增删操作具有很高的效率。
Set接口
Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。
Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。因为此接口没有 List 接口中定义 的 get(intindex)方法(get()方法为List接口方法),所以无法使用循环进行输出。
那么在此接口中有两个常用的子类:HashSet、TreeSet
HashSet
既然 Set 接口并没有扩充任何的 Collection 接口中的内容,所以使用的方法全部都是 Collection 接口定义而来的。
HashSet 属于散列的存放类集,里面的内容是无序存放的。
package org.listdemo.hashsetdemo;
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo01 {
public static void main(String[] args) {
Set<String> all = new HashSet<String>();
// 实例化Set接口对象
all.add("A");
all.add("B");
all.add("C");
all.add("D");
all.add("E");
System.out.println(all);
}
}
使用 HashSet 实例化的 Set 接口实例,本身属于无序的存放。
那么,现在思考一下?能不能通过循环的方式将 Set 接口中的内容输出呢?
是可以实现的,因为在 Collection 接口中定义了将集合变为对象数组进行输出。
package org.listdemo.hashsetdemo;
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo01 {
public static void main(String[] args) {
Set<String> all = new HashSet<String>();
// 实例化Set接口对象
all.add("A");
all.add("B");
all.add("C");
all.add("D");
all.add("E");
Object obj[] = all.toArray();
// 将集合变为对象数组
for (int x = 0; x < obj.length; x++) {
System.out.print(obj[x] + "、");
}
}
下面再进一步验证 Set 接口中是不能有重复的内容的。
import java.util.Set;
public class HashSetDemo01 {
public static void main(String[] args) {
Set<String> all = new HashSet<String>();
// 实例化Set接口对象
all.add("A");
all.add("A");//重复的元素
all.add("A");//重复的元素
all.add("A");//重复的元素
all.add("A");//重复的元素
all.add("B");
all.add("C");
all.add("D");
all.add("E");
Object obj[] = all.toArray();
// 将集合变为对象数组
for (int x = 0; x < obj.length; x++) {
System.out.print(obj[x] + "、");
}
}
以上字符串“A”设置了很多次,因为 Set 接口中是不能有任何的重复元素的,所以其最终结果只能有一个“A”。
TreeSet
与 HashSet 不同的是,TreeSet 本身属于排序的子类,此类的定义如下:
public class TreeSet<E>
extends AbstractSet<E>
implements NavigableSet<E>,Cloneable,Serializable
下面通过代码来观察其是如何进行排序的。
package org.listdemo.treesetdemo01;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetDemo01 {
public static void main(String[] args) {
Set<String> all = new TreeSet<String>();
// 实例化Set接口对象\
all.add("D");
all.add("X");
all.add("A");
System.out.println(all);
}
}
/**
输出:A D X
*/
排序的说明
既然 Set 接口的 TreeSet 类本身是允许排序,那么现在自定义一个类是否可以进行对象的排序呢?
package org.listdemo.treesetdemo02;
public class Person {
private String name;
private int age;
public Person() { }
public Person(String name, int age) {
this.name = name; this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}
}
下面定义一个 TreeSet 集合,向里面增加若干个 Person 对象。
package org.listdemo.treesetdemo02;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetPersonDemo01 {
public static void main(String[] args) {
Set<Person> all = new TreeSet<Person>();
all.add(new Person("张三", 10));
all.add(new Person("李四", 10));
all.add(new Person("王五", 11));
all.add(new Person("赵六", 12));
all.add(new Person("孙七", 13));
System.out.println(all);
}
}
执行以上的操作代码之后,发现出现了如下的错误提示:
Exception in thread "main" java.lang.ClassCastException:
org.lamp.listdemo.treesetdemo02.Person cannot be cast to java.lang.Comparable
at java.util.TreeMap.put(Unknown Source)
at java.util.TreeSet.add(Unknown Source)
at
org.lamp.listdemo.treesetdemo02.TreeSetPersonDemo01.main(TreeSetPersonDemo01.java:11)
此时的提示是:Person类不能向 Comparable 接口转型的问题? 所以,证明,如果现在要是想进行排序的话,则必须在 Person类中实现 Comparable 接口。
package org.listdemo.treesetdemo03;
public class Person implements Comparable<Person> {
private String name;
private int 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 Person() { }
public Person(String name, int age) {
this.name = name; this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}
}
//结果:[姓名:张三,年龄:10, 姓名:王五,年龄:11, 姓名:赵六,年龄:12, 姓名:孙七,年龄:13]
从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则 此时必须修改 Person类,如果假设年龄相等的话,按字符串进行排序。
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);
}
}
此时,可以发现李四出现了,如果加入了同一个人的信息的话,则会认为是重复元素,所以无法继续加入。
关于重复元素的说明
之前使用 Comparable 完成的对于重复元素的判断,那么 Set 接口定义的时候本身就是不允许重复元素的,那么证明 如果现在真的是有重复元素的话,使用 HashSet 也同样可以进行区分。
package org.listdemo.treesetdemo04;
import java.util.HashSet;
import java.util.Set;
public class HashSetPersonDemo01 {
public static void main(String[] args) {
Set<Person> all = new HashSet<Person>();
all.add(new Person("张三", 10));
all.add(new Person("李四", 10));
all.add(new Person("李四", 10));
all.add(new Person("王五", 11));
all.add(new Person("赵六", 12));
all.add(new Person("孙七", 13));
System.out.println(all);
}
}
此时发现,并没有去掉所谓的重复元素,也就是说之前的操作并不是真正的重复元素的判断,而是通过 Comparable 接口间接完成的。
如果要想判断两个对象是否相等,则必须使用 Object 类中的 equals()方法。
从最正规的来讲,如果要想判断两个对象是否相等,则有两种方法可以完成:
· 第一种判断两个对象的编码是否一致,这个方法需要通过 hashCode()完成,即:每个对象有唯一的编码
· 还需要进一步验证对象中的每个属性是否相等,需要通过 equals()完成。 所以此时需要覆写 Object 类中的 hashCode()方法,此方法表示一个唯一的编码,一般是通过公式计算出来的。
小结:
关于 TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现 Comparable 接口,则不能实现 TreeSet 的排序,会报类型转换(转向 Comparable 接口)错误。 换句话说要添加到 TreeSet 集合中的对象的类型必须实现了 Comparable 接口。
不过 TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是 Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet 集合中也是不能去掉重复值的。
Map接口
以上的 Collection 中,每次操作的都是一个对象,如果现在假设要操作一对对象,则就必须使用 Map 了,类似于以 下一种情况:
那么保存以上信息的时候使用 Collection 就不那么方便,所以要使用 Map 接口。里面的所有内容都按照 key-value (键值对)的形式保存,也称为二元偶对象。
此接口定义如下:
public interface Map<K,V>
此接口与 Collection 接口没有任何的关系,是第二大的集合操作接口。此接口常用方法如下:
Map 本身是一个接口,所以一般会使用以下的几个子类:HashMap、TreeMap、Hashtable
HashMap
HashMap 是 Map 的子类,此类的定义如下:
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>,Cloneable,Serializable
此类继承了 AbstractMap 类,同时可以被克隆,可以被序列化下来。
范例:向集合中增加内容
package org.listdemo.hashmapdemo;
import java.util.HashMap;
import java.util.Map;
public class HashMapDemo01 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "张三A");
map.put(1, "张三B");
// 新的内容替换掉旧的内容
map.put(2, "李四");
map.put(3, "王五");
String val = map.get(1);
System.out.println(val);
}
}
以上的操作是 Map 接口在开发中最基本的操作过程,根据指定的 key 找到内容,如果没有找到,则返回 null,找到 了则返回具体的内容。
以上的操作是 Map 接口在开发中最基本的操作过程,根据指定的 key 找到内容,如果没有找到,则返回 null,找到 了则返回具体的内容。
范例:得到全部的 key 或 value
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class HashMapDemo02 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "张三A");
map.put(2, "李四");
map.put(3, "王五");
Set<Integer> set = map.keySet();
// 得到全部的key
Collection<String> value = map.values();
// 得到全部的value
Iterator<Integer> iter1 = set.iterator();
Iterator<String> iter2 = value.iterator();
System.out.print("全部的key:");
while (iter1.hasNext()) {
System.out.print(iter1.next() + "、");
}
System.out.print("\n全部的value:");
while (iter2.hasNext()) {
System.out.print(iter2.next() + "、");
}
}
}
Hashtable
Hashtable 是一个最早的 keyvalue 的操作类,本身是在 JDK1.0的时候推出的。其基本操作与 HashMap 是类似的。
操作的时候,可以发现与 HashMap 基本上没有什么区别,而且本身都是以 Map 为操作标准的,所以操作的结果形式 都一样。但是 Hashtable 中是不能向集合中插入 null 值的。(HashMap可以存储null键和null值)
HashMap 与 Hashtable 的区别
在整个集合中除了 ArrayList 和 Vector 的区别之外,另外一个最重要的区别就是 HashMap 与 Hashtable 的区别。
TreeMap
TreeMap 子类是允许 key 进行排序的操作子类,其本身在操作的时候将按照 key 进行排序,另外,key 中的内容可以 为任意的对象,但是要求对象所在的类必须实现 Comparable 接口。
package org.listdemo.treemapdemo;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class TreeMapDemo01 {
public static void main(String[] args) {
Map<String, String> map = new TreeMap<String, String>();
map.put("ZS", "张三");
map.put("LS", "李四");
map.put("WW", "王五");
map.put("ZL", "赵六");
map.put("SQ", "孙七");
Set<String> set = map.keySet();
// 得到全部的key
Iterator<String> iter = set.iterator();
while (iter.hasNext()) {
String i = iter.next();
// 得到key
System.out.println(i + " --:> " + map.get(i));
}
}
Iterator接口
已经学习过了基本的集合操作,那么对于集合的输出本身也是有多种形式的,可以使用如下的几种方式:
· Iterator 迭代输出(90%)、ListIterator(5%)、Enumeration(1%)、foreach(4%)
但是在讲解输出的时候一定要记住以下的原则:“只要是碰到了集合,则输出的时候想都不想就使用 Iterator 进行输出。”
Iterator 属于迭代输出,基本的操作原理:是不断的判断是否有下一个元素,有的话,则直接输出。
此接口定义如下:
public interface Iterator<E>
要想使用此接口,则必须使用 Collection 接口,在 Collection 接口中规定了一个 iterator()方法,可以用于为 Iterator 接口进行实例化操作。
此接口规定了以下的三个方法:
通过 Collection 接口为其进行实例化之后,一定要记住,Iterator 中的操作指针是在第一条元素之上,当调用 next()方 法的时候,获取当前指针指向的值并向下移动,使用 hasNext()可以检查序列中是否还有元素。
package org.listdemo.iteratordemo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo01 {
public static void main(String[] args) {
Collection<String> all = new ArrayList<String>();
all.add("A"); all.add("B");
all.add("C"); all.add("D");
all.add("E");
Iterator<String> iter = all.iterator();
while (iter.hasNext()) {
// 判断是否有下一个元素
String str = iter.next();
// 取出当前元素
System.out.print(str + "、");
}
}
}
以上的操作是 Iterator 接口使用最多的形式,也是一个标准的输出形式。
但是在使用 Iterator 输出的时候有一点必须注意,在进行迭代输出的时候如果要想删除当前元素,则只能使用 Iterator 接口中的 remove()方法,而不能使用集合中的 remove()方法。否则将出现未知的错误。
package org.listdemo.iteratordemo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo02 {
public static void main(String[] args) {
Collection<String> all = new ArrayList<String>();
all.add("A");
all.add("B");
all.add("C");
all.add("D");
all.add("E");
Iterator<String> iter = all.iterator();
while (iter.hasNext()) {
// 判断是否有下一个元素
String str = iter.next();
// 取出当前元素
if (str.equals("C")) {
all.remove(str);
// 错误的,调用了集合中的删除
} else {
System.out.print(str + "、");
}
}
此时出现了错误,因为原本的要输出的集合的内容被破坏掉了。
Iterator<String> iter = all.iterator();
while (iter.hasNext()) {
// 判断是否有下一个元素
String str = iter.next();
// 取出当前元素
if (str.equals("C")) {
iter.remove();
// 注意:调用迭代器的删除方法
} else {
System.out.print(str + "、");
}