JAVA容器

容器位于java.util包内

 

Java容器类库的用途是保存对象,根据数据结构不同将其划分为两个不同的概念


(1)   Collection,一个独立元素的序列,其中List按照元素的插入顺序保存元素,而set不能有重复元素,Queue(队列)按照先进先出(FIFO)的方式来管理数据,Stack(栈)按照后进先出(LIFO)的顺序管理数据。

(2)   Map,一组键值对(key-value)对象的序列,可以使用key来查找value,其中key是不可以重复的,value可以重复。我们可以称其为字典或者关联数组。其中HashMap是无序的,TreeMap是有序的,WeakHashMap是弱类型的,Hashtable是线程安全的。


容器API的类图结构如图:


               JAVA容器_第1张图片


Collection接口


--定义了存取一组对象的方法,其子接口SetList分别定义了存储方式。

 

示例:

[java] view plaincopyprint?
  1. import java.util.*;  
  2. public class TestCollection{  
  3.     public static void main(String[] args){  
  4.         Collection c=new ArrayList();  
  5.         //可以放入不同类型的对象  
  6.         c.add("hello");  
  7.         c.add(new Name("f1","l1"));  
  8.         c.add(new Integer(100));  
  9.         System.out.println(c.size());  
  10.         System.out.println(c);  
  11.         }  
  12. }  
  13. class Name{  
  14.     private String firstName,lastName;  
  15.     public Name(String firstName,String lastName){  
  16.         this.firstName=firstName;  
  17.         this.lastName=lastName;  
  18.         }  
  19.     public String getFirstName(){  
  20.         return firstName;  
  21.         }  
  22.     public String getLastName(){  
  23.         return lastName;  
  24.           
  25.     }  
  26.     public String toString(){  
  27.         return firstName+" "+lastName;  
  28.         }  
  29. }  



Set接口:


Set接口是Collection的子接口,Set接口没有提供额外的方法,但实现Set接口的容器类中的元素是没有顺序的,而且不可以重复。

[java] view plaincopyprint?
  1. import java.util.*;  
  2. public class TestSet{  
  3.     public static void main(String[] args){  
  4.         Set s1=new HashSet();  
  5.         Set s2=new HashSet();  
  6.         s1.add("a");  
  7.         s1.add("b");  
  8.         s1.add("c");  
  9.         s2.add("d");  
  10.         s2.add("a");  
  11.         s2.add("b");  
  12.         Set sn=new HashSet(s1);  
  13.         //sn中与s2相同的  
  14.         sn.retainAll(s2);  
  15.         Set su=new HashSet(s1);  
  16.         //所有的  
  17.         su.addAll(s2);  
  18.         System.out.println(sn);  
  19.         System.out.println(su);  
  20.         }  
  21. }  



List接口:


List接口是Collection的子接口,实现List接口的容器类中的元素是有顺序的,而且可以重复。List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。

 

次序是List最重要的特点;它确保维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(只推荐 LinkedList使用)。


[java] view plaincopyprint?
  1. import java.util.*;  
  2. public class TestList{  
  3.     public static void main(String[] args){  
  4.         List l1=new LinkedList();  
  5.         for(int i=0;i<=5;i++){  
  6.             l1.add("a"+i);  
  7.             }  
  8.         System.out.println(l1);  
  9.         l1.add(3,"a100");  
  10.         System.out.println(l1);  
  11.         l1.set(6,"a200");  
  12.         System.out.println(l1);  
  13.         System.out.print((String)l1.get(2)+" ");  
  14.         System.out.println(l1.indexOf("a3"));  
  15.         l1.remove(1);  
  16.         System.out.println(l1);  
  17.     }  
  18. }  

JAVA容器_第2张图片


Map接口


定义了存储“键(key-值(value)映射对”的方法

[java] view plaincopyprint?
  1. import java.util.*;  
  2. public class TestMap{  
  3.     public static void main(String args[]){  
  4.         Map m1=new HashMap();  
  5.         Map m2=new TreeMap();  
  6.         m1.put("one",new Integer(1));  
  7.         m1.put("two",new Integer(2));  
  8.         m1.put("three",new Integer(3));  
  9.         m2.put("A",new Integer(1));  
  10.         m2.put("B",new Integer(2));  
  11.           
  12.         System.out.println(m1.size());  
  13.         System.out.println(m1.containsKey("one"));  
  14.         System.out.println(m2.containsValue(new Integer(1)));  
  15.           
  16.         if(m1.containsKey("two")){  
  17.             int i=((Integer)m1.get("two")).intValue();  
  18.             System.out.println(i);  
  19.               
  20.             }  
  21.             Map m3=new HashMap(m1);  
  22.             m3.putAll(m2);  
  23.             System.out.println(m3);  
  24.         }  
  25.   
  26. }  


JAVA容器_第3张图片


Iterator接口:


所有实现了Collection接口的容器类都有一个iterator方法用以返回一个实现了Iterator接口的对象。

Iterator对象称作迭代器,用以方便的实现对容器内元素的遍历操作。


[java] view plaincopyprint?
  1. import java.util.*;  
  2. public class TestIterator{  
  3.     public static void main(String[] args){  
  4.         Collection c=new HashSet();  
  5.         c.add(new Name("f1","l1"));  
  6.         c.add(new Name("f2","l2"));  
  7.         c.add(new Name("f3","l3"));  
  8.         Iterator i=c.iterator();  
  9.         while (i.hasNext()){  
  10.             //next()的返回值为Object类型,需要转换为相应类型  
  11.             Name n=(Name)i.next();  
  12.             System.out.println(n.getFirstName()+" ");   
  13.             }  
  14.         }  
  15. }  
  16. class Name{  
  17.     private String firstName,lastName;  
  18.     public Name(String firstName,String lastName){  
  19.         this.firstName=firstName;  
  20.         this.lastName=lastName;  
  21.         }  
  22.     public String getFirstName(){  
  23.         return firstName;  
  24.         }  
  25.     public String getLastName(){  
  26.         return lastName;  
  27.           
  28.     }  
  29.     public String toString(){  
  30.         return firstName+" "+lastName;  
  31.         }  
  32. }  




数组和容器类的区别:


效率、类型限定和对于基本类型的处理。


1,效率肯定是内建的数组效率更高一些。数组是一种高效的存储和随机访问对象引用序列的方式,使用数组可以快速的访问数组中的元素。但是当创建一个数组对象 ( 注意和对象数组的区别 ) 后,数组的大小也就固定了,当数组空间不足的时候就再创建一个新的数组,把旧的数组中所有的引用复制到新的数组中。,

2,在泛型出来之前,容器类都是存取Object,而数组规定了确定类型。

3,在自动封包解包前,容器类不支持基本类型,而数组支持。




泛型:

    

    起因:类型不明确

    装入集合的类型都被当做Object对待,从而失去自己的实际类型

    从集合中取出时往往需要转型,效率底,容易产生错误

 

解决办法:

    在定义集合的时候同时定义集合中对象的类型

         1,可以在定义Collection的时候指定

         2,也可以在循环时用Iterator指定

    好处:增强程序的可读性和稳定性


[java] view plaincopyprint?
  1. import java.util.*;  
  2.   
  3. public class BasicGeneric {  
  4.     public static void main(String[] args) {  
  5.         List<String> c = new ArrayList<String>();  
  6.         c.add("aaa");  
  7.         c.add("bbb");  
  8.         c.add("ccc");  
  9.         for(int i=0; i<c.size(); i++) {  
  10.             String s = c.get(i);  
  11.             System.out.println(s);  
  12.         }  
  13.           
  14.         Collection<String> c2 = new HashSet<String>();  
  15.         c2.add("aaa"); c2.add("bbb"); c2.add("ccc");  
  16.         for(Iterator<String> it = c2.iterator(); it.hasNext(); ) {  
  17.             String s = it.next();  
  18.             System.out.println(s);  
  19.         }  
  20.     }  
  21. }  

JAVA容器_第4张图片


总结:



    以上只是简单介绍了容器的概念,以及各个容器类的特点和使用范例,对于容器类和数组来说,一般情况下,考虑到效率与类型检查,应该尽可能考虑使用数组。如果要解决一般化的问题,数组可能会受到一些限制,这时可以使用Java提供的容器类。

   

Comparable接口:针对排序list

 

问题:上面的算法根据什么确定容器中对象的“大小”顺序?

所有可以“排序”的类都实现了java.lang.Comparable接口,Comparable接口中只有一个方法

Publicint compareTo(Object obj)

返回0:表示this==obj

返回正数:表示this>obj

返回负数:表示this<obj

实现了Comparable接口的类通过实现comparaTo方法从而确定该对象的排序方式


equals()与hashCode():保证不重复

 

由来:

 

    要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。  

    于是,Java采用了哈希表的原理。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。

    这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

 

规则:

 

      所以,Java对于eqauls方法和hashCode方法是这样规定的:


      1.如果两个对象相同,那么它们的hashCode值一定要相同;

      2.如果两个对象的hashCode相同,它们并不一定相同(这里说的对象相同指的是用eqauls方法比较)。如不按要求去做了,会发现相同的对象可以出现在Set集合中,同时,增加新元素的效率会大大下降。

      3.equals()相等的两个对象,hashcode()一定相等;equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。

       

    在object类中,hashcode()方法是本地方法,返回的是对象的地址值,而object类中的equals()方法比较的也是两个对象的地址值,如果equals()相等,说明两个对象地址值也相等,当然hashcode()也就相等了;

    在String类中,equals()返回的是两个对象内容的比较,当两个对象内容相等时Hashcode()方法根据String类的重写代码的分析,也可知道hashcode()返回结果也会相等。

    以此类推,可以知道Integer、Double等封装类中经过重写的equals()和hashcode()方法也同样适合于这个原则。当然没有经过重写的类,在继承了object类的equals()和hashcode()方法后,也会遵守这个原则。

 

总结:

 

    hashCode()方法被用来获取给定对象的唯一整数。这个整数被用来确定对象被存储在HashTable类似的结构中的位置。默认的,Object类的hashCode()方法返回这个对象存储的内存地址的编号。 hashCode()和equals()定义在Object类中,这个类是所有java类的基类,所以所有的java类都继承这两个方法。

    如果我们不重写这两个方法,将几乎不遇到任何问题,但是有的时候程序要求我们必须改变一些对象的默认实现。

 

举例说明:

[java] view plaincopyprint?
  1. public class TestString{  
  2.     public static void main(String[] args){  
  3.         String s1="Hello";  
  4.         String s2="World";  
  5.         String s3="Hello";  
  6.         System.out.println(s1 == s3);  
  7.           
  8.         s1=new String("hello");  
  9.         s2=new String("hello");  
  10.         System.out.println(s1 == s2);  
  11.         System.out.println(s1.equals(s2));  
  12.           
  13.           
  14.         }  
  15. }  

JAVA容器_第5张图片

以上的正确执行是因为我们已经默认重写了String类的equals()方法和hashCode()方法。但是如果是Object类呢?

[java] view plaincopyprint?
  1. import java.util.HashSet;  
  2. import java.util.Set;  
  3.   
  4. public class TestEquals1{  
  5.     public static void main(String[] args){  
  6.             Cat cl=new Cat(1,2,3);  
  7.             Cat c2=new Cat(1,2,3);  
  8.             System.out.println(cl==c2);  
  9.             System.out.println(cl.equals(c2));  
  10.               
  11.         }  
  12.     }  
  13.   
  14. class Cat{  
  15.           
  16.         int color,height,weight;  
  17.         public Cat(int color,int height,int weight){  
  18.               
  19.             this.color=color;  
  20.             this.height=height;  
  21.             this.weight=weight;  
  22.             }  
  23.           
  24. }  



毫无疑问,上面的程序将输出false,但是,事实上上面两个对象代表的是通过一个cat。真正的商业逻辑希望我们返回true。重写equals()方法!


[java] view plaincopyprint?
  1. public class TestEquals{  
  2.     public static void main(String[] args){  
  3.             Cat cl=new Cat(1,2,3);  
  4.             Cat c2=new Cat(1,2,3);  
  5.             System.out.println(cl==c2);  
  6.             System.out.println(cl.equals(c2));  
  7.         }  
  8.     }  
  9.   
  10. class Cat{  
  11.           
  12.         int color,height,weight;  
  13.         public Cat(int color,int height,int weight){  
  14.               
  15.             this.color=color;  
  16.             this.height=height;  
  17.             this.weight=weight;  
  18.             }  
  19.         public boolean equals(Object obj){  
  20.             if(obj==null)return false;  
  21.             else{  
  22.                 if(obj instanceof Cat){  
  23.                     Cat c=(Cat)obj;  
  24.                     if(c.color==this.color && c.height==this.height && c.weight==this.weight){  
  25.                         return true;  
  26.                         }  
  27.                     }  
  28.                 }  
  29.                 return false;  
  30.             }  
  31.     }  


JAVA容器_第6张图片


So are we done?没有,让我们换一种测试方法来看看。


上面的程序输出的结果是两个。如果两个cat对象equals返回true,Set中应该只存储一个对象才对,而且System.out.println(cat.contains(newCat(1,2,3)));判断是否存在时,结果输出为false,如图:


JAVA容器_第7张图片


那么问题在哪里呢?



我们忘掉了第二个重要的方法hashCode()。就像JDK的Javadoc中所说的一样,如果重写equals()方法必须要重写hashCode()方法。我们加上下面这个方法,程序将执行正确。


[java] view plaincopyprint?
  1. public int hashCode()  
  2.      {  
  3.       final int PRIME =31;  
  4.       int result = 1;  
  5.       result = PRIME * result ;  
  6.       return result;  
  7. }  





再总结:


    以上这些都是针对容器来说的(如何判断set中不重复,如何判断list中的顺序),当然最主要的是解释为什么重写equals()方法必须要重写hashCode()方法的问题。

    根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode方法,它们仅仅是两个对象。因此,违反了“相等的对象必须具有相等的散列码”。

    所以只要重写了equals(),一定要重写hashCode,否则Hash表都会失效,工作不正常。即便你用equals方法比较得到两个对象是相等的结论那你也得不到相同的哈希码

 

    即如果cat类只重写了equals(),hashcode没有被重写,加入元素时使用的hashcode()是继承于set<-collection<-object的,所以计算的hashcode值不同,存储位置不同,则认为元素不相同,也就能明白为什么上面的判断是否存在(System.out.println(cat.contains(newCat(1,2,3))))时输入的结果为false了。

 


你可能感兴趣的:(JAVA容器)