day14_集合框架1(ArrayList,LinkedList,HashSet)
1.集合框架大致示意图:
2.集合概述:
/* 数据 -封装在-> 对象 -存储在-> 集合 集合类: 1.为什么出现集合类? 面向对象语言对事物的体现都是以对象的形式,所以为了方便对 多个对象的操作,就对 对象进行存储,集合就是存储对象最常用的 一种方式 2.数组和集合类同是容器,有何不同? 数组虽然也可以存储对象,但长度是固定的,只能存储同一类型对象 集合长度是可变的. 数组中可以存储基本数据类型,集合只能存储对象 集合类的特点: 集合只用于存储对象 集合长度是可变的 集合可以存储不同类型的对象 关于数组: 例如: Person p=new int[3]; p[0]=new Person("zhangsan"); ... 为什么有这么多容器?(集合) 因为每一个容器对数据的存储方式都有不同 这个存储方式称为数据结构. */
3.Collection中的一些方法:
/* 1.add方法的参数类型是Object,以便于接收任意类型的对象 2.集合中存储的都是对象的地址(引用) */ import java.util.*; class CollectionDemo { public static void sop(Object obj) { System.out.println(obj); } public static void base_method() { ArrayList a1=new ArrayList(); a1.add("java1"); a1.add("java2"); a1.add("java3"); a1.add("java4"); ArrayList a2=new ArrayList(); a2.add("java5"); a2.add("java2"); a2.add("java3"); a2.add("java6"); a1.retainAll(a2); sop("取交集后a1为: "+a1);//[java2,java3] sop("取交集后a2为: "+a2);//不变 //a1中存放的是a1∩a2(如果为空集则a1为空) a1.removeAll(a2) sop(“a1-a1∩a2: ”+a1); //a1中存放的是a1-a1∩a2 } public static void main(String[] args) { base_method(); //创建一个集合容器.使用Collection接口的子类(ArrayList) ArrayList a1=new ArrayList(); //1.添加元素. a1.add("java1");//添加字符串对象 a1.add("java2"); a1.add("java3"); a1.add("java4"); //2.获取个数,集合长度 sop(a1.size()); //打印集合 sop(a1);//继承了AbstractCollection中的toString()方法 //返回此 collection 的字符串表示形式, //通过 String.valueOf(Object) 可以将元素转换成字符串。 //并用"[]"括起来 //3.删除元素 a1.remove("java2"); sop(a1); //a1.clear();//清空集合 //4.判断元素 sop("java3是否存在:"+a1.contains("java3")); sop("集合是否为空:"+a1.isEmpty()); } }
4.迭代器(Iterator):
/* 什么是迭代器? 其实就是集合的取出元素的方式. 把元素的取出方式定义在集合的内部(内部类) 这样取出方式就可以直接访问集合内部的元素 那么取出方式就被定义成了内部类. 而每一个容器的数据结构不同, 所以取出的动作细节(代码)也不一样. 但是都有共性内容->判断和取出->进行向上抽取描述 这些内部类都符合一个规则.该规则是Iterator. 如何获取集合的取出对象呢? 通过一个对外提供的方法iterator(); (简单示意图) 该方法是Collection接口继承Java.lang.Iterable接口中的iterator()方法 */ import java.util.*; class CollectionDemo3 { public static void sop(Object obj) { System.out.println(obj); } //获取元素 public static void method_get() { ArrayList a1=new ArrayList(); a1.add("java1"); a1.add("java2"); a1.add("java3"); a1.add("java4"); Iterator it=a1.iterator();//返回一个Iterator接口的子类对象 while(it.hasNext()) sop(it.next()); //另一种写法:迭代器对象随着for循环结束变成垃圾,节约内存. for(Iterator it2=a1.iterator();it2.hasNext(); ) sop(it2.next()); } public static void main(String[] args) { method_get(); } }
5.List集合中的方法:
/* Collection<-List:元素是有序的(存入和取出顺序一致,因为有角标的缘故),元素可以重复.因为该集合体系有索引. <-Set:元素是无序的(存入顺序和取出顺序不一定一致),元素不可以重复. List集合特有方法: 凡是可以操作角标的方法都是该体系特有的方法 增 boolean add(index,element) boolean addAll(index,Collection) 删 remove(index) 改 set(index,element) 查 get(index) sublist(fromIndex,toIndex) listIterator() indexOf(Object o) List集合特有的迭代器: ListIterator是Iterator的子接口:可以实现在遍历过程中的对集合的增 删 改 查 当对一个集合在迭代时,不可以通过集合对象的方法操作(增,删)该集合中的元素. 因为会发生ConcurrentModificationException(并发修改异常)异常. 所以,在迭代时,只能用迭代器的方法操作元素, 可是Iterator的方法是有限的, 只能对元素进行判断,取出,删除的操作 如果想要其他的操作:添加,修改等,就需要使用其子接口:ListIterator 该接口只能通过List集合的listIterator方法获取 */ import java.util.*; class ListDemo { public static void sop(Object obj) { System.out.println(obj); } //增 public static void method_add(ArrayList a1) { a1.add(1,"java增"); sop("插入后: "+a1); } //删 public static void method_remove(ArrayList a1) { a1.remove(2); sop("删后: "+a1); } //改 public static void method_set(ArrayList a1) { a1.set(2,"java改"); sop("改后: "+a1); } //查 public static void method_get(ArrayList a1) { sop("get(1): "+a1.get(1)); //取出全部 for(Iterator it=a1.iterator();it.hasNext();) sop(it.next()); sop("\n"); //或通过遍历 for(int i=0;i<a1.size();++i) sop(a1.get(i)); //通过indexOf获取对象位置 sop("indexOf(2): "+a1.indexOf("java改")); sop("subList(1,3): "+a1.subList(1,3)); } public static void main(String[] args) { ArrayList a1=new ArrayList(); a1.add("java1"); a1.add("java2"); a1.add("java3"); a1.add("java4"); sop("原集合为: "+a1); method_listIterator(a1); method_add(a1); method_remove(a1); method_set(a1); method_get(a1); } //ListIterator public static void method_listIterator(ArrayList a1) { //在迭代过程中准备添加或者删除元素 /* for(Iterator it=a1.iterator();it.hasNext();) { Object obj=it.next(); if(obj.equals("java2")) //a1.add("java7");//集合在该位置添加,对迭代器来说还是认为有4个元素,不能确定取不取 //如果用集合进行删除操作,同理. //it.remove();//迭代器删除的是集合java2的引用 sop("obj="+obj); } */ /* 会报并发修改(迭代器和集合操作)异常. 解决: 要么使用集合方法修改, 要么使用迭代器方法修改. */ ListIterator li=a1.listIterator(); //sop("nextIndex: "+li.nextIndex());//0 //sop("previousIndex: "+li.previousIndex());//-1 while(li.hasNext()) { Object obj=li.next(); if(obj.equals("java2")) li.add("java7"); //li.set("java8");//改 } sop("li.add(\"java7\"): "+a1); //sop("nextIndex: "+li.nextIndex());//5 //sop("previousIndex: "+li.previousIndex());//4 //逆向遍历 while(li.hasPrevious()) sop(li.previous()); //sop("nextIndex: "+li.nextIndex());//0 //sop("previousIndex: "+li.previousIndex());//-1 } } /* hasNext(),next()与hasPrevious(),previous public boolean hasNext() { return cursor!=size; } public boolean hasPrevious() { return cursor!=0; } cursor=0; 0 1 2 next() 0 1 2 3 //返回当前元素(return cursor),指向下一个(cursor++)[0,1 1,2 2,3] previous() 0 1 2 //返回前一个(return cursor-1),指向前一个(cursor)[] public int nextIndex() { return cursor; } public int previousIndex() { return cursor - 1; } */
6.ArrayList与Vector
/* List<-ArrayList:底层的数据结构使用的是数组结构 :查询更改很快,但是增删稍慢 :线程不同步 (线性表...) <-LinkedList:底层的数据结构使用的是链表结构 (单链表,双向链表,循环链表...) :增删速度很快,查询稍慢,线程不同步 <-Vector:底层是数组数据结构. :线程同步.被ArrayList替代了. 可变长度数组: ArrayList: public ArrayList()构造一个初始容量为 10 的空列表。 >10 new一个新的数组长度为10*(1+0.5)=15 把原来数组中的元素拷贝到新数组,把新元素添加到末尾元素后. Vector:则每次需要增大容量时,容量将增大一倍->比较浪费空间 */
7.Vector集合简单示例:
import java.util.*; /* 枚举就是Vector特有的取出方式. 发现枚举(Enumeration<E>)和迭代器很像 其实枚举和迭代器是一样的. 因为枚举的名称以及方法名称都过长 所以被迭代器取代了 枚举郁郁而终了 */ class VectorDemo { public static void main(String[] args) { Vector v=new Vector(); v.add("java1"); v.add("java2"); v.add("java3"); v.add("java4"); Enumeration en=v.elements();//返回Enumeration<E>是一个接口 while(en.hasMoreElements())//使用的Enumeration中的方法 { System.out.println(en.nextElement()); } } }
8.LinkedList集合:
import java.util.*; /* LinkedList特有方法: addFirst();//头插法建 addLast();//尾插法建表 getFirst(); getLast(); 获取元素,但不删除元素 removeFirst(); removeLast(); 既取还删,并且返回被删元素 如果列表为空抛出NoSuchElementException 在JDK1.6出现替代方法 offerFirst(); offerLast(); 插入指定元素 peekFirst(); peekLast(); 获取但不移除此列表的元素;如果此列表为空,则返回 null。 pollFirst() pollLast(); 获取并移除此列表的元素;如果此列表为空,则返回 null。 */ class LinkedListDemo { public static void sop(Object obj) { System.out.println(obj); } public static void main(String[] args) { LinkedList link=new LinkedList(); link.addFirst("java1"); link.addFirst("java2"); link.addLast("java3"); link.addFirst("java4"); sop(link); sop(link.removeFirst()); sop("size="+link.size()); sop("\n"); while(!link.isEmpty()) sop(link.removeFirst());//取一个删一个 } }
9.利用LinkedList集合模拟栈和队列:
/* 使用LinkedList模拟一个堆栈或者队列数据结构 堆栈:后进先出 如同一个杯子 队列:先进先出 如同一个水管 */ import java.util.*; /* 为什么要封装? LinkedList只有自身含义 要做成跟项目相关的名称 用自定义名称显得更清晰 */ class QueueList { private LinkedList link; QueueList() { link=new LinkedList(); } public void queueAdd(Object obj) { link.addFirst(obj); } public Object queueGet() { return link.removeLast(); //return link.removeFirst();栈 } public boolean isNull() { return link.isEmpty(); } } class LinkedListTest { public static void main(String[] args) { QueueList q=new QueueList(); q.queueAdd("aa"); q.queueAdd("cc"); q.queueAdd("ee"); q.queueAdd("ff"); while(!q.isNull()) System.out.println(q.queueGet()); } } /* 比较频繁增删操作:LinkedList 增删同时有查询:ArrayList */
10.ArrayList集合:去除集合中重复元素
1.存储字符串对象:
/* 去除ArrayList集合中重复的元素 */ /* 算法思想: ①新建一个容器 ②把旧容器中的元素放到新容器中,并且每次放入均进行判断 如果出现相同元素则不再放入. */ import java.util.*; class ArrayListTest { public static ArrayList removeRepeat(ArrayList al) { //方法一:集合操作 /* ArrayList as=new ArrayList(); for(int i=0;i<al.size();++i) if(!as.contains(al.get(i))) as.add(al.get(i)); */ //方法二:迭代器操作 ArrayList as=new ArrayList(); Object obj; ListIterator li=al.listIterator(); while(li.hasNext()) if(!as.contains(obj=li.next())) as.add(obj); return as; } public static void main(String[] args) { ArrayList al=new ArrayList(); al.add("aa"); al.add("bb"); al.add("aa"); al.add("aa"); al.add("cc"); al.add("bb"); System.out.println(al); System.out.println(removeRepeat(al)); } } /* 总结: ①尽量不要next和previous同时用 这样很容易被指针的指向搞糊涂. 它们两个都会改变cursor. ②尽量不要使用多个next,取一次判断一次 */
2.存储自定义对象:
/* 将自定义对象作为元素存到ArrayList集合中,并去除重复元素 比如:存人对象.同姓名同年龄,视为同一个人,为同一个人. */ /* 思想: ①定义人 类,将数据封装人对象.并且复写掉Object中的equals方法 ②调用contains进行比较,筛选出不同的元素 */ import java.util.*; class Person { private String name; private int age; Person(String name,int age) { this.name=name; this.age=age; } public boolean equals(Object obj) { if(!(obj instanceof Person)) return false;//不同的对象 Person p=(Person)obj; return (this.name==p.name)&&(this.age==p.age); //this.name.equals(p.name) 用String类的equals方法 } public void printPerson() { System.out.println("name:"+name+",age:"+age); } //以上也可以通过定义两个方法(getName,getAge)获取姓名和年龄 } class ArrayListTest2 { public static ArrayList singlePerson(ArrayList ai) { ArrayList as=new ArrayList(); ListIterator lt = ai.listIterator(); while(lt.hasNext()) { Object obj; if(!as.contains(obj=lt.next())) as.add(obj); } return as; } public static void main(String[] args) { ArrayList ai=new ArrayList(); ai.add(new Person("zhang",12)); ai.add(new Person("li",12)); ai.add(new Person("wang",13)); ai.add(new Person("zhang",12)); ai.add(new Person("zhang",12)); ai.add(new Person("wang",13)); for(int i=0;i<ai.size();++i) ((Person)(ai.get(i))).printPerson(); System.out.println(); ai=singlePerson(ai); for(int i=0;i<ai.size();++i) ((Person)(ai.get(i))).printPerson(); } } /* contains 方法: //ArrayList类 public boolean contains(Object o) { return indexOf(o) >= 0; } public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i]))//在集合中,AbstractList中复写了equals //其实比较的是集合中对象的地址 return i; } return -1; } public boolean equals(Object obj) { if(!(obj instanceof Person)) return false;//不同的对象 Person p=(Person)obj; System.out.println(this.name+"..."+p.age); return (this.name==p.name)&&(this.age==p.age); //this.name.equals(p.name) 用String类的equals方法 //用这种方法更好点,因为字符串常量也是一个对象 //用==也可以,因为比较的是对象中的name } remove方法也是调用equals方法把要删除的对象与集合中的对象逐一比较 */ /* List集合判断元素是否相同,依据的是equals方法. */
11.HashSet集合:
/* set:元素是无序的(存入和取出的顺序不一定一致),元素不可以重复. <-hashSet:底层的数据结构是哈希表 Set集合的功能和Collection是一致的 */ import java.util.*; class HashSetDemo { public static void main(String[] args) { String s1=new String("ab"); String s2=new String("ab"); HashSet hs=new HashSet(); hs.add(new HashSetDemo()); hs.add(new HashSetDemo()); hs.add(new String("java")); hs.add(new String("java")); System.out.println(hs); System.out.println(s1.hashCode()+" "+s2.hashCode());// 内存地址不同 //但是其哈希地址相同,String复写了hashCode /* 在hs集合中由哈希函数决定在哈希表中的存储顺序, 造成存的顺序和取的顺序不一致 而List集合中的元素存的顺序和取的顺序一致 因为每一个元素都有一个索引(角标) */ } }
12.HashSet集合如何保证元素唯一性?
/* 用HashSet存储自定义对象(以人为例) HashSet是如何保证元素唯一性呢? 是通过元素的两个方法,hashCode和equals来完成 如果元素的HashCode值相同,会再调用equals与其具有相同hashCode的元素逐一比较,如果为true则认为相同元素,反之不同元素. 如果元素的hashCode()值不同,不在调用equals进行比较,即说明是不同元素. */ /* 通常自定义对象时,要复写equals和hashCode 因为可能存到HashSet集合中 */ import java.util.*; class Person { private String name; private int age; Person(String name,int age) { this.name=name; this.age=age; } //复写hashCode(),使其返回一个定值 /* public int hashCode() { System.out.println(this.name+"....hashCode()...."); return 60; } */ //重新复写hashCode() public int hashCode()//注意返回值为int,注意复写时返回值不要超出int范围 { System.out.println(this.name+"....hashCode()...."); return name.hashCode()+age*37;//如果不让age*37,不同对象可能出现40+20和30+30,相同概率很高,为了尽量保证hashCode唯一 //利用String类中的hashCode()方法 } public boolean equals(Object obj) { if(!(obj instanceof Person)) return false;//不同的对象 Person p=(Person)obj; System.out.println(this.name+"...equals..."+p.name); return (this.name==p.name)&&(this.age==p.age); //this.name.equals(p.name) 用String类的equals方法 } public void printPerson() { System.out.println("name:"+name+",age:"+age); } } class HashSetDemo2 { public static void main(String[] args) { HashSet hs=new HashSet(); hs.add(new Person("zhang",11)); hs.add(new Person("li",11)); hs.add(new Person("zhang",11)); hs.add(new Person("wang",12)); Iterator it=hs.iterator(); while(it.hasNext()) ((Person)it.next()).printPerson(); } } /*以下为分析过程: 1.未复写hashCode()和equals 会打印出四个值,并且有相同的. 这是因为在存入hs集合时,会首先调用hashCode方法,比较哈希值,即是Object的hashCode方法 Object中的hashCode有个特点: 实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数. 也就是说以上为四个不同对象->hashCode值一定不同->因此都存入hs中 2.只复写了equals 在对象存入hs中的时候,并没有调用equals,这是因为hashCode已经不同,无需调用 3.当再把hashCode()复写返回一个定值 ①new Person("zhang",11)会首先调用hashCode,此时集合中没有元素,存入集合 ②new Person("li",11)先调用hashCode->与zhang,11相同 ->调用equals方法与zhang,11比较->return false->存入 ③new Person("zhang",11)调用hashCode->与zhang,11和li,11都相同 ->调用equals方法和zhang,11比较->return true->不再比较并且不存入hs ④new Person("wang",12)同理与以上已存入集合的两个比较->最终存入hs 这时候,发现避免了重复的存入,但是让hashCode返回一个值,这样每个对象都必须进行 两次比较->没有必要(内存地址不同,无需再equals) 4.重新复写hashCode() */ /* 了解下String类中的hashCode() private int hash; public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; } */3.
4.
一个非常好的总结关于Equals和HashCode:http://www.iteye.com/topic/257191
13.HashSet中的remove和contains方法:
import java.util.*; class Person { private String name; private int age; Person(String name,int age) { this.name=name; this.age=age; } public int hashCode()//注意返回值为int,注意复写时返回值不要超出int范围 { System.out.println(this.name+"....hashCode()...."); return name.hashCode()+age*37; } public boolean equals(Object obj) { if(!(obj instanceof Person)) return false;//不同的对象 Person p=(Person)obj; System.out.println(this.name+"...equals..."+p.name); return (this.name==p.name)&&(this.age==p.age); //this.name.equals(p.name) 用String类的equals方法 } public void printPerson() { System.out.println("name:"+name+",age:"+age); } } class HashSetDemo2 { public static void main(String[] args) { HashSet hs=new HashSet(); hs.add(new Person("zhang",11)); hs.add(new Person("li",11)); hs.add(new Person("wang",12)); Iterator it=hs.iterator(); while(it.hasNext()) ((Person)it.next()).printPerson(); System.out.println(hs.contains(new Person("zhang",11)));//true,remove同理 //以上,hashCode()与集合中zhang,11相同->在调用equals->依然相同->存在(true) /* 注意: 对于判断元素是否存在,以及删除等操作,依据的是元素的hashCode方法和equals方法 而List集合判断元素是否相同,以及删除等操作,依据的是equals方法. */ } }