本章目标:
- 了解Java设置类集的主要目的
- 掌握Collection接口的作用及主要操作方法
- 掌握Collection子接口List、Set的区别及常用子类的使用
- 掌握Map接口的作用及与Collection接口的区别,并掌握Map接口的常用字类
- 掌握SortedSet、SortedMap接口的排序原理
- 掌握集合的3种常用输出方式: Iterator、Enumeration和foreach
- 掌握Properties类的使用
- 掌握集合的实际操作范例
- 了解类集工具类Collections的作用
类集是Java中的一个重要特性,在实际开发中占有很重要的地位,如果要写出一个好的程序,则一定要将类集的作用和各个组成部分的特点掌握清楚。本章将对Java类集进行完整的介绍,针对于一些较为常用的操作也将进行深入的讲解。
在JDK1.5之后,为了使类集操作更加安全,对类集框架进行了修改,加入了泛型的操作。
Note 在使用各个类集接口时,如果没有指定泛型,则肯定会出现警告信息。 (此时,泛型将被擦除,全部使用Object接收!!!)
13.1 认识类集
13.1.1 基本概念
之前,保存一组对象只能使用对象数组。但是使用对象数组有一些限制条件:就是数组有长度的限制;而通过一些数据接口的操作,如链表,则可以完成动态对象数组的操作。但是这些如果全部由开发人员来做是比较麻烦的!!!
类集框架很好的解决了上面的难题。
所谓的类集就是一个动态的对象数组,是对一些实现好的数据接口进行了包装!这样在使用时就会非常方便;而且最重要的是类集框架本身不受对象数据长度的限制!!!!!
类集框架被设计成拥有以下几个特性:
- 这种框架是高性能的,对基本类集(动态数组、链接表、树和散列表)的实现是高效率的。所以一般很少需要人工去对这些“数据引擎”编写代码
- 框架必须允许不同类型的类集以相同的方法是高度互操作方式功能
- 类集必须是容易扩展和修改的。为了实现这一目标,类集框架被设计成包含了一组标准接口
Tips:对于相同的方式及高度的解释
在类集的操作中,因为是使用类的形式实现的动态对象数组,所以对于任何对象所有的操作形式都是一样的。例如,只要是想向集合中增加内容,则一定使用add()方法。
高度一般指的是类集中的元素类型都是同一的。即一个集合中要么全部是A类对象,要么全部是B类对象。
13.1.2 类集框架主要接口
在整个Java类集中,最常使用的类集接口是:
- Collection (java.util.Collection)
- List
- Set
- Map
- Iterator
- ListIterator
- Enumeration
- SortedSet
- SortedMap
- Queue
- Map.Entry
这些接口的具体特点如下:
- Collection接口: 是存放一组单值的最大接口。 所谓的单值是指集合中的每个元素都是一个对象。一般很少直接使用此接口直接操作!
- List接口: 是Collection接口的子接口,也是最常用的接口。此接口对Collection接口进行了大量的扩充,里面的内容是允许重复的!
- Set接口: 是Collection接口的子类, 没有对Collection接口进行扩充,里面不允许存放重复内容!
- Map接口: 是存放一对值的最大接口!即接口中的每个元素都是一对, 以key --> value的形式保存
- Iterator接口: 集合的输出接口,用于输出集合中的内容,只能进行从前到后的单向输出!!!
- ListIterator接口: 是Iterator接口的子接口,可以进行双向输出
- Enumeration接口: 是最早的输出接口,用于输出指定集合中的内容
- SortedSet接口: 单值的排序接口。实现此接口的集合类,里面的内容可以使用比较器排序
- SortedMap接口: 存放一对值得排序接口。实现此接口的集合类,里面的内容按照key排序,使用比较器排序
- Queue接口: 队列接口。此接口的子类可以实现队列操作
- Map.Entry接口: Map.Entry的内容接口,每一个Map.Entry对象都保存着一对 key --> value的内容,每个Map接口中都保存有多个Map.Entry接口实例
这些接口中本身是存在继承关系的,其中部分接口的继承关系如下所示:
Tips:SortedXxx定义的接口都属于排序接口。 在Java类集众凡是以Sorted开头的全部都属于排序的接口,如SortedSet、SortedMap
13.2 Collection接口
13.2.1 Collection接口的定义
Collection接口的定义如下:
public interface Collection<E> extends Iterable<E>
从接口的定义中可以看出,此接口使用了泛型的定义!在操作时必须指定具体的操作类型。这样可以保证类集操作的安全性,避免产生ClassCastException异常!!!
Tips:JDK1.5之后类集才增加了泛型的支持! 在JDK1.5之前的类集框架中可以存放任意的对象到集合中,这样一来在操作时就可以出现因为类型不统一而造成的ClassCastException异常。所以在JDK1.5之后为了保证类集众所有元素的类型一致,将类集框架进行了升级,加入了泛型!这样就可以保证一个集合中的全部元素的类型是统一的。
Collection接口是单值存放的最大父类接口,可以向其中保存多个单值(单个的对象)数据。此接口定义以下的方法:
在一般的开发中,往往很少直接使用Collection接口进行开发,基本上都是使用其子接口。
**********子接口主要有List、Set、Queue、SortedSet**********
Tips:关于Collection接口很少直接使用的说明!!! 在Java最早的程序开发中提倡使用Collection,这一点在EJB 2.x(Enterprise JavaBean, 专门用作分布式程序开发的一套标准规范)中体现得非常明显。但是随着Java的发展,为了让程序的开发及使用更加明确,例如,此集合中的内容是否可以重复、是否可以排序等,所以提倡直接使用Collection的子接口,这一点在Sun的开源项目 -- 宠物商店中特别明显!!!!!!!!!!!!!!!
13.2.2 Collection子接口的定义
Collection接口虽然是集合的最大接口,但是如果直接使用Collection接口进行操作,则表示的操作意义不明确,所以在Java开发中不提倡直接使用Collection接口。主要的子接口介绍如下:
- List: 可以存放重复的内容
- Set: 不能存放重复的内容,所有的重复内容靠hashCode()和equals()两个方法区分
- Queue: 队列接口
- SortedSet: 可以对集合中的数据进行排序
13.3 List接口
13.3.1 List接口的定义
List是Collection接口的子接口。其中可以保存重复的内容。其定义格式如下:
public interface List<E> extends Collection<E>
但是与Collection不同的是, 在List接口中大量地扩充了Collection接口。拥有了比Collection接口中更多的方法定义,其中有些方法还非常常用!! 下图列出了List中对Collection接口的扩展方法。
List接口比Collection接口扩充了更多的方法,而且这些方法操作起来很方便。但是如果要想使用此接口,则需要通过其子类进行实例化。
13.3.2 List接口的常用字类
- 新的子类: ArrayList
ArrayList是List的子类。 可以直接通过对象的多态性为List接口实例化。此类的定义如下:
//ArrayList的定义
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable
ArrayList类也继承了AbstractList类。
//Abstract类的定义:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>
AbstractList类也实现了List接口,所以可以直接使用ArrayList为List接口实例化。
- List接口中主要方法的使用:
(1)向集合中增加元素
- 要完成此类操作,可以直接使用Collection接口中定义的两个方法。
//增加一个元素
public boolean add(E o)
//增加一组元素
public boolean addAll(Collection<? extends E> c)
- 也可以使用List扩充的add()方法在指定位置处增加元素
//在指定位置处添加元素:
public void add(int index, E element)
范例:验证增加数据的操作
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
public class ArrayListDemo01{
public static void main(String args[]){
List<String> allList = null; //定义List、Collection对象
Collection<String> allCollection = null;
allList = new ArrayList<String>(); //实例化List、Collection对象,只能是String类型
allCollection = new ArrayList<String>();
allList.add("Hello"); //从Collection继承的方法
allList.add(0, "World"); //List接口自身扩充的方法
System.out.println(allList); //输出集合中的内容
allCollection.add("forfan06"); //增加数据
allCollection.add("www.csdn.net");
allList.addAll(allCollection); //从Collection继承的方法,增加一组数据
allList.addAll(0, allCollection); //List接口自身扩展的方法,增加一组数据
System.out.println(allList); //输出对象,调用toString()方法
}
}
运行结果:
[World, Hello]
[forfan06, www.csdn.net, World, Hello, forfan06, <a target=_blank href="http://www.csdn.net">www.csdn.net</a>]
可以看出,使用List中的add(int index, E element)方法可以在集合中的指定位置增加元素;而其他的两个add()方法只是在集合的最后进行内容的追加!!
问题:为什么输出结果会用“[]”括起来呢??????
(2)删除元素
- 在类集中提供了专门的删除元素的方法,Collection和List接口都分别定义了删除数据的方法
//Collection定义的方法
public boolean remove(Object o) //每次删除一个对象
public boolean removeAll(Collection<?> c) //每次删除一组对象
//List扩展的方法
public E remove(int index) //删除指定位置的元素
范例:删除对象
package org.forfan06.listdemo;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
public class ArrayListDemo02{
public static void main(String args[]){
List<String> allList = null;
allList = new ArrayList<String>();
allList.add("Hello");
allList.add(0, "World");
allList.add("forfan06");
allList.add("www.csdn.net");
allList.remove(0); //删除指定位置的元素 List接口扩充的方法
allList.remove("Hello"); //删除指定内容的元素 Collection中定义的方法
System.out.println(allList);
}
}
运行结果:
[forfan06, <a target=_blank href="http://www.csdn.net">www.csdn.net</a>]
在集合中增加完数据后,可以通过下标或对象的方式直接对集合中的元素进行删除!!!
- 关于remove(Object o)方法删除对象的说明:在集合中可以插入任意类型的对象。在ArrayListDemo02.java中是以String类的对象为例,所以在使用remove(Object o)方法删除时可以直接删除;而对于自定义的类,如果要通过此方式删除,则必须在类中覆写Object类的equals()、hashCode()两个方法。
(3)输出List中的内容
在Collection接口中定义了取得全部数据长度的方法size(), 而在List接口中存在取得集合中指定位置元素的操作get(int index), 使用这两个方法即可输出集合中的全部内容!!!!
范例:输出全部元素
package org.forfan06.listdemo;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
public class ArrayListDemo03{
public static void main(String args[]){
List<String> allList = null;
allList = new ArrayList<String>();
allList.add("Hello");
allList.add("Hello");
allList.add(0, "World");
allList.add("forfan06");
allList.add("www.csdn.net");
System.out.print("由前向后输出:");
for(int i = 0; i < allList.size(); i++){
System.out.print(allList.get(i) + "、");
}
System.out.print("\n由后向前输出:");
for(int i = allList.size() -1; i >= 0; i--){
System.out.print(allList.get(i) + "、");
}
}
}
输出结果:
由前向后输出:World、Hello、Hello、forfan06、www.csdn.net、
由后向前输出:www.csdn.net、forfan06、Hello、Hello、World、
从程序的运行结果可以看出: 在List集合中数据增加的顺序就是输出后的顺序,本身顺序不会发生改变。
(4)将集合变为对象数组
在Collection中定义了toArray()方法,此方法可以将集合变为对象数组。 但是由于在类集声明时已经通过泛型指定了集合中的元素类型,所以在接收时要使用泛型指定的类型。
范例: 将集合变为对象数组
package org.forfan06.listdemo;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
public class ArrayListDemo03{
public static void main(String args[]){
List<String> allList = null;
allList = new ArrayList<String>();
allList.add("Hello");
allList.add("Hello");
allList.add(0, "World");
allList.add("forfan06");
allList.add("www.csdn.net");
String str[] = allList.toArray(new String[] {}); //这里修改为String str[] = allList.toArray(new String[0]);也可以正确运行
//String[] tempObj = new String[]{};
//String[] str = allList.toArray(tempObj);
System.out.print("指定数组类型:");
for(int i = 0; i < str.length; i++){
System.out.print(str[i] + "、");
}
System.out.print("\n返回对象数组:");
Object obj[] = allList.toArray();
for(int i = 0; i < obj.length; i++){
String temp = (String) obj[i];
System.out.print(temp + "、");
}
}
}
输出结果:
指定数组类型:World、Hello、Hello、forfan06、www.csdn.net、
返回对象数组:World、Hello、Hello、forfan06、www.csdn.net、
代码分析:
String[] str = new String[] {}; //属于静态初始化!!!
//大括号的意思是初始化,前面定义的String[] str; 但是现在大括号里面是空的,也就是没有内容。打印str的长度是0
//如果这样定义String[] str = new String[] {"111", "2222"}; str的长度就为2
//综上所述,大括号的作用就是初始化!!!!
===================================================
补充说明:String数组初始化!!!!来自这里 和这里
//一维数组
String[] str = new String[5]; //创建一个长度为5的String类型的一维数组
String[] str = new String[] {"", "", "", "", ""};
String[] str = {"", "", "", "", ""};
//二维数组
String[][] = new String[2][2]; //创建一个2行2列的二维数组
String数组初始化区别
String[] str = {"1", "2", "3"}与String[] = str = new String[]{"1", "2", "3"}在内存里有什么区别??
编译执行结果没有任何区别。更不可能像别人说的在栈上分配空间,Java的对象都是在堆上分配空间的。
这里的区别仅仅是代码书写上的:
String[] str = {"1", "2", "3"}; 这种形式叫 数组初始化式(Array Initializer),只能用在声明同时赋值的情况下
而String[] = str = new String[]{"1", "2", "3"}是一般性是的赋值,= 号右边的叫数组字面量(Array Literal),数组字面量可以用在任何需要一个数组的地方(类型兼容的情况下)。 例如:
String[] str = {"1", "2", "3"}; //正确
String[] str = new String[]{"1", "2", "3"} //正确
但是:
String[] str;
str = {"1", "2", "3"}; //编译错误
因为,数组初始化式只能用于声明同时赋值的情况下。
下面:
String[] str;
str = new String[]{"1", "2", "3"} ; //编译正确。
又如:
void f(String[] str){
}
f({"1", "2", "3"}); //编译错误
正确的应该是:
f(new String[]{"1", "2", "3"});
notice: String[] str = new String[]{}相当于创建了一个长度为0的String类型的一维数组。不能进行str[0] = "A"的赋值!!!!!!!!!!
===================================================
(5)集合的其他相关操作
在List中还存在截取集合、查找元素位置、判断元素是否存在、集合是否为空 等等操作。
范例:测试集合的其他操作
package org.forfan06.listdemo;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
public class ArrayListDemo05{
public static void main(String args[]){
List<String> allList = null;
allList = new ArrayList<String>();
System.out.println("集合操作前是否为空?" + allList.isEmpty());
allList.add("Hello");
allList.add(0, "World");
allList.add("forfan06");
allList.add("www.csdn.net");
System.out.println(allList.contains("Hello") ? "\"Hello\"字符串存在!!" : "\"Hello\"字符串不存在!!");
List<String> allSub = allList.subList(2, 3); //取出里面的部分集合
System.out.print("集合截取:");
for(int i = 0; i < allSub.size(); i++){
System.out.print(allSub.get(i) + "、");
}
System.out.println("");
System.out.println("forfan06字符串的位置:" + allList.indexOf("forfan06"));
System.out.println("集合操作后是否为空??" + allList.isEmpty());
}
}
输出结果:
集合操作前是否为空?true
"Hello"字符串存在!!
集合截取:forfan06、
forfan06字符串的位置:2
集合操作后是否为空??false
- 挽救的子类: Vector
在List接口中还有一个子类Vector。 Vector类属于一个挽救的子类,从整个Java的集合发展历史来看,Vector类算是一个元老级的类, 在JDK1.0时就已经存在Vector类。到了Java2(JDK1.2)之后重点强调了集合框架的概念,所以先后定义了很多的新接口(如List等等), 但是考虑到一大部分用户已经习惯了使用Vector类,所以在Java的后续版本中让Vector类多实现了一个List接口!!!这才将其保留了下来。。。但是因为其是List的子类,所以Vector类的使用与之前的并没有太大的区别。
//Vector类的定义:
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable
Vector类与ArrayList类一样也是继承自AbstractList类
范例:Vector子类!!!
package org.forfan06.listdemo;
import java.util.Collection;
import java.util.List;
import java.util.Vector;
public class VectorDemo01{
public static void main(String args[]){
List<String> allList = null;
allList = new Vector<String>();
allList.add("Hello");
allList.add(0, "World");
allList.add("forfan06");
allList.add("www.csdn.net");
for(int i = 0; i< allList.size(); i++){
System.out.print(allList.get(i) + "、");
}
}
}
运行结果:
World、Hello、forfan06、www.csdn.net、
此时,直接使用的是List接口的方法进行操作的。在运行结果上与ArrayList类没有任何的区别。
但是,因为Vector类出现较早,所以也定义了许多在List接口中没有定义的方法,这些方法的功能与List类似。
例如,Vector类中的addElement(E o)方法,是最早的向集合中增加元素的操作,但是在JDK1.2之后此方法的功能与List接口中的add(E o)方法是一致的。
范例:使用Vector类的方法!!!
package org.forfan06.listdemo;
import java.util.Collection;
import java.util.List;
import java.util.Vector;
public class VectorDemo02{
public static void main(String args[]){
Vector<String> allList = null;
allList = new Vector<String>();
allList.addElement("Hello");
//allList.addElement(0, "World"); addElement()方法没有向指定位置插入数据的功能!!
allList.addElement("forfan06");
allList.addElement("www.csdn.net");
for(int i = 0; i < allList.size(); i++){
System.out.print(allList.get(i) + "、");
}
}
}
- List子类的差异: ArrayList类和Vector类的区别
从实际的应用开发来看, ArrayList类使用较多。应该重点掌握!!!!
- LinkedList子类与Queue接口
LinkedList表示的是一个链表的操作类, 即Java中已经为开发者提供了一个链表程序,开发者直接使用即可,无须再重新开发。
//LinkedList类的定义:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Queue<E>, Cloneable, Serializable
LinkedList类既实现了List接口,同时,也实现了Queue接口。其中,Queue表示队列的操作接口,它采用的是FIFO(先进先出)的操作方式。
队头永远指向新加入的对象!!!!
注意:先进先出(First Input First Output, FIFO)
先进先出指的是在一个集合中,会按照顺序增加内容,在输出时先进如的数据会首先输出。 与之对应的还有另外一种方式:先进后出(堆栈操作好象就是先进后出!!!),指的是先进入的数据最后取出,而后进入的数据最先取出,在随后的栈(Stack)就是采用的这种方式。
Queue接口是Collection接口的子接口,
//Queue接口的定义如下:
public interface Queue<E> extends Collection<E>
Queue接口也可以增加元素并输出。此接口的方法定义如下:
在LinkedList类中除了实现Queue接口中定义的方法之外,还提供了一下几个链表操作方法:
下面,通过几个程序来了解此类的操作方法:
(1)在链表的开头和结尾增加数据! 为了实现操作链表, 必须直接使用LinkedList类。因为List接口中没有定义Queue接口中定义关于链表的操作方法!!!
实例:为链表增加数据
package org.forfan06.listdemo;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.LinkedList;
public class LinkedListDemo01{
public static void main(String args[]){
LinkedList<String> link = new LinkedList<String>();
link.add("A");
link.add("B");
link.add("C");
System.out.println("初始化链表:" + link);
link.addFirst("X");
link.addLast("Y");
System.out.println("增加头和尾元素之后的链表:" + link);
}
}
运行结果:
初始化链表:[A, B, C]
增加头和尾元素之后的链表:[X, A, B, C, Y]
(2)找到链表头元素。
在LinkedList类中存在很多找到链表头的操作, 其中最常用的如下:
- 找到表头: public E element();
- 找到但不删除表头: public E peek();
- 找到并且删除表头: public E poll();
范例:找到表头
package org.forfan06.listdemo;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.LinkedList;
public class LinkedListDemo02{
public static void main(String args[]){
LinkedList<String> link = new LinkedList<String>();
link.add("A");
link.add("B");
link.add("C");
System.out.println("初始化链表:" + link);
System.out.println("1-1、element()方法找到的表头:" + link.element());
System.out.println("1-2、找完之后的链表内容:" + link);
System.out.println("2-1、peek()方法找到的表头:" + link.peek());
System.out.println("2-2、找完之后的链表内容:" + link);
System.out.println("3-1、poll()方法找到的表头:" + link.poll());
System.out.println("3-2、找完之后的链表内容:" + link);
}
}
运行结果:
初始化链表:[A, B, C]
1-1、element()方法找到的表头:A
1-2、找完之后的链表内容:[A, B, C]
2-1、peek()方法找到的表头:A
2-2、找完之后的链表内容:[A, B, C]
3-1、poll()方法找到的表头:A
3-2、找完之后的链表内容:[B, C]
(3)以先进先出的方式取出全部数据
在LinkedList类中存在poll()方法来找到表头,并且删除了表头!! 通过循环此方法操作表头,就可以把内容全部取出(以先进先出FIFO的方式)
范例: 以FIFO方式取出内容
package org.forfan06.listdemo;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.LinkedList;
public class LinkedListDemo03{
public static void main(String args[]){
LinkedList<String> link = new LinkedList<String>();
link.add("A");
link.add("B");
link.add("C");
System.out.println("初始化链表:" + link);
System.out.print("以FIFO方式输出:");
for(int i = 0; i < link.size() + 2; i++){ //以为循环体中,poll()方法会修改链表,所以link.size()每执行一次循环体都会变化
System.out.print(link.poll() + "、");
}
/*上面也可以用以下代码来替代:
int num = link.size();
for(int i = 0; i < num; i++){
System.out.print(link.poll() + "、");
}
*/
}
}
对比一下代码。 修改for循环的次数 for(int i = 0; i < link.size(); i++)
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.LinkedList;
public class LinkedListDemo04{
public static void main(String args[]){
LinkedList<String> link = new LinkedList<String>();
link.add("A");
link.add("B");
link.add("C");
System.out.println("初始化链表:" + link);
System.out.print("以FIFO方式输出:");
for(int i = 0; i < link.size(); i++){ //以为循环体中,poll()方法会修改链表,所以link.size()每执行一次循环体都会变化
System.out.print(link.poll() + "、");
}
/*上面也可以用以下代码来替代:
int num = link.size();
for(int i = 0; i < num; i++){
System.out.print(link.poll() + "、");
}
*/
}
}
运行结果是:
初始化链表:[A, B, C]
以FIFO方式输出:A、B、
=============================================
copy From http://bbs.bccn.net/thread-404740-1-1.html
这个很有意思,不是那么容易发觉,出现问题的地方是 link.size()+1 和poll()的组合, poll()是移除并返回第一个数据,关键是存在移除动作,它会改变link.size()+1 的数值。
for循环会是这样的结果
0 5
1 4
2 3
3 2/这个地方就是条件不符合了,推出循环,
所以for循环只执行了3次,所以只能执行到移除C。
你在for循环里加一个System.out.println( link.size());就能清楚看到这个变化了。
=============================================
这里也可以看出for循环语句中,判断次数的变量会在执行完循环提后,重新计算一次。再进行判断!!!!
=============================================
13.4 Set接口
13.4.1 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)方法!!!!
13.4.2 Set接口的常用子类
-
散列的存放:HashSet
HashSet类是Set接口的一个子类。 其主要特点: 里面不能存放重复元素,而且采用散列的存储方式,所以没有顺序!!!
范例:验证HashSet类
package org.forfan06.setdemo;
import java.util.Collection;
import java.util.Set;
import java.util.HashSet;
public class HashSetDemo01{
public static void main(String args[]){
Set<String> allSet = new HashSet<String>();
allSet.add("A");
allSet.add("B");
allSet.add("C");
allSet.add("B"); //重复元素,不能加入!!!
allSet.add("C"); //重复元素,不能加入!!!
allSet.add("D");
allSet.add("E");
allSet.add("F");
allSet.add("A"); //重复元素,不能加入!!!
allSet.add("D"); //重复元素,不能加入!!!
System.out.println(allSet); //输出集合对象,调用toString()
}
}
运行结果:
[D, E, F, A, B, C]
从结果可以看出:HashSet类对于重复元素,只能增加一次。而且程序运行时向集合中加入元素的顺序并不是集合中的保存顺序。从而,证明了HashSet类中的元素是无序排列的!!!!
-
有序的存放: TreeSet类
如果想对输入的数据进行有序排列,可以使用TreeSet子类!!!!
//TreeSet类的定义:TreeSet类继承了AbstractSet类
public class TreeSet<E> extends AbstractSet<E> impements SortedSet<E>, Cloneable, Serializable
<pre class="java" name="code">//AbstractSet类的定义:
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E>
范例:验证TreeSet类
package org.forfan06.setdemo;
import java.util.Collection;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetDemo01{
public static void main(String args[]){
Set<String> allSet = new TreeSet<String>();
allSet.add("D");
allSet.add("E");
allSet.add("B");
allSet.add("C");
allSet.add("C"); //重复元素,不能添加
allSet.add("A");
allSet.add("D"); //重复元素,不能添加
System.out.println(allSet);
}
}
运行结果:
[A, B, C, D, E]
程序代码
在向集合中插入数据时是没有顺序的,但是,输出之后的数据是有序的。可以推断出TreeSet类可以进行排序的!!!
-
关于TreeSet类的排序说明(自定义类的排序)
既然TreeSet类本身是可以进行排序操作的。那么现在定义一个自己的类,是否也可以进行排序的操作呢???
(1) 范例:自定义类的排序
package org.forfan06.setdemo;
import java.util.Collection;
import java.util.Set;
import java.util.TreeSet;
class Person{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + ";年龄:" + this.age;
}
}
public class TreeSetDemo02{
public static void main(String args[]){
Set<Person> allSet = new TreeSet<Person>();
allSet.add(new Person("Eric", 30));
allSet.add(new Person("Linda", 29));
allSet.add(new Person("Tyler", 17));
allSet.add(new Person("forfan06", 27));
allSet.add(new Person("Dylan", 21));
allSet.add(new Person("Linda", 27));
allSet.add(new Person("forfan06", 27)); //重复元素,不能加入
allSet.add(new Person("Cassie", 17)); //年龄重复
allSet.add(new Person("forfan06", 27)); //重复元素,不能加入
System.out.println(allSet);
}
}
编译出错:
Exception in thread "main" java.lang.ClassCastException: Person cannot be cast to java.lang.Comparable
at java.util.TreeMap.compare(TreeMap.java:1188)
at java.util.TreeMap.put(TreeMap.java:531)
at java.util.TreeSet.add(TreeSet.java:255)
at TreeSetDemo02.main(Unknown Source)
以上程序出现了类转换异常(ClassCastException异常),是因为TreeSet类中的元素是有序存放的。所以,
================================================================
对于一个对象必须指定好它的排序规则,并且TreeSet类中的每一个对象所在的类都必须实现Comparable接口才可以正常使用!!!!!!!!!!!!
================================================================
(2)范例:指定排序规则
package org.forfan06.setdemo;
import java.util.Collection;
import java.util.Set;
import java.util.TreeSet;
class Person implements Comparable<Person>{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + ";年龄:" + this.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 class TreeSetDemo03{
public static void main(String args[]){
Set<Person> allSet = new TreeSet<Person>();
allSet.add(new Person("Eric", 30));
allSet.add(new Person("Linda", 29));
allSet.add(new Person("Tyler", 17));
allSet.add(new Person("forfan06", 27));
allSet.add(new Person("Dylan", 21));
allSet.add(new Person("Linda", 27));
allSet.add(new Person("forfan06", 27)); //重复元素,不能加入
allSet.add(new Person("Cassie", 17)); //年龄重复
allSet.add(new Person("forfan06", 27)); //重复元素,不能加入
System.out.println(allSet);
}
}
运行结果:
[姓名:Tyler;年龄:17, 姓名:Dylan;年龄:21, 姓名:forfan06;年龄:27, 姓名:Linda;年龄:29, 姓名:Eric;年龄:30]
结果分析:
1, 重复的元素 forfan06对象一个只有一个了。
2, Cassie和Tyler的数据中姓名并不重复,只是年龄相同,但是Cassie对象却没有加入到集合中。
产生上面(2)的原因是: 比较器造成的,因为在Person类中的比较器操作时如果某个属性没有进行比较的指定,则也会被认为是同一个对象。
所以此时,Person类中的比较器compareTo()方法中还需要增加按姓名的比较!!!
(3)范例:修改Person类中的比较器
package org.forfan06.setdemo;
import java.util.Collection;
import java.util.Set;
import java.util.TreeSet;
class Person implements Comparable<Person>{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + ";年龄:" + this.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); //调用了String类中的比较方法,来判断字符串的比较
}
}
}
public class TreeSetDemo03{
public static void main(String args[]){
Set<Person> allSet = new TreeSet<Person>();
allSet.add(new Person("Eric", 30));
allSet.add(new Person("Linda", 29));
allSet.add(new Person("Tyler", 17));
allSet.add(new Person("forfan06", 27));
allSet.add(new Person("Dylan", 21));
allSet.add(new Person("Linda", 27));
allSet.add(new Person("forfan06", 27)); //重复元素,不能加入
allSet.add(new Person("Cassie", 17)); //年龄重复
allSet.add(new Person("forfan06", 27)); //重复元素,不能加入
System.out.println(allSet);
}
}
运行结果:
[姓名:Cassie;年龄:17, 姓名:Tyler;年龄:17, 姓名:Dylan;年龄:21, 姓名:Linda;年龄:27, 姓名:forfan06;年龄:27, 姓名:Linda;年龄:29, 姓名:Eric;年龄:30]
以上程序运行结果中有了“Cassie”,而且去掉了重复的内容。
但是,此时的重复内容去掉,并不是真正意义上的去掉重复元素。因为此时依靠的是Comparable完成的;
而如果换成HashSet类则同样也会出现重复的内容。所以想要真正地去掉重复元素,则必须深入研究Object类。
=====================================================
补充String类(字符串)中的比较器
//String类的定义格式:
public final class String extends Object implements Serializable, Comparable<String>, CharSequence
可以看出String类也实现了Comparable接口!!
另外,String类中有一个比较方法:
public int compareTo(String anotherString)
=====================================================
-
关于重复元素的说明!!!
通过下面的demo来观察所谓的重复元素
(1)范例:加入重复对象
package org.forfan06.setdemo;
import java.util.Collection;
import java.util.Set;
import java.util.HashSet;
class Person{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + ";年龄:" + this.age;
}
}
public class RepeatDemo01{
public static void main(String args[]){
Set<Person> allSet = new HashSet<Person>();
allSet.add(new Person("Eric", 30));
allSet.add(new Person("Linda", 29));
allSet.add(new Person("Tyler", 17));
allSet.add(new Person("forfan06", 27));
allSet.add(new Person("Dylan", 21));
allSet.add(new Person("Linda", 27));
allSet.add(new Person("forfan06", 27)); //重复元素,不能加入
allSet.add(new Person("Cassie", 17)); //年龄重复
allSet.add(new Person("forfan06", 27)); //重复元素,不能加入
System.out.println(allSet);
}
}
运行结果:
[姓名:Tyler;年龄:17, 姓名:Eric;年龄:30, 姓名:Linda;年龄:27, 姓名:Cassie;年龄:17, 姓名:Linda;年龄:29, 姓名:forfan06;年龄:27, 姓名:Dylan;年龄:21, 姓名:forfan06;年龄:27, 姓名:forfan06;年龄:27]
“forfan06”的内容重复了,也就是说,此时的程序并没有像Set接口规定的那样是不允许有重复元素的。 而此时,想要去掉重复元素,则首先必须进行对象是否重复的判断!!!!
想要进行对象的重复判断,则类中就必须覆写Object类中的equals()方法, 才能完成对象是否相等的判断;但是,若只覆写equals()方法是不够的,还需要覆写hashCode()方法。 hashCode()方法表示一个哈希编码,可以简单地理解为表示一个对象的编码!!!一般的哈希码矢通过公式进行计算的。可以将类中的全部属性进行适当的计算,以求出一个不会重复的哈希码
(2)范例:去掉重复元素
package org.forfan06.setdemo;
import java.util.Collection;
import java.util.Set;
import java.util.HashSet;
class Person{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
//覆写equals()
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(!(obj instanceof Person)){
return false;
}
Person p = (Person) obj;
if(this.name.equals(p.name) && this.age == age){
return true;
}else{
return false;
}
}
//覆写hashCode()方法@override
public int hashCode(){
return this.name.hashCode() * this.age;
}
public String toString(){
return "姓名:" + this.name + ";年龄:" + this.age;
}
}
public class RepeatDemo02{
public static void main(String args[]){
Set<Person> allSet = new HashSet<Person>();
allSet.add(new Person("Eric", 30));
allSet.add(new Person("Linda", 29));
allSet.add(new Person("Tyler", 17));
allSet.add(new Person("forfan06", 27));
allSet.add(new Person("Dylan", 21));
allSet.add(new Person("Linda", 27));
allSet.add(new Person("forfan06", 27)); //重复元素,不能加入
allSet.add(new Person("Cassie", 17)); //年龄重复
allSet.add(new Person("forfan06", 27)); //重复元素,不能加入
System.out.println(allSet);
}
}
运行结果:
[姓名:Tyler;年龄:17, 姓名:forfan06;年龄:27, 姓名:Dylan;年龄:21, 姓名:Cassie;年龄:17, 姓名:Linda;年龄:29, 姓名:Linda;年龄:27, 姓名:Eric;年龄:30]
此时, 集合中的重复内容完全消失了。是equals()方法、hashCode()方法共同作用的结果。 此时就是真正意义上的消除了重复元素
===================================================
-
Object类的总结:
Object类中的方法!!!在实际的开发中经常会碰到区分同一个对象的问题!!!!
一个完整的类最好覆写Object类中的hashCode()、equals()、toString()这3个方法!!!!
===================================================
13.5 SortedSet接口
从TreeSet类的定义中可以发现,TreeSet类实现了SortedSet接口。
SortedSet接口主要用于排序操作,即,实现了此接口的子类都属于排序的子类!!!!
//SortedSet接口的定义格式:SortedSet接口继承了Set接口!!!!
public interface SortedSet<E> extends Set<E>
SortedSet类中定义了以下方法:
(1)范例:验证SortedSet接口
package org.forfan06.setdemo;
import java.util.Collection;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
public class TreeSetDemo05{
public static void main(String args[]){
SortedSet<String> allSet = new TreeSet<String>();
allSet.add("A");
allSet.add("B");
allSet.add("C");
allSet.add("B"); //重复元素,不能加入
allSet.add("C"); //重复元素,不能加入
allSet.add("D");
allSet.add("D"); //重复元素,不能加入
allSet.add("E");
System.out.println("第一个元素:" + allSet.first());
System.out.println("最后一个元素:" + allSet.last());
System.out.println("headSet元素:" + allSet.headSet("C")); //不包括char元素
System.out.println("tailSet元素:" + allSet.tailSet("C")); //包含char元素
System.out.println("subSet元素:" + allSet.subSet("B", "D"));
}
}
运行结果:
第一个元素:A
最后一个元素:E
headSet元素:[A, B]
tailSet元素:[C, D, E]
subSet元素:[B, C]
13.6 集合的输出
如果要输出Collection、Set集合中的内容,可以将其转换为对象数组输出;使用List接口则可以直接通过get()方法输出;但是这些都不是集合的标准输出方式。
在类集众提供了以下4种常见的输出方式:
- Iterator: 迭代输出,是使用最多的输出方式
- ListIterator: 是Iterator的子接口,专门用于输出List中的内容
- Enumeration: 是一个旧的接口,功能与Iterator类似
- foreach: JDK1.5之后提供的新功能,可以输出数组或集合
注意:4种输出操作以Iterator为操作的标准!!!! 以上虽然提供了4种输出的操作,但是从实际的使用上来看,Iterator接口是最常使用的输出形式!!!!
13.6.1 迭代输出:Iterator
1. Iterator接口简介:
“在使用集合输出时, 必须形成这样的思路: ‘只要是碰到了集合输出的操作,就一定使用Iterator接口’”,因为这是最标准的做法!!!
Iterator接口是专门的迭代输出接口,所谓的迭代输出就是将元素一个个进行判断,判断其是否有内容,如果有内容则把内容取出。如下图所示:
Iterator接口的定义:
//Iterator接口的定义格式:
public interface Iterator<E>
Iterator接口在使用时也需要指定泛型类型,当然在此处指定的泛型类型最好与集合中的泛型类型一致!!下面是Iterator接口中定义的方法:
2. Iterator接口的相关操作
(1)实例操作一: 输出Collection接口中的全部内容
Iterator是一个接口,可以直接使用Collection接口中定义的iterator()方法为其实例化!!!既然Collection接口中存在了此方法,则List和Set接口中也一定存在此方法,所以同样也可以使用Iterator接口输出!!!!
范例:进行输出
package org.forfan06.iteratordemo;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class IteratorDemo01{
public static void main(String args[]){
List<String> all = new ArrayList<String>();
all.add("Hello");
all.add("_");
all.add("world");
Iterator<String> iter = all.iterator(); //直接实例化Iterator接口对象
while(iter.hasNext()){ //依次判断
System.out.print(iter.next() + "、"); //Iterator.next()取出当前对象
}
}
}
运行结果:
Hello、_、world、
以上的输出代码,是Iterator的标准操作形式,将集合中的内容一个个地循环输出。此种输出也是必须掌握的形式!!!!
(2)实例操作二:使用Iterator删除指定内容
Iterator接口中,直接使用remove()方法可以删除当前的内容
范例:删除元素
package org.forfan06.iteratordemo;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class IteratorDemo02{
public static void main(String args[]){
List<String> all = new ArrayList<String>();
all.add("Hello");
all.add("_");
all.add("world");
Iterator<String> iter = all.iterator(); //直接实例化Iterator接口对象
while(iter.hasNext()){ //依次判断
String str = iter.next();
if("_".equals(str)){
iter.remove();
}else{
System.out.print(str + "、"); //Iterator.next()取出当前对象
}
}
System.out.println("\n删除之后的集合:" + all);
}
}
运行结果
Hello、world、
删除之后的集合:[Hello, world]
(3)实例操作三:迭代输出时删除元素的注意点
正常情况下,一个集合要把内容交给Iterator接口输出。但是,集合操作中也存在一个remove()方法,如果在使用Iterator输出时集合自己调用了删除方法,则会出现运行时的错误
范例:不正确的删除方法
package org.forfan06.iteratordemo;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class IteratorDemo03{
public static void main(String args[]){
List<String> all = new ArrayList<String>();
all.add("Hello");
all.add("_");
all.add("world");
Iterator<String> iter = all.iterator(); //直接实例化Iterator接口对象
while(iter.hasNext()){ //依次判断
String str = iter.next();
if("_".equals(str)){
//iter.remove(); //调用Iterator接口的remove方法
all.remove(str); //使用集合删除方法remove来删除当前元素
}else{
System.out.print(str + "、"); //Iterator.next()取出当前对象
}
}
System.out.println("\n删除之后的集合:" + all);
}
}
运行结果: 迭代输出的结果中,少了world这个元素!!!
Hello、
删除之后的集合:[Hello, world]
程序运行结果中,内容确实被删除了;但是,迭代输出在内容删除之后就终止了。因为集合本身的内容被破坏掉,所以迭代将出现错误,停止输出。
13.6.2 双向迭代输出: ListIterator
1. ListIterator接口简介
Iterator接口的主要功能是由前向后单向输出,而此时如果想要实现由后向前或是由前向后的双向输出,则必须使用Iterator接口的子接口 ---- ListIterator接口!!!!
//ListIterator接口的定义格式:
public interface ListIterator<E> extends Iterator<E>
ListIterator接口定义了比Iterator接口中更多的方法,如下所示:
=============================
与Iterator接口不同的是,ListIterator接口只能通过List接口来实例化,即只能输出List接口的内容。在List接口中定义了可以为ListIterator接口的实例化方法:
//ListIterator接口的实例化方法
public ListIterator<E> listIterator()
=============================
2. ListIterator接口的相关操作
(1)实例操作一:进行双向迭代
使用ListIterator接口中的hasPrevious()方法由后向前判断,并使用previous()方法取出前一个元素。
范例:进行双向迭代
package org.forfan06.iteratordemo;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Iterator;
public class ListIteratorDemo01{
public static void main(String args[]){
List<String> all = new ArrayList<String>();
all.add("Hello");
all.add("_");
all.add("world");
ListIterator<String> iter = all.listIterator(); //实例化ListIterator接口对象
System.out.print("由前向后输出:");
while(iter.hasNext()){ //依次判断
String str = iter.next();
System.out.print(str + "、"); //Iterator.next()取出当前对象
}
System.out.print("\n有后向前输出:");
while(iter.hasPrevious()){
String str = iter.previous();
System.out.print(str + "、");
}
}
}
运行结果:
由前向后输出:Hello、_、world、
有后向前输出:world、_、Hello、
=================================================
上面的程序,实现了双向的迭代输出;但是,此种输出方式只能List接口才可以做到!!!!
*****使用ListIterator接口进行双向输出时:想要完成由后向前输出时,必须要先由前向后输出*****
=================================================
(2)实例操作二:增加及替换集合中的元素
使用add()、set()方法可以增加或替换集合中的元素,但是这样的操作在开发中不建议使用!!!
范例:增加及替换集合中的元素
package org.forfan06.iteratordemo;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Iterator;
public class ListIteratorDemo02{
public static void main(String args[]){
List<String> all = new ArrayList<String>();
all.add("Hello");
all.add("_");
all.add("world");
ListIterator<String> iter = all.listIterator(); //实例化ListIterator接口对象
System.out.print("由前向后输出:");
while(iter.hasNext()){ //依次判断
String str = iter.next();
System.out.print(str + "、"); //Iterator.next()取出当前对象
iter.set("forfan-" + str);
}
System.out.print("\n有后向前输出:");
iter.add("csdn");
while(iter.hasPrevious()){
String str = iter.previous();
System.out.print(str + "、");
}
}
}
运行结果:
由前向后输出:Hello、_、world、
有后向前输出:csdn、forfan-world、forfan-_、forfan-Hello、
在ListIterator接口中使用set()方法修改了每个元素的内容,而且也可以使用ListIterator接口中的add()方法向集合中增加元素
13.6.3 Java新支持: foreach
使用foreach除了可以完成数组的输出;对于集合也同样支持。foreach的输出格式如下:
//foreach的输出格式:
for(类 对象:集合){
//集合操作
}
范例:使用foreach输出
package org.forfan06.iteratordemo;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Iterator;
public class ForeachDemo01{
public static void main(String args[]){
List<String> all = new ArrayList<String>();
all.add("Hello");
all.add("_");
all.add("world");
for(String str:all){
System.out.print(str + "、");
}
}
}
=============================================
虽然foreach输出的功能强大,而且操作的代码也比较简单;但是,从实际的开发上讲,还是建议使用Iterator接口完成输出功能!!!!
=============================================
13.6.4 废弃的接口:Enumeration
Enumeration接口是JDK1.0时就推出的,是最早的迭代输出接口; 最早使用Vector类时,就是使用Enumeration接口进行输出的。 其定义如下:
//Enumeration接口的定义:
public interface Enumeration<E>
虽然Enumeration接口是一个旧的接口,但是,在JDK1.5之后为Enumeration类进行了扩充,增加了泛型的操作应用。其主要方法如下:
以上的方法与Iterator类似,只是Iterator中存在删除数据的方法,而Enumeration接口并不存在删除操作;而且可以发现,这里的方法名称的定义要比Iterator中的方法名更长。
要想使用此接口可以通过Vector类,Vector类定义了一下的方法可以为Enumeration接口实例化。
//Enumeration接口的实例化
public Enumeration<E> elements()
范例:使用Enumeration输出
package org.forfan06.iteratordemo;
import java.util.Vector;
import java.util.List;
import java.util.Collection;
import java.util.Enumeration;
public class EnumerationDemo01{
public static void main(String args[]){
Vector<String> all = new Vector<String>();
all.add("hello");
all.add("_");
all.add("world");
Enumeration<String> enu = all.elements();
while(enu.hasMoreElements()){
System.out.print(enu.nextElement() + "、");
}
}
}
==========================================================
Enumeration接口和Iterator接口的功能非常类似,而且Enumeration接口中方法的名称也比Iterator接口中的方法名称长很多,那么为什么还要继续使用Enumeration接口呢????
在一些比较古老的系统或是类库的方法中(例如,Web开发中)还在使用Enumeration接口。所以掌握它的操作也是很有必要的!!!
==========================================================