黑马程序员——Java集合

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


一、学习目的

在完成了本模块的学习后,你应当能够:

  • 了解标准Java库提供的最基本的数据结构。
  • 容易地用Java编程语言实现任何传统的数据结构。

二、集合是什么?

Java提供了容纳对象(实际是对象的句柄)的多种方式。一是数组,更重要的是在java.util包中提供的一些“集合类”。

为明白集合类的真正含义,我们先看看数组。对于Java来说,为保存和访问一系列对象,最有效的方法莫过于数组。数组实际代表一个简单的线性序列,它使得元素的访问速度非常快,但创建一个数组对象后,数组对象的大小不能改变。

当我们编写程序时,通常并不能确切地知道最终需要多少个对象。例如我们希望从控制台接收不定数量的字符串,并随后打印出来。用数组如何去做?


<span style="font-family:SimSun;font-size:14px;"><span style="font-family:SimSun;font-size:14px;">  import java.io.*;
  import java.util.*;
  
  public class Hello {
      public static void main(String[] args) {
          String str = null;
          ArrayList c = new ArrayList();
  
          try {
              BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
              while (!(str = in.readLine()).equals("")) {
                  c.add(str);
              }
          } catch (IOException e) {
              //......
          }
  
          for (int i = 0; i < c.size(); i++) {
              System.out.println(((String) c.get(i)).toUpperCase());
          }
      }
  }</span></span>


ArrayList的使用是非常简单的:先创建一个,再用add()置入对象,以后用get()取得那些对象。所有Java集合类都能自动改变自身的大小,所以,我们在编程时可使用数量众多的对象,同时不必担心会将集合弄得有多大。

集合类将所有加入集合中的对象当作Object类型处理。也就是说任何Java对象都可以进入那个集合。但也将对象类型信息丢失了。集合实际容纳的是类型为Object的一些对象的句柄。这种类型当然代表Java中的所有对象,因为它是所有类的根。集合类只能容纳对象句柄。但对一个数组,却既可令其直接容纳基本类型的数据,亦可容纳指向对象的句柄。利用象Integer、Double之类的“封装器”类,可将基本数据类型的值置入一个集合里。

由于类型信息不复存在,所以集合能肯定的唯一事情就是自己容纳的是指向一个Object类型的句柄。所以任何类型的对象都可进入我们的集合——即便特别指示它只能容纳特定类型的对象。正式使用它之前,必须对其进行造型,使其具有正确的类型。如果造型错误,就会得到一个违例。

三、迭代器(Iterator)

我们经常会遍历集合中的每一个对象,并处理取出的每个对象

<span style="font-family:SimSun;font-size:14px;"><span style="font-family:SimSun;font-size:14px;">for (int i = 0; i < c.size(); i++) {
            System.out.println(((String) c.get(i)).toUpperCase());
        }</span></span>


我们可能有着种需要:对于一个集合,提供一种方法来让别人遍历,而又不需暴露它的内部结构。此外,针对不同的需要,可能以不同的方式遍历这个集合。但是即使可以预见到所需的那些遍历操作,你可能也不希望集合的接口中充斥着各种不同遍历的操作。

可利用迭代器(Iterator)的概念达到这个目的。它是将对集合的遍历从集合对象中分离出来并放入一个迭代器对象中。迭代器类定义了一个访问该列表元素的接口。迭代器对象负责跟踪当前的元素。

<span style="font-family:SimSun;font-size:14px;"><span style="font-family:SimSun;font-size:14px;">Iterator it = c.iterator();
        while (it.hasNext()) {
            System.out.println(((String) it.next()).toUpperCase());
        }</span></span>

Iterator接口:

(1) 用一个名为iterator()的方法要求集合为我们提供一个实现Iterator接口的对象。一旦有了该列表迭代器的实例,就可以顺序地访问该列表的各个元素。

       (2) 用next()获得序列中的下一个元素。我们首次调用它的next()时,这个Iterator对象会返回序列中的第一个元素。
  (3) 用hasNext()检查序列中是否还有更多的对象。即检查是否已越过最后一个元素,也就是说完成了这次遍历。


<span style="font-family:SimSun;font-size:14px;"><span style="font-family:SimSun;font-size:14px;">import java.util.*;

public class ArrayListIterator extends ArrayList {

    public Iterator iterator1() {
        return new Itr1();
    }

    public Iterator iterator2() {
        return new Itr2();
    }

    private class Itr1 implements Iterator {
        int cursor = size() - 1;

        public boolean hasNext() {
            return cursor != -1;
        }

        public Object next() {
            try {
                Object next = get(cursor--);
                return next;
            } catch (IndexOutOfBoundsException e) {
                throw new NoSuchElementException();
            }
        }
    }

    private class Itr2 implements Iterator {
        int cursor = 0;

        public boolean hasNext() {
            return cursor < size();
        }

        public Object next() {
            try {
                Object next = get(cursor);
                cursor = cursor + 2;
                return next;
            } catch (IndexOutOfBoundsException e) {
                throw new NoSuchElementException();
            }
        }
    }

    public static void main(String[] args) {
        ArrayListIterator c = new ArrayListIterator();
        c.add("111");
        c.add("222");
        c.add("333");
        c.add("444");
        c.add("555");
        Iterator it = c.iterator();
        while (it.hasNext()) {
            System.out.println(((String) it.next()).toUpperCase());
        }
        Iterator it1 = c.iterator1();
        while (it1.hasNext()) {
            System.out.println(((String) it1.next()).toUpperCase());
        }
        Iterator it2 = c.iterator2();
        while (it2.hasNext()) {
            System.out.println(((String) it2.next()).toUpperCase());
        }
    }
}</span></span>

迭代器模式:

   1 ) 它支持以不同的方式遍历一个集合,复杂的集合可用多种方式进行遍历。例如, 代码生成和语义检查要遍历语法分析树。代码生成可以按中序或者按前序来遍历语法分析树。迭代器模式使得改变遍历算法变得很容易: 仅需用一个不同的迭代器的实例代替原先的实例即可。你也可以自己定义迭代器的子类以支持新的遍历。

   2) 迭代器简化了集合的接口。有了迭代器的遍历接口,集合本身就不再需要类似的遍历接口了。这样就简化了集合的接口。

3) 在同一个集合上可以有多个遍历。每个迭代器保持它自己的遍历状态。因此你可以同时进行多个遍历。

若在一个集合上反复的过程中,有其他代码介入,并在那个集合中插入、删除或修改一个对象,便会面临发生冲突的危险。针对这个问题,集合库有一套解决的机制,能查出在一个集合上反复时,如果有其他代码修改了集合,便会立即产生一个ConcurrentModificationException(并发修改违例)。


<span style="font-family:SimSun;font-size:14px;"><span style="font-family:SimSun;font-size:14px;">public class ConcurrentModify {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        c.add("1");
        c.add("2");
        c.add("3");
        Iterator it = c.iterator();
        it.next();
        c.add("4");
        it.next(); // throws ConcurrentModificationException
    }
}</span></span>

四、集合的类型

Java集合框架图




一个平台(framework)有一组类构成,它包含一些提供基础功能和机制的类。用户可继承这些类创建自己的子类,扩充它的功能以满足自己的要求。

Java Colletion FrameWork为数据结构类构建了一个平台。它定义了实现数据结构的若干接口和抽象类,同时它也提供了一些常用的数据结构类。对这个平台不需了解太多,就可以很好使用这些常用数据结构类。但如果想实现另外的数据结构的类算法,或要添加一个新的数据结构,那么理解这个平台的结构是很有用的。

这个平台定义了两个基本接口:Colletion和Map。

 

下面的继承于Colletion和Map 的5个抽象类提供了许多例行方法:

  • AbstractCollection
  • AbstractList
  • AbstractSequentialList
  • AbstractSet
  • AbstractMap

当实现自己的数据结构类时,可以继承上面的一个类以利用其提供的例行方法。

Java库提供了6种常用的数据结构类:

  • LinkedList
  • ArrayList
  • HashSet
  • TreeSet
  • HashMap
  • TreeMap

接口的分类:

(1) Collection:包含一组对象的容器。

List:继承于Collection,按特定的顺序容纳对象的容器。 

Set: 继承于Collection,包含不重复对象的容器。
(2)  Map: 包含一系列“键-值”对对象的容器。类似于一个两列的表格,第一列是关键字,给出关

键字就可以查找出所对应的第二列的值。另一角度,可以方便地查看Map的某个部分,Map就可以

返回所有键的一个Set、一个包含所有值的List或者包含所有“键-值”对的一个List。

(3) Iterator:利用iterator()方法,所有Collection都能生成一个“反复器”(Iterator)。

使用Collection:

<span style="font-family:SimSun;font-size:14px;">boolean add(Object)
boolean addAll(Collection)
void clear()
boolean contains(Object) 
boolean containsAll(Collection) 
boolean isEmpty()
Iterator iterator()
boolean remove(Object) 
boolean removeAll(Collection)
 boolean retainAll(Collection) 
int size()
Object[] toArray() </span>

boolean add(Object) 保证集合内包含了自变量。如果它没有添加自变量,就返回false(假)
boolean addAll(Collection) 添加自变量内的所有元素。如果没有添加元素,则返回true(真)
void clear() 删除集合内的所有元素
boolean contains(Object) 若集合包含自变量,就返回“真”
boolean containsAll(Collection) 若集合包含了自变量内的所有元素,就返回“真”
boolean isEmpty() 若集合内没有元素,就返回“真”
Iterator iterator() 返回一个反复器,以用它遍历集合的各元素
boolean remove(Object) 如自变量在集合里,就删除那个元素的一个实例。如果已进行了删除,就返回“真”
boolean removeAll(Collection) 删除自变量里的所有元素。如果已进行了任何删除,就返回“真”
boolean retainAll(Collection) 只保留包含在一个自变量里的元素(一个理论的“交集”)。如果已进行了任何改变,就返回“真”
int size() 返回集合内的元素数量
Object[] toArray() 返回包含了集合内所有元素的一个数组

<span style="font-family:SimSun;font-size:14px;">public class CollectionTest {
    public static void main(String[] args) {
        Collection c1 = new ArrayList();
        c1.add("1");
        c1.add("2");
        c1.add("3");

        Collection c2 = new ArrayList();
        c2.add("a");
        c2.add("b");

        System.out.println(c1.contains("1"));
        System.out.println(c1.containsAll(c2));

        c1.addAll(c2);
        System.out.println(c1.containsAll(c2));

        System.out.println(c1.equals(c2));
        c2.addAll(c1);

        System.out.println(c1.equals(c2)); // count and order must equal
        if (c1.containsAll(c2) && c2.containsAll(c1))
            System.out.println(true);

        Iterator it = c1.iterator();
        System.out.println(it.next());

/*
        c2.remove("a");
        c2.remove("b");
//        c2.remove("a");
//        c2.remove("b");
        c1.removeAll(c2);
        System.out.println(c1);
*/

        c2.remove("a");
        c2.remove("b");
//        c2.remove("a");
//        c2.remove("b");
        c1.retainAll(c2);
        System.out.println(c1);

        Object[] o1 = c1.toArray();
        for (int i = 0; i < o1.length; i++)
            System.out.print(o1[i] + " ");

        Object[] o2 = new Object[3];
//        Object[] o2 = new Object[5];
        o2[0] = "m";
        o2[1] = "n";
        o2[2] = "l";

        Object[] o3 = c1.toArray(o2);
        System.out.println();
        for (int i = 0; i < o3.length; i++)
            System.out.print(o3[i] + " ");

        System.out.println();
        for (int i = 0; i < o2.length; i++)
            System.out.print(o2[i] + " ");

    }
}</span>

使用List


<span style="font-family:SimSun;font-size:14px;">   void add(int index,Object element)
   Object get(int index)
   void remove(int index)
   ArrayList,LinkedList以及Vector都实现了List接口</span>

列表是一种有序的数据结构(ordered collection)。在列表中,每个元素都被放于一个合适的位置。List接口定义了一些随机访问方法:

void add(int index,Object element)

Object get(int index)

void remove(int index)

 

List(接口)最重要的特性是可保证元素按照规定的顺序排列。

List为Collection添加了大量方法,以便我们在List中部插入和删除元素。

List也会生成一个ListIterator(列表反复器),利用它可在一个列表里朝两个方向遍历,同时插入和删除位于列表中部的元素。ListIterator接口定义了add()方法,它把一个元素插入指针指示的位置前面。

ArrayList作为一个常规用途的对象容器使用。允许我们快速访问元素,但在从列表中部插入和删除元素时,速度却嫌稍慢。一般只应该用ListIterator对一个ArrayList进行向前和向后遍历,不要用它删除和插入元素。

LinkedList 提供优化的顺序访问性能,同时可以高效率地在列表中部进行插入和删除操作。但在进行随机访问时,速度却相当慢,此时应换用ArrayList。

LinkedList也提供了未在任何接口或基础类中定义的方法addFirst(),addLast(),getFirst(),getLast(),removeFirst()以及removeLast(),以便将其作为一个队列或一个双向队列使用

<span style="font-family:SimSun;font-size:14px;">public class ListTest {
    public static void main(String[] args) {
        List l1 = new ArrayList();
        l1.add("1");
        l1.add("2");
        l1.add("3");
//        l1.add(4, "4"); // throws IndexOutOfBoundsException
        l1.add(3, "4");

        l1.set(1, "22");

        System.out.println(l1);
        System.out.println(l1.get(3));

        l1.remove(3);
        System.out.println(l1);

        ListIterator lit = l1.listIterator();
        while (lit.hasNext()){
            System.out.println(lit.next());
        }
        while (lit.hasPrevious()){
            System.out.println(lit.previous());
        }

        System.out.println(lit.nextIndex());

        lit.add("4");
        System.out.println(l1);

        System.out.println(lit.previous());
        lit.set("9");
        System.out.println(lit.next());

        LinkedList l2 = new LinkedList();
        l2.add("1");
        l2.add("2");
        l2.add("3");

        System.out.println(l2.getFirst());
        System.out.println(l2.getLast());
        l2.addFirst("0");
        l2.addLast("4");
        System.out.println(l2.getFirst());
        System.out.println(l2.getLast());

    }
}
</span>

使用Set

Set接口等同于Collection接口,只是它不会把集合已有元素再次插入集合。包含的对象必须定义equals(),从而建立对象的唯一性。

Set 接口的两个实现:

HashSet:用于比较大的Set。包含的对象必须定义hashCode()
TreeSet:从中按某种顺序提取对象的Set。包含的对象必须实现了Comparable接口

      或在构建TreeSet时传入Comparator接口类型的对象参数。

Set(接口) 添加到Set的每个元素都必须是独一无二的;Set就不会添加重复的元素。添加到Set里的对象必须定义equals(),从而建立对象的唯一性。

一个Set不能保证自己可按任何特定的顺序维持自己的元素

它的equals()方法是这样定义的:两个集合A和B相等表示一个元素在集合A中也一定在集合B,且A和B的元素个数相同,但元素的顺序无关紧要。

它的hashCode()方法应这样定义:相同的元素产生相同的散列码。要把类置入一个HashSet的前提是必须重写hashCode()


HashSet 用于比较大的Set。对象也必须定义hashCode()。
TreeSet 由一个“红黑树”后推得到的顺序Set。我们可以从一个Set里提到一个顺序集合。

<span style="font-family:SimSun;font-size:14px;">public class SetTest {
    public static void main(String[] args) {
        Set s1 = new HashSet();
        s1.add("111");
        s1.add("222");
        s1.add("333");

        System.out.println(s1);
        s1.add("444");
        s1.add("333");
        System.out.println(s1);

        Set s2 = new TreeSet();
        s2.addAll(s1);
        System.out.println(s2);

        Set s3 = new HashSet();
        s3.add(new SetItem(1));
        s3.add(new SetItem(2));
        s3.add(new SetItem(3));
        System.out.println(s3.contains(new SetItem(1)));
    }
}

class SetItem{
    int id;

    public SetItem(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public boolean equals(Object obj) {
        return id == ((SetItem)obj).getId();
    }

 /*   public int hashCode() {
        return id;
    }*/
}</span>

使用Map

Map包含一系列“键-值”对对象的容器。可以通过键查找相应的值。

Map接口的两个实现:

HashMap 基于一个散列表实现。具有最稳定的性能。
TreeMap按固定的顺序(取决于Comparable或Comparator)查看键或者“键-值”对。

HashMap每个对象都有“相对唯一”的整数:散列码,散列码由每个对象的hashCode()(根类Object的一个方法)计算得到。“键-值”对对象被加入HashMap时,先散列关键字,然后通过散列码组织存储形式。当进行检索时可利用散列码,不必用冗长的线性搜索技术,来查找一个键,使检索性能得到大幅度提升。这种形式具有最稳定的性能。

TreeMap通过用排序方法对关键字排序把对象组织到一个查找树上。在查看键或者“键-值”对时,它们会按固定的顺序排列(取决于Comparable或Comparator)。最大的好处就是我们得到的是已排好序的结果。

关键字必须是唯一的。映像中不会储存这样的两个不同对象:它们的关键字相同。

调用put()方法储存两个值时,如果两次调用的关键字相同,那么第二个值覆盖第一个值。并返回先前储存的其关键字和key相等的值。如果put(key,value)返回一个非null值,就表明它覆盖了一个以前储存的对象。

remove()方法删除映像的一个元素。

size()方法返回映像的目前长度。

 

我们可以得到Map的三种实现Collection接口或它的子接口的对象:

Set keySet()关键字集合

Collection values()值的全体

Set entrySet()关键字/值对集合

<span style="font-family:SimSun;font-size:14px;">   public class MapTest {
       public static void main(String[] args) {
           Map m1 = new HashMap();
           m1.put("1", "aaa");
           m1.put("2", "bbb");
           m1.put("3", "ccc");
   
           System.out.println(m1.put("3", "ccc"));
           System.out.println(m1.put("4", "ddd"));
   
           System.out.println(m1.containsKey("1"));
           System.out.println(m1.containsValue("ddd"));
   
           m1.put("0", "z");
           System.out.println(m1.get("0"));
           System.out.println(m1.size());
           m1.remove("0");
           System.out.println(m1.get("0"));
           System.out.println(m1.size());
   
           Map m2 = new TreeMap();
           m2.put("1", "aaa");
           m2.put("2", "bbb");
           m2.put("3", "ccc");
           m2.put("4", "ddd");
   
           System.out.println(m1.equals(m2));
   
           Set s1 = m1.entrySet();
           Set s2 = m1.keySet();
           Collection c = m1.values();
   
           Map m3 = new TreeMap();
           m3.putAll(m1);
           m3.putAll(m2);
           System.out.println(m3);
   
       }
   }
   
   可以用下列源代码枚举出一个映像的所有关键字:
   Set keys = map.keySet();
   Iterator iter = keys.iterator();
   while (iter.hasNext())
   {  Object key = iter.next();
      do something with key
   }
   
 可以同时查看关键字和值:
 Set entries = staff.entrySet();
 Iterator iter = entries.iterator();
 while (iter.hasNext())
 {  Map.Entry entry = (Map.Entry)iter.next();
    Object key = entry.getKey();
    Object value = entry.getValue();
    do something with key,value
 }</span>





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值