13. Java类集 Part 1(类集、Collection接口、List接口、Set接口、SortedSet接口、集合的输出) ----- 学习笔记

来源:互联网 发布:歼二十 知乎 编辑:程序博客网 时间:2024/05/21 09:25

本章目标:

  • 了解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 基本概念

        之前,保存一组对象只能使用对象数组。但是使用对象数组有一些限制条件:就是数组有长度的限制;而通过一些数据接口的操作,如链表,则可以完成动态对象数组的操作。但是这些如果全部由开发人员来做是比较麻烦的!!!

       类集框架很好的解决了上面的难题。

      所谓的类集就是一个动态的对象数组,是对一些实现好的数据接口进行了包装!这样在使用时就会非常方便;而且最重要的是类集框架本身不受对象数据长度的限制!!!!!

      类集框架被设计成拥有以下几个特性:

  1. 这种框架是高性能的,对基本类集(动态数组、链接表、树和散列表)的实现是高效率的。所以一般很少需要人工去对这些“数据引擎”编写代码
  2. 框架必须允许不同类型的类集以相同的方法是高度互操作方式功能
  3. 类集必须是容易扩展和修改的。为了实现这一目标,类集框架被设计成包含了一组标准接口

Tips:对于相同的方式及高度的解释

     在类集的操作中,因为是使用类的形式实现的动态对象数组,所以对于任何对象所有的操作形式都是一样的。例如,只要是想向集合中增加内容,则一定使用add()方法。

     高度一般指的是类集中的元素类型都是同一的。即一个集合中要么全部是A类对象,要么全部是B类对象。

     13.1.2 类集框架主要接口

在整个Java类集中,最常使用的类集接口是:

  • Collection (java.util.Collection)
  • List
  • Set
  • Map
  • Iterator
  • ListIterator
  • Enumeration
  • SortedSet
  • SortedMap
  • Queue
  • Map.Entry

  这些接口的具体特点如下:

  1. Collection接口:           是存放一组单值的最大接口。 所谓的单值是指集合中的每个元素都是一个对象。一般很少直接使用此接口直接操作!
  2. List接口:                     是Collection接口的子接口,也是最常用的接口。此接口对Collection接口进行了大量的扩充,里面的内容是允许重复的!
  3. Set接口:                     是Collection接口的子类, 没有对Collection接口进行扩充,里面不允许存放重复内容!
  4. Map接口:                   是存放一对值的最大接口!即接口中的每个元素都是一对, 以key --> value的形式保存
  5. Iterator接口:               集合的输出接口,用于输出集合中的内容,只能进行从前到后的单向输出!!!
  6. ListIterator接口:         是Iterator接口的子接口,可以进行双向输出
  7. Enumeration接口:     是最早的输出接口,用于输出指定集合中的内容
  8. SortedSet接口:         单值的排序接口。实现此接口的集合类,里面的内容可以使用比较器排序
  9. SortedMap接口:       存放一对值得排序接口。实现此接口的集合类,里面的内容按照key排序,使用比较器排序
  10. Queue接口:              队列接口。此接口的子类可以实现队列操作
  11. 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()方法找到的表头:A1-2、找完之后的链表内容:[A, B, C]2-1、peek()方法找到的表头:A2-2、找完之后的链表内容:[A, B, C]3-1、poll()方法找到的表头:A3-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.Comparableat 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最后一个元素:EheadSet元素:[A, B]tailSet元素:[C, D, E]subSet元素:[B, C]

13.6 集合的输出

       如果要输出Collection、Set集合中的内容,可以将其转换为对象数组输出;使用List接口则可以直接通过get()方法输出;但是这些都不是集合的标准输出方式。

       在类集众提供了以下4种常见的输出方式:

  1. Iterator: 迭代输出,是使用最多的输出方式
  2. ListIterator: 是Iterator的子接口,专门用于输出List中的内容
  3. Enumeration: 是一个旧的接口,功能与Iterator类似
  4. 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接口。所以掌握它的操作也是很有必要的!!!

==========================================================


1 0