【Java系列】巧取集合类--架构师必备技能

简介: 【Java系列】巧取集合类--架构师必备技能

5ae74150676e416ebfba0c2fe30a45e5.png

一、List结构的集合


主要分为:ArrayList类,LinkedList类,Vector类,Stack类
• 1


1.1 ArrayList类


无同步性,线程不安全


import java.util.*;//集合类基本上在util包中
public class Demo01 {
  public static void main(String[] args) {
    //定义ArrayList对象
    ArrayList al=new ArrayList();
    //显示大小
    System.out.println("al大小:"+al.size());
    //向all中加入数据(类型是Object)
    //创建一个职员
    Clerk clerk1=new Clerk("宋江",50,1000);
    Clerk clerk2=new Clerk("吴用",45,1200);
    Clerk clerk3=new Clerk("林冲",35,1300);
    //将clerk1加入到al中
    al.add(clerk1);
    al.add(clerk2);
    al.add(clerk3);
    //可不可以放入同样的对象?
    al.add(clerk1);
    //显示大小
    System.out.println("al大小:"+al.size());
    //如何访问al中的对象(数据)
    //访问第一个对象
    //Clerk temp=(Clerk)al.get(0);
    //System.out.println("第一个人的名字是:"+temp.getName());
    //遍历al所有的对象(数据)
    for(int i=0;i<al.size();i++){
      Clerk temp=(Clerk)al.get(i);
      System.out.println("名字:"+temp.getName());
    }
    //如何从al中删除一个对象
    al.remove(1);
    System.out.println("===删除吴用===");
    //遍历al所有的对象(数据)
    for(int i=0;i<al.size();i++){
      Clerk temp=(Clerk)al.get(i);
      System.out.println("名字:"+temp.getName());
    }
  }
}
//定义一个员工类
class Clerk{
  private String name;
  private int age;
  private float sal;
  public Clerk(String name,int age,float sal){
    this.name=name;
    this.age=age;
    this.sal=sal;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public int getAge() {
    return age;
  }
  public void setAge(int age) {
    this.age = age;
  }
  public float getSal() {
    return sal;
  }
  public void setSal(float sal) {
    this.sal = sal;
  }
}


1.2 LinkedList集合类


//LinkedList集合类的使用
import java.util.*;
public class Demo02 {
  public static void main(String[] args) {
    LinkedList ll=new LinkedList();
    Empp emp1=new Empp("sa01","aa",1.2f);
    Empp emp2=new Empp("sa02","bb",1.2f);
    Empp emp3=new Empp("sa03","cc",1.2f);
    //addFirst表示把emp1加载(链表)队列的最前面
    ll.addFirst(emp1);//addFirst方法是可以插入在数组之前
    ll.addFirst(emp2);//也可以理解为addFirst方法是后进先出的方法
    //addLast表示把emp3加载(链表)队列的后面
    ll.addLast(emp3);
    System.out.println("测试LinkedList集合类中的addFist及addLast方法");
    for(int i=0;i<ll.size();i++){
      System.out.println(((Empp)ll.get(i)).getName());
    }
    //remove表示将某一条数据进行删除
    ll.remove(emp1);//将ll中的emp1数据删除
    System.out.println("测试LinkedList集合类中的remove方法");
    for(int i=0;i<ll.size();i++){
      System.out.println(((Empp)ll.get(i)).getName());
    }
    ll.removeAll(ll);//清除整个链表
    System.out.println("测试LinkedList集合类中的remmoveall方法");
    for(int i=0;i<ll.size();i++){
      System.out.println(((Empp)ll.get(i)).getName());
    }
  }
}
//创建员工类
class Empp{//在同一个包中的类,可以同包中的其它class文件直接访问或调用
  //定义成员变量工号、姓名、薪水
  private String empNo;
  private String name;
  private float sal;
  //创建构造函数,初始化成员变量
  public Empp(String empNo,String name,float sal){
    this.empNo=empNo;
    this.name=name;
    this.sal=sal;
  }
  //使用set、get方法进行数据传递
  public String getEmpNo() {
    return empNo;
  }
  public void setEmpNo(String empNo) {
    this.empNo = empNo;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public float getSal() {
    return sal;
  }
  public void setSal(float sal) {
    this.sal = sal;
  }
}


1.3 Vector集合类


线程安全具有同步性


//Vector集合类(向量)的使用方法
import java.util.*;
public class Demo03 {
  public static void main(String[] args) {
    //Vector的用法
    Vector vv=new Vector();
    AEmp emp1=new AEmp("1","aa",1.2f);
    AEmp emp2=new AEmp("2","bb",1.2f);
    AEmp emp3=new AEmp("3","cc",1.2f);
    vv.add(emp1);
    vv.add(emp2);
    vv.add(emp3);
    //遍历
    for(int i=0;i<vv.size();i++){
      AEmp emp=(AEmp)vv.get(i);
      System.out.println(emp.getName());
    }
  }
}
//创建员工类
class AEmp{
  //定义成员变量工号、姓名、薪水
  private String empNo;
  private String name;
  private float sal;
  //创建构造函数,初始化成员变量
  public AEmp(String empNo,String name,float sal){
    this.empNo=empNo;
    this.name=name;
    this.sal=sal;
  }
  //使用set、get方法进行数据传递
  public String getEmpNo() {
    return empNo;
  }
  public void setEmpNo(String empNo) {
    this.empNo = empNo;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public float getSal() {
    return sal;
  }
  public void setSal(float sal) {
    this.sal = sal;
  }
}


1.4 Stack集合类(栈)


//Stack集合类(栈)的使用方法
package com.haiding.set;
import java.util.*;
public class Demo04 {
  public static void main(String[] args) {
    //Stack的用法
    Stack stack=new Stack();
    AEmp emp1=new AEmp("s1","aa",1.2f);
    AEmp emp2=new AEmp("s2","bb",1.2f);
    stack.add(emp1);
    stack.add(emp2);
    for(int i=0;i<stack.size();i++){
      System.out.println(((AEmp)stack.get(i)).getName());
    }
  }
}
//创建员工类
class AEmp{
  //定义成员变量工号、姓名、薪水
  private String empNo;
  private String name;
  private float sal;
  //创建构造函数,初始化成员变量
  public AEmp(String empNo,String name,float sal){
    this.empNo=empNo;
    this.name=name;
    this.sal=sal;
  }
  //使用set、get方法进行数据传递
  public String getEmpNo() {
    return empNo;
  }
  public void setEmpNo(String empNo) {
    this.empNo = empNo;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public float getSal() {
    return sal;
  }
  public void setSal(float sal) {
    this.sal = sal;
  }
}


总结


ArrayList和Vector的区别
    ArrayList与Vector都是java的集合类,都可以用来存放java对象,
    这是他们的相同点,但是他们也有区别:
1、同步性
Vector是线程同步的。这个类中的一些方法保证了Vector中的对象是线程安全的。
而ArrayList则是线程异步的,因此ArrayList中的对象并不是线程安全的。
因为同步的要求会影响执行的效率,
所以如果你不需要线程安全的集合那么使用ArrayList是一个很好的选择,
这样可以避免由于同步带来的不必要的性能开销。
2、数据增长
    从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。
    当你向这两种类型中增加元素的时候,
    如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,
    Vector缺省情况下自动增长原来一倍的数组长度,
    ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。
    所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,
    因为你可以通过设置集合的初始化大小来避免不必要的资源开销。


二、Map结构的集合类



分为:HashMap类,Hashtable类


2.1 HashMap类


import java.util.*;
public class Demo05 {
  public static void main(String[] args) {
    //创建HashMap对象
    HashMap hm=new HashMap();
    Emp emp1=new Emp("s001","aa",3.4f);
    Emp emp2=new Emp("s002","bb",5.6f);
    Emp emp3=new Emp("s003","cc",1.2f);
    //将emp放入到hm中
    //hm.put(null,null);//可以放空值
    hm.put("s001", emp1);
    hm.put("s002", emp2);
    hm.put("s002", emp3);//不允许key重复,所以emp3会覆盖emp2
    //如果你要查找编号是s002
    if(hm.containsKey("s002")){//取键值containsKey
      System.out.println("有该员工");
      //如何取出,键<key>值
      Emp emp=(Emp)hm.get("s002");
      System.out.println("名字"+emp.getName());
    }else{
      System.out.println("没该员工");
    }
    //遍历HashMap中所有的key和value值
    //Iterator迭代
    Iterator it=hm.keySet().iterator();
    //hasNext返回一个boolean值
    while(it.hasNext()){
      //如果有下一个取出key值
      String key=it.next().toString();
      //通过key取出value
      Emp emp=(Emp)hm.get(key);
      System.out.println("名字:"+emp.getName());
      System.out.println("工资:"+emp.getSal());
    }
  }
}
//创建员工类
class Emp{
  //定义成员变量工号、姓名、薪水
  private String empNo;
  private String name;
  private float sal;
  //创建构造函数,初始化成员变量
  public Emp(String empNo,String name,float sal){
    this.empNo=empNo;
    this.name=name;
    this.sal=sal;
  }
  //使用set、get方法进行数据传递
  public String getEmpNo() {
    return empNo;
  }
  public void setEmpNo(String empNo) {
    this.empNo = empNo;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public float getSal() {
    return sal;
  }
  public void setSal(float sal) {
    this.sal = sal;
  }
}


2.2 Hashtable集合类


Hashtable具有同步性,线程安全


import java.util.*;
public class Demo06{
  public static void main(String []args){
    Hashtable ht=new Hashtable();//Hashtable与HsahMap在用法上一致
    Emp emp4=new Emp("s101","a1",2.2f);
    Emp emp5=new Emp("s102","a2",1.2f);
    Emp emp6=new Emp("s103","a3",4.2f);
    ht.put("s101", emp4);
    ht.put("s102", emp5);
    ht.put("s103", emp6);
    //遍历
    for(Iterator it=ht.keySet().iterator();it.hasNext();){
      String key=it.next().toString();
      Emp emp=(Emp)ht.get(key);
      System.out.println("名字:"+emp.getName()+"\t工资:"+emp.getSal());
    }
  }
}
//创建员工类
class Emp{
  //定义成员变量工号、姓名、薪水
  private String empNo;
  private String name;
  private float sal;
  //创建构造函数,初始化成员变量
  public Emp(String empNo,String name,float sal){
    this.empNo=empNo;
    this.name=name;
    this.sal=sal;
  }
  //使用set、get方法进行数据传递
  public String getEmpNo() {
    return empNo;
  }
  public void setEmpNo(String empNo) {
    this.empNo = empNo;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public float getSal() {
    return sal;
  }
  public void setSal(float sal) {
    this.sal = sal;
  }
}


总结


HashMap和Hashtable集合类的区别
    HashMap与Hashtable都是java的集合类,都可以用来存放java对象,这是他们的相同点,但是他们也有区别。
1、历史原因
    Hashtable是基于陈旧的Dictionary类的,HashMap是java 1.2引进的Map接口的一个实现。
2、同步性
Hashtable是线程同步的。这个类中的一些方法保证了Hashtable中的对象是线程安全的。而HashMap则是线程异步的,因此HashMap中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用HashMap是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销,从而提高效率。
3、值
HashMap可以让你将空值作为一个表的条目的key或value但是Hashtable是不能放入空值的(null)
集合框架--深入讨论
进一步理解集合框架
    java的设计者给我们提供了这些集合类,在后面编程中是相当有用的,具体什么时候用什么集合,要根据我们刚才分析的集合异同来选取。
 如何选用集合类?
1、要求线程安全,使用Vector、Hashtable
2、不要求线程安全,使用ArrayList,LinkedList,HashMap
3、要求key和value键值,则使用HashMap,Hashtable
4、数据量很大,又要线程安全,则使用Vector


三、Set结构的集合类



分为:HashSet类,TreeSet类
HashSet是基于HashMap实现的,HashSet底层采用HashMap来保存所有元素。
hashCode和equal()是HashMap用的,因为无需排序所以只需要关注定位和唯一性即可
hashCode是用来计算hash值的,hash值是用来确定hash表索引的
hash表中的一个索引存放的是一张链表,
所以还要通过equal方法循环比较链上的每一个对象才可以真正定位到键值对应的Entry
put时,如果hash表中没定定位到,就在链表前加一个Entry,
如果定位到了,则更换Entry中的value(值)并返回旧value(值)
覆写key的hashCode()和equal()时一定要注意,不要把它们和可变属性关联上,
否则属性变了之后hashCode会变,equal也会为false,
这样在Map中就找不到它了而且这样的对象因为找不到它所以得不到释放,这样就变成了一个无效引用(相当于内存泄漏)


3.1 HashSet集合


import java.util.*;
public class Demo07{
  public static void main(String []args){
    HashSet<Emp> hs=new HashSet<Emp>();
    Emp emp1=new Emp("s001","aa",1.2f);
    Emp emp2=new Emp("s002","bb",1.6f);
    Emp emp3=new Emp("s003","cc",1.8f);
    Emp emp4=new Emp("s001","aa",1.2f);
    hs.add(emp1);
    hs.add(emp2);
    hs.add(emp3);
    hs.add(emp4);
    hs.add(emp1);//重复的emp1,HashSet会自动去除
    System.out.println("HashSet_size="+hs.size());
    System.out.println();
    ArrayList<Emp> al=new ArrayList<Emp>();
    Emp emp5=new Emp("s004","dd",1.0f);
    Emp emp6=new Emp("s005","ee",2.5f);
    al.add(emp5);
    al.add(emp6);
    //al.add(emp1);
    hs.addAll(al);//将al中的值加到hs中,并去除重复的emp1
    System.out.println("HashSet_ArrayList_size="+hs.size());
    System.out.println();
    //转换数组o[],遍历并输出HashSet中的无素
    Object o[]=hs.toArray();
    for(int i=0;i<o.length;i++){
      System.out.println("工号:"+((Emp)o[i]).getEmpNo()+"\t姓名:"+((Emp)o[i]).getName()+"\t工资:"+((Emp)o[i]).getSal());
    }
  }
}
class Emp{
  private String empNo;
  private String name;
  private float sal;
  public Emp(String empNo,String name,float sal){
    this.empNo=empNo;
    this.name=name;
    this.sal=sal;
  }
  public String getEmpNo() {
    return empNo;
  }
  public void setEmpNo(String empNo) {
    this.empNo = empNo;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public float getSal() {
    return sal;
  }
  public void setSal(float sal) {
    this.sal = sal;
  }
}


3.2 TreeSet集合


TreeSet集合类是一个有序集合,它的元素按照升序排序,默认是自然顺序排列,也就是说
TreeSet中的对象元素需要实现Comparable接口。
TreeSet与HashSet类一样没有get()方法来获取列表中的元素,所以也只能通过迭代器方法来获取。
由于TreeMap需要排序,所以需要一个Comparator为键值进行大小比较,
当然也是用Comparator定位的Comparator可以在创建TreeMap时指定,这时排序时使用Comparator.compare
如果创建时没有指定Comparator那么就会使用key.compareTo()方法,
这就要求key必须实现Comparable接口
TreeMap是使用Tree数据结构实现的,所以使用compare接口就可以完成定位了。
TreeSet是依靠TreeMap来实现的,TreeSet是一个有序集合,
它的元素按照升序排列,默认是按照自然顺序排列,也就是说TreeSet中的对象元素需要实现Comparable接口。
TreeSet类中跟HashSet类一要也没有get()方法来获取列表中的元素,所以也只能通过迭代器的方法来获取 。


import java.util.*;
public class Demo08{
  public static void main(String[] args){
    //传递一个比较器来实现你自己的排序方式
    TreeSet tr =new TreeSet(new Student.StudentComparator());
    tr.add(new Student(301,"张三"));//将学生数据写入TreeSet集合类的tr中
    tr.add(new Student(201,"李二"));
    tr.add(new Student(101,"王五"));
    tr.add(new Student(101,"穷一"));
    Iterator it=tr.iterator();//迭代器,遍历
    while(it.hasNext()){//判断是否有下一个元素
      System.out.println(it.next());
    }
  }
}
//创建Strudent学生类并实现Comparable与Comparator接口
class Student implements Comparable,Comparator{
  private int num;//定义学号
  private String name;//定义名字
  public Student(int num,String name){
    this.num=num;
    this.name=name;
  }
  public int compareTo(Object o){
    Student st=(Student)o;
    int result;
    result=num>st.num?1:(num==st.num?0:-1);//判断学号是否相同并返回result的值
    //如果学号相等,就按姓名排列
  /*  if(result==0){
      return name.compareTo(st.name);
    }*/
    return result;
  }
  //实现Comparator接口并实现它的抽象方法
  public int compare(Object o1,Object o2){
    Student st1 =(Student)o1;
    Student st2 =(Student)o2;
    return st1.name.compareTo(st2.name);//比较姓名是否相同
  }
  //重写toString()方法,因为如果不重写,打印出来的是16进制代码
  public String toString(){
    return "num="+num+"; name="+name;
  }
  public static class StudentComparator implements Comparator{//定义一个静态StudentComparator类并实现Comparator接口
    public int compare(Object o1,Object o2){
      Student st1 =(Student)o1;
      Student st2 =(Student)o2;
      int result;
      result=st1.num>st2.num?1:(st1.num==st2.num?0:-1);//判断学号是否相同进行排序
      if(result==0){//如果学号相等 就进行名字排序
        result=st1.name.compareTo(st2.name);
      }
      return result;
    }
  }
}


总结


HashSet与TreeSet集合类的区别:
HashSet是基于hash算法实现的,性能优于TreeSet。
通常使用HashSet,在我们需要对其中元素排序的时候才使用TreeSet。


四、Queue结构的集合



Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Queue接口。
Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法了,而不能直接访问 LinkedList的非Queue的方法),
以使得只有恰当的方法才可以使用。BlockingQueue继承了Queue接口。
    队列是一种数据结构。
    它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素。就是说,
    队列以一种先进先出的方式管理数据,如果你试图向一个已经满了的阻塞队列中添加一个元素或者是从一个空的阻塞队列中移除一个元索,将导致线程阻塞。
    在多线程进行合作时,阻塞队列是很有用的工具。
    工作者线程可以定期地把中间结果存到阻塞队列中而其他工作者线线程把中间结果取出并在将来修改它们。
    队列会自动平衡负载。如果第一个线程集运行得比第二个慢,则第二个线程集在等待结果时就会阻塞。
    如果第一个线程集运行得快,那么它将等待第二个线程集赶上来。


d74f74e3c2ed49d582bf52108fe46805.png


阻塞队列的操作可以根据它们的响应方式分为以下三类:add、remove和element操作在你试图为一个已满的队列增加元素或从空队列取得元素时 抛出异常。当然,在多线程程序中,队列在任何时间都可能变成满的或空的,所以你可能想使用offer、poll、peek方法。这些方法在无法完成任务时 只是给出一个出错示而不会抛出异常。
注意:poll和peek方法出错进返回null。因此,向队列中插入null值是不合法的。
offer,add区别:
一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,多出的项就会被拒绝。这时新的 offer 方法就可以起作用了。它不是对调用 add() 方法抛出一个 unchecked 异常,而只是得到由 offer() 返回的 false。
poll,remove区别:
remove()和poll()方法都是从队列中删除第一个元素(head)。remove()的行为与Collection 接口的版本相似,但是新的 poll()方法在用空集合调用时不是抛出异常,只是返回null。因此新的方法更适合容易出现异常条件的情况。
peek,element区别:
element()和peek()用于在队列的头部查询元素。与 remove()方法类似,在队列为空时,element()抛出一个异常,而peek()返回null。
五个队列所提供的各有不同:
* ArrayBlockingQueue :
一个由数组支持的有界队列。基于数组的阻塞循环队列,先进先出原则对元素进行排序。
* LinkedBlockingQueue:
一个由链接节点支持的可选有界队列。基于链表的队列,先进先出排序元素。
* PriorityBlockingQueue:
一个由优先级堆支持的无界优先级队列。PriorityBlockingQueue是对PriorityQueue的再次包装,是基于堆数据结构的,而PriorityQueue是没有容量限制的,与ArrayList一样,所以在优先阻塞队列上put时是不会受阻的。但是由于资源被耗尽,所以试图执行添加操作可能会导致OutOfMemoryError),但是如果队列为空,那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,往入该队列中的元素要具有比较能力。
* DelayQueue:
一个由优先级堆支持的、基于时间的调度队列。(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用null元素。
* SynchronousQueue:
一个利用 BlockingQueue接口的简单聚集(rendezvous)机制。
SynchronousQueue 类是最简单的。它没有内部容量。它就像线程之间的手递手机制。在队列中加入一个元素的生产者会等待另一个线程的消费者。当这个消费者出现时,这个元素就直接在消费者和生产者之间传递,永远不会加入到阻塞队列中。
注意:Queue队列是不能直接实例化的。
原先在java编程中,Queue的实现都是用LinkedList
如:Queue qe=new LinkedList();
注意:此实现不是同步的。如果多个线程同时访问一个链接列表,而其中至少一个线程结构上修改了该列表,则它必须保质外部同步。(结构修改指添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。


五、Iterator(迭代器)



java.util包中包含了一系列重要的集合类,集合类的根接口Collection。
Collection接口是所有集合类的根类型。它的一个主要的接口方法是:
boolean add(Object c)添加数据
    add()方法将添加一个新元素。注意这个方法会返回一个boolean,但是返回值不是表示添加成功与否。Collection规定:如果一个集合拒绝添加这个元素,无论任何原因,都必须抛出异常。这个返回值表示的意义是add()方法执行后,集合的内容是否改变了(就是元素有无数量,位置等变化),这是由具体类实现的。即:如果方法出错,总会抛出异常;返回值仅仅表示该方法执行后这个Collection的内容有无变化。
类似还有:
boolean addall(Collection c);添加所有数据
boolean remove(Object o);删除数据
boolean removeall(Collection c);删除所有数据
boolean remainall(Collection c);保持所有数据
    Object[]toArray()方法很简单,把集合转换成数组返回。Object[]toArray(Object[] a)方法就有点复杂了,首先,返回的Object[]仍然是把集合的所有元素变成数组,但是类型和参数a的类型是相同的。
如:String[] o=(String)c.toArray(new String[0]);
得到的o实际类型是String[]数组。
    其次,如果参数a的大小装不下集合的所有元素,返回的将是一个新的数组。如果参数a的大小能装下集合的所有元素,则返回的还是a,但a的内容用集合的元素来填充。尤其要注意的是,如果a的大小比集合元素的个数还多,a后面的部分全部被置为null(空)。
    最后一个最重要的方法是Iterator(),返回一个Iterator(迭代子),用于遍历集合的所有元素。


5.1 遍历集合


Iterator模式 是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。
例如,如果没有使用Iterator,遍历一个数组的方法是使用索引:
  for(int i=0;i<array.size();i++){..get(i)...}
而访问一个链表(LinkedList)又必须使用while循环:
  while((e=e.next())!=null){...e.data()...}
    以上两种方法客户端都必须事先知道集合的内部结构,访问代码和集合本身是紧耦合,无法将访问逻辑从集合类和客户端代码中分离出来,每一种集合对应一种遍历方法,客户端代码无法复用。
    更恐怖的是,如果以后需要把ArrayList更换为LinkedList,则原来的客户端代码必须全部重写。
    为解决以上问题,Iterator模式总是用同一种逻辑来遍历集合:
  for(Iterator it=c.iterater();it.hasNext();){ ...}
    奥秘在于客户端自身不维护遍历集合的"指针",所有的内部状态(如当前元素位置,是否有下一个元素)都由Iterator来维护,而这个Iterator由集合类通过工厂方法生成,因此,它知道如何遍历整个集合。
    客户端从不直接和集合类打交道,它总是控制Iterator,向它发送"向前","向后","取当前元素"的命令,就可以间接遍历整个集合。
首先看看java.util.Iterator接口的定义:
public interface Iterator {
  boolean hasNext();
  Object next();
  void remove();
}
    依赖前两个方法就能完成遍历,典型的代码如下:
for(Iterator it=c.iterator();it.hasNext();){
  Object o=it.next();
  // 对o的操作...
}
    在JDK1.5中,还对上面的代码在语法上作了简化:
// Type是具体的类型,如String。
for(Type t:c){
// 对t的操作...
}
    每一种集合类返回的Iterator具体类型可能不同,Array可能返回ArrayIterator,Set可能返回SetIterator,Tree 可能返回TreeIterator,但是它们都实现了Iterator接口,因此,客户端不关心到底是哪种Iterator,它只需要获得这个 Iterator接口即可,这就是面向对象的威力。
    要确保遍历过程顺利完成,必须保证遍历过程中不更改集合的内容(Iterator的remove()方法除外),因此,确保遍历可靠的原则是只在一个线程中使用这个集合,或者在多线程中对遍历代码进行同步。
Iterator示例:
  Collection c=new ArrayList();
  c.add("abc");
  c.add("xyz");
  for(Iterator it=c.iterator();it.hasNext();){
    String s=(String)it.next();
    System.out.println(s);
  }
如果你把第一行代码的ArrayList换成LinkedList或Vector,剩下的代码不用改动一行就能编译,而且功能不变,这就是针对抽象编程的原则:对具体类的依赖性最小。


其他


Java中的List/Set和Map的区别
    List按对象进入的顺序保存对象,不做排序和编辑操作。
    Set对每个对象只接受一次,并使用自己内部的排序方法(通常,你只关心某个元素是否属于Set而不关心它的顺序--否则使用List)。
    Map同样对每个元素保存一份,但这是基于"键"(key)的,Map也有内置的排序,因而不关心元素添加的顺序。
如果添加元素的顺序对程序设计很重要,应该使用LinkedHashSet或者LinkedHashMap。
List的功能方法
    实际上有两种List:一种是基本的ArrayList其优点在于随机访问元素,另一种是更强大的LinkedList它并不是为快速随机访问设计的,而是具有一套更通用的方法。
    List:次序是List最重要的特点:它保证维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(这只推荐LinkedList使用)一个List可以生成Listlterator,使用它可以从两个方向遍历List,也可以从List中间插入和移除元素。
    ArrayList:由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速率很慢。Listlterator只应该用来由后向前遍历ArrayList。而不是用来插入和移除元素。因为那比LinkedList开销要大很多。
    LinkedList:对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。(使用ArrayList代替)还具有下列方法:addFirst(),addLast(),getFirst(),getLast(),removeFirst()和removeLast()这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
Set的功能方法
    Set具有与Collection完全一样的接口,因此没有任何额外的功能,不象前面有两个不同的List。实际上Set就是Collection,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)Set不保存重复的元素(至于如何判断元素相同则较为负责)
    Set:存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必需定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。
    HashSet:为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。
    TreeSet:保存次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。
    LinkedHashSet:具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
Map的功能方法
    方法put(Object key,Object value)添加一个"值"(想要得东西)和与"值"相关的"键"(key)(使用它来查找)。方法get(Object key)返回与给定"键"相关联的"值"。可以用containsKey()和containsValue()测试Map中是否包含某个"键"或"值"。标准的java类库中包含了几种不同的Map:HashMap,TreeMap,LinkedHashMap,WeakHashMap,ldentityHashMap。它们都有同样的基本接口Map,但是行为、效率、排序策略、保存对象的生命周期和判定"键"等价的策略等各不相同。
    执行效率是Map的一个大问题。看看get()要做哪些事,就会明白为什么在ArrayList中搜索"键"是相当慢的。这正是HashMap提高速度的地方。HashMap使用了特殊的值,称为"散列码"(hash code),来取代对键的缓慢搜索。"散列码"是"相对唯一"用以代表对象的int值,它是通过将该对象的某些信息进行转换而生成的。所有java对象都能产生散列码,因为hashCode()是定义在基类Object中的方法。
    HashMap就是使用对象的hashCode()进行快速查询的。此方法能够显著提高性能。
    Map:维护"键值对"的关联性,使你可通过"键"查找"值"
    HashMap:Map基于散列表的实现。插入和查询"键值对"的开销是固定的。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。
    LinkedHashMap:类似于HashMap,但是迭代遍历它时,取得"键值对"的顺序是其插入次序,或者是最近最少使(LRU)的次序。只能HashMap慢一点。而在迭代访问时发而更快,因为它使用键表维护内部次序。
    TreeMap:基于红黑树数据结果的实现。查看"键"或"键值对"时,它们会被排序(次序由Comparabel或Comparator决定)。TreeMap的特点在于,你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。
    WeakHashMap:旨键(weak key)Map,Map中使用的对象也被允许释放:这是为解决特殊问题设计的。如果没有map之外的引用指向某个"键",则此"键"可以被垃圾收集器回收。
    ldentifyHashMap:使用==代替equals()对"键"作比较的hash map。专为解决特殊问题而设计。



目录
相关文章
|
1月前
|
Java 开发者
在 Java 中,一个类可以实现多个接口吗?
这是 Java 面向对象编程的一个重要特性,它提供了极大的灵活性和扩展性。
143 57
|
3天前
|
JSON Java Apache
Java基础-常用API-Object类
继承是面向对象编程的重要特性,允许从已有类派生新类。Java采用单继承机制,默认所有类继承自Object类。Object类提供了多个常用方法,如`clone()`用于复制对象,`equals()`判断对象是否相等,`hashCode()`计算哈希码,`toString()`返回对象的字符串表示,`wait()`、`notify()`和`notifyAll()`用于线程同步,`finalize()`在对象被垃圾回收时调用。掌握这些方法有助于更好地理解和使用Java中的对象行为。
|
13天前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
34 5
|
26天前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
38 4
|
1月前
|
存储 缓存 安全
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
52 8
|
1月前
|
存储 安全 Java
java.util的Collections类
Collections 类位于 java.util 包下,提供了许多有用的对象和方法,来简化java中集合的创建、处理和多线程管理。掌握此类将非常有助于提升开发效率和维护代码的简洁性,同时对于程序的稳定性和安全性有大有帮助。
73 17
|
1月前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
33 2
|
1月前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
1月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
1月前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
133 4