1.容器API的类图结构如下:
JAVA的集合类是一种特别有用的工具类,它可以用于存储数量不等的多个对象,并可以实现常用数据结构,如栈,队列等,除此之外,JAVA集合还可用于保存具有映射关系的关联数组。
JAVA的集合大致上可分为:Set,List和Map三种体系,其中Set代表无序,不可重复的集合;List代表有序,重复的集合,而Map则代表具有遇敌关系的集合。Queue体系集合,代表一种队列集合实现。
JAVA集合概述:
JAVA提供集合类主要负责保存盛装其他数据,因此集合类也被称为容器类。所有集合类都位于java.util包下。
JAVA集合类主要由两个接口派生而出:Collection和Map,也是根接口。
Map保存的每项数据都是key-value对,也就是由key和value两个值组成。
下面给个比喻,再给个图就非常容易理解了。
JAVA的所有集合分成三大类,其中Set 集合类似于一个罐子,把一个对象添加到Set集合时,Set集合无法记住添加这个元素的顺序,所以Set里的元素不能重复(否则系统无法准确识别这个元素)
List集合非常像一个数组,它可以记住每次添加元素的顺序,只是List长度可变。
Map集合也像一个罐子,只是它里面的每项数据都由两个值组成。
如果访问List集合中的元素,可以直接根据元素的索引来访问,如果需要访问Map集合中的元素,可以根据每项元素的key来访问其value,如果希望访问Set集合中的元素,则只能根据元素本身来访问(这也是Set集合里元素不允许重复的原因)
对于Set,List和Map三种集合,最常用的实现类,分别是HashSet,ArrayList ,,HashMap 三个实现类。
1.1 Collection接口:
Collection接口——定义了存取一组对象的方法,其子接口Set和List分别定义了存储方式。
(1)Set中的数据对象没有顺序且不可以重复。
(2)List中的数据对象有顺序且可以重复。
Collection接口所定义的方法:
方法 |
描述 |
Boolean add(Object o) |
向集合中添加一个对象的引用 |
Void clear() |
删除集合中的所有对象,即不再持有这些对象的引用 |
Boolean contains(Object o) |
判断在集合中是否持有特定对象的引用 |
Boolean isEmpty() |
判断集合是否为空 |
Iterator iterator() |
返回一个Iterator对象,可用它来遍历集合中的元素 |
Boolean remove(Object o) |
从集合中删除一个对象的引用 |
Boolean retainAll(Collection c) |
保留集合中的指定内容 |
Int size() |
返回集合中元素的数目 |
Object[] toArray() |
返回一个数组,该数组包含集合中的所有元素 |
Boolean equals(Object o) |
对象比较 |
Int hashCode() |
返回hash码 |
注意:相等的对象应该具有相等的 hash codes。容器类对象在调用remove,contains等方法时需要比较对象是否相等,这回涉及到对象类型的equals方法和hasCode方法;对于自定义的类型,需要重写equals和hashCode方法以实现自定义的对象相等规则。
Set接口和list接口都继承了Collection接口,而map接口没有继承Collection接口,因此可以对set对象和list对象调用以上方法,但是不能对Map对象调用以上方法。
Collection接口的iterator()和toArray()方法都用于获取集合中的所有元素,前者返回一个Iterator对象,后者放回一个包含集合中所有元素的数组。
增加Name类的equals和hashCode方法如下:
1 public boolean equals(Object obj){ 2 if(obj instanceof Name){ 3 Name name=(Name)obj; return(firstName.equals(name.firstName)&&lastName.equals(name.lastName));
4 } 5 return super.equals(obj); 6 } 7 public int hashCode(){ 8 return firstName.hashCode(); 9 }
方法应用例子:
1 import java.util.ArrayList; 2 import java.util.Collection; 3 import java.util.HashSet; 4 public class TestCollection{ 5 public static void main(String[] args){ 6 Collection c= new ArrayList(); 7 c.add("孙悟空"); 8 c.add(6); 9 System.out.println("c集合的元素的个数为:"+c.size()); 10 System.out.println("c集合里面是否包含孙悟空字符"+" "+c.contains("孙悟空")); 11 c.add("世界你好"); 12 System.out.println("c集合里面的元素:"+c); 13 Collection books=new HashSet(); //HashSet不允许元素重复。 14 books.add("世界你好"); 15 books.add("你好世界"); 16 System.out.println("c集合里面是否完全包含books:"+c.contains(books)); 17 c.removeAll(books); 18 System.out.println("c集合中的元素:"+c); 19 c.clear(); 20 System.out.println("c集合里面的元素:"+c); 21 books.retainAll(c); 22 System.out.println("books集合的元素:"+books); 23 24 } 25 }
输出结果:
c集合的元素的个数为:2
c集合里面是否包含孙悟空字符 true
c集合里面的元素:[孙悟空, 6, 世界你好]
c集合里面是否完全包含books:false
c集合中的元素:[孙悟空, 6]
c集合里面的元素:[]
books集合的元素:[]
1.2 Iterator接口:
使用Iterator接口遍历集合元素,Iterator接口是JAVA集合框架的成员,也被称为迭代器。
Iterator接口中声明了如下方法:
boolean hashNext():如果迭代的集合元素还没被遍历,则返回true
Object next():返回集合里下一个元素。
Void remove():删除集合里上一次next方法返回的元素。
1 import java.util.Collection; 2 import java.util.HashSet; 3 import java.util.Iterator; 4 public class TextIterator { 5 public static void main(String[] args){ 6 Collection books=new HashSet(); //无序序列,元素不可重复 7 books.add("世界你好"); 8 books.add("你好世界"); 9 books.add("你好Java"); 10 System.out.println("现在结合的元素:"+books); 11 Iterator it=books.iterator(); //获取books集合对应的迭代器。 12 while(it.hasNext()){ 13 String book=(String)it.next(); //it.next()方法返回的数据类型是object类型,需要强制类型转换。 14 System.out.println(book); 15 if(book.equals("你好世界")){ 16 it.remove(); //从集合中删除上一次next方法返回的元素。 17 } 18 book="测试字符串"; //对book变量赋值,不会改变集合元素本身。 19 } 20 System.out.println(books); 21 22 23 }
输出结果:
现在结合的元素:[世界你好, 你好Java, 你好世界]
世界你好
你好Java
你好世界
[世界你好, 你好Java]
Iterator仅用于遍历集合,Iterator本身并不提供盛装对象的能力,如果需要创建Iterator对象,则必须有一个被迭代的集合。
Iterator必须依附于Collection对象,有一个Iterator对象,则必然有一个怀之关联的Collection对象。Iterator提供了2个方法来迭代访问Collection集合里的元素。
结论:当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何改变。
1.3使用foreach循环遍历集合元素
我们知道,除了可以使用Iterator类迭代访问Collection集合里的元素之外,现在还可以使用foreach循环来迭代访问集合元素会更加便捷。
1 import java.util.Collection; 2 import java.util.HashSet; 3 4 public class TextForeach { 5 public static void main(String[] args){ 6 Collection books=new HashSet(); 7 books.add("世界您好"); 8 books.add("您好世界"); 9 books.add("您好JAVA"); 10 for(Object obj:books){ 11 String book=(String)obj; //此处的book变量也不是集合元素本身 12 System.out.println(book); 13 if(book.equals("您好世界")) { 14 books.remove(book); 15 } 16 17 } 18 19 System.out.println(books); 20 21 }
输出结果:
现在结合的元素:[世界你好, 你好Java, 你好世界]
世界你好
你好Java
你好世界
[世界你好, 你好Java]
1.4 Set集合的通用知识:
1.4.1HashSet类:
HashSet是Set接口的典型实现,大多数时候使用Set集合时就是使用这个实现类。HashSet按Hash算法一存储集合中的元素,因此具有很好的存取和查找性能。
特点:
》不能保证元素的排列顺序,顺序有可能发生变化。
》HashSet来是同步的,如果多个线程同时访问一个Set集合,如果多个线程同时访问一个HashSet,如果有2条或者2条以上线程同时修改了HashSet集合,必须通过代码来保证其同步。
》集合元素值可以是Null
我们可以简单的认为:HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值也相等。
1 import java.util.*; 2 class A{ //类A的equals方法总是返回true,但没有重写其hashCode()方法 3 public boolean equals(Object obj){ 4 return true; 5 } 6 } 7 class B{ //类B的hashCode()方法总是返回l,但没有重写equals()方法 8 public int hashCode(){ 9 return 1; 10 } 11 } 12 class C{ //类C的hashCode()方法总是返回2,重写其equals()方法 13 public int hashCode(){ 14 return 2; 15 } 16 public boolean equals(Object obj){ 17 return true; 18 } 19 } 20 public class TextHashset{ 21 public static void main(String[] args){ 22 HashSet books = new HashSet(); 23 books.add(new A()); 24 books.add(new A()); 25 books.add(new B()); 26 books.add(new B()); 27 books.add(new C()); 28 books.add(new C()); 29 30 } 31 }
我们从编译结果可以看出,即使2个A对象通过equals比较返回true,但HashSet依然把它们当成2个,即使2个B对象的hashCode()返回相同值(都是1)但,HashSet依然把它们当成2个对象。
简单的提醒点:
如果需要某个类的对象保存到HashSet集合中,重写这个类的equals()方法和hashCode()方法时,应该尽量保证两个对象通过equals比较返回true时,它们的hashCode方法返回值也相等。
HashSet采用每个元素的hashCode作为其索引,从而可以自由增加HashSet的长度,并可以根据元素的hashCode值来访问该元素。
(我们可以理解为相当于数组里的下标索引)。
因为hashCode()方法很重要,看看重写hashCode()方法的基本规则。
》当两个对象通过equals方法比较返回true时,这个两个对象的hashCode应该相等。
》对象中用作equals比较标准的属性,都应该用来计算hashCode值。
1 import java.util.Iterator; 2 3 class R{ 4 int count; 5 public R(int count){ 6 this.count=count; 7 } 8 public String toString(){ 9 return "R[count"+count+"]"; 10 } 11 public boolean equals(Object obj){ 12 if(obj instanceof R){ 13 R f=(R)obj; 14 if(f.count==this.count){ 15 return true; 16 } 17 } 18 return false; 19 } 20 public int hashCode(){ 21 return this.count; 22 } 23 } 24 public class TestHashSet2{ 25 public static void main(String[] args){ 26 HashSet hs=new HashSet(); 27 hs.add(new R(5)); 28 hs.add(new R(-3)); 29 hs.add(new R(9)); 30 hs.add(new R(-2)); 31 System.out.println("第1个hs,集合中的元素:"+hs+"\n"); //打印HashSet集合,集合元素没有重复 32 Iterator it=hs.iterator(); 33 R first = (R)it.next(); 34 first.count = -3; //为第一个元素的count实例变量赋值 35 System.out.println("第2个hs,集合中的元素:"+hs+"\n"); 36 hs.remove(new R(-3)); //删除count为-3的R对象 37 System.out.println("第3个hs,集合中的元素:"+hs+"\n"); 38 System.out.println("hs是否包含count为-3的R对象?"+hs.contains(new R(-3))); //输出false 39 System.out.println("hs是否包含count为5的R对象"+hs.contains(new R(5))); //输出false 40 } 41 }
输出结果:
第1个hs,集合中的元素:[R[count5], R[count9], R[count-3], R[count-2]]
第2个hs,集合中的元素:[R[count-3], R[count9], R[count-3], R[count-2]]
第3个hs,集合中的元素:[R[count-3], R[count9], R[count-2]]
hs是否包含count为-3的R对象?false
hs是否包含count为5的R对象false
ps:
(1)object类中的hashcode()方法比较的是对象的地址(引用地址),使用new方法创建对象,两次生成的当然是不同的对象,造成的结果就是两个对象的hashcode()返回的值不一样。所以hashset会把new方法创建的两个它们当作不同的对象对待,
(2)在java的集合中,判断两个对象是否相等的规则是:
a),判断两个对象的hashCode是否相等
如果不相等,认为两个对象也不相等,完毕
如果相等,转入2)
(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。)
b),判断两个对象用equals运算是否相等
如果不相等,认为两个对象也不相等
如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)
LinkedHashSet类:
HashSet还有一个子类LinkedHashSet,它也是根据元素hashCode值来决定元素存储位置,它是需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。
1 import java.util.LinkedHashSet; 2 public class LinkedHashSetTest{ 3 public static void main(String[] args){ 4 LinkedHashSet books = new LinkedHashSet(); 5 books.add("世界您好"); 6 books.add("您好世界"); 7 System.out.println(books); 8 books.remove("世界您好"); //删除世界您好 9 books.add("世界您好"); //重新添加世界您好 10 System.out.println(books); 11 } 12 }
输出结果:
[世界您好, 您好世界]
[您好世界, 世界您好]
编译结果相信大家都理解,因为元素的顺序正好与添加顺序一致。