java容器

来源:互联网 发布:dnf选角色就网络中断 编辑:程序博客网 时间:2024/06/03 22:41
容器:java里面为了方便管理多个对象而出现因为有的时候对象会非常之多如果对每个对象都new一下的话,不仅是占用大量的内存空间而且还会生成许多冗余的代码出来,所以就有了这种容器,它可以用来做什么呢?它可以用来方便的管理这些个对象,在用到它们的时候可以进行统一的调用和处理.容器不仅可以放同一种类型的对象,也可以放不同种类的对象,可以有任意多种,只要在取出来的时候去判断它们是什么对象就可以了比如用instanceOf来查看一个取出来的对象为什么类型的对象.这一章要记住的是:1136一个图,一个类,3个知识点,6个接口一个图是java里面如何组织容器的图一个类是Collections类,它实现的都是静态的方法3个知识点6个接口:Collection,Set,List,Map,Iterator,Compareable在集合中如何的确定一个容器是不是包含了另一个对象呢?equals就是完全的一样的意思,如果是这样就是包含,否则不包含,同样在对一个容器进行操作的时候遵循的也是相同的道理,比如remove(a)(a肯定是一个对象了)一个元素来说,它会去它里面去找这个元素a,如果这容器里面有个元素跟a是equals的,那么结果就是可以把它给移除了,否则就移除不了.在实例化一个数组的对象的时候经常用的是:Collection c=new ArrayList();这种声明的好处在于什么呢?在于这样的声明相当于父类引用指向子类的对象,所以呢再下面用c去调用一些方法的时候它调用的都不是ArrayList()自己所特有的方法这样一来,如果后面我发现我需要用linkedList来实现我的功能的话,那么直接的就可以把定义修改为Collection c=new LinkedList();这样做的好处就是下面的所有的原来写过的代码都不用动,因为Collection的子类都必须要实现它所定义的接口,所以那些都是可以继续使用的,为写程序带来了最大的方便.容器里面装的都是Object就是对象,不可以是基础类型的数据,因为基础类型的数据往往都是分配在栈上的,而栈上面的东西经常会被清空的,所以呢在容器里面是保存不了它的.还有就是如果在没有键值对的容器里面的时候在调用容器的remove(),contains()的方法的时侯通常都是重写的equals()方法,如果说是在map的容器里面,去调用这些方法的时候重写的就不仅仅是equals()方法了,而且还有hashcode()这个方法,为什么呢?因为这个方法它比较的时候直接就调用的是hashcode(),这种比较的方式效率就高出好多了.但是hashcode()它不是绝对的物理地址.hashcode()就像是在查字典的时候的索引一样而你所查找的字就是对象,那么同样的hashcode通过它所找到的对象应该是一样的,否则就不符合常规了.重写hashCode是因为如果两个对象相等的话,那么它的hashCode就肯定是相等的,但是hashCode相等并不能代表两个对象相等的,就好像两个相同的子在同一本字典里面的位置是同一页的,但是同一页的字不一定就是同一个字。实际上接口它是可以被实例化的,但是实例化的时候你必须要直接的重写接口所定义的方法,其实跟创建一个新的类,用这个类去实现接口中所定义的方法,然后用这个类去new一个新的实例结果是一样的,如下:public class JieKouDuiXiang {public static void main(String[] args) {DongWu r=new niao();r.eat();r.sleep();DongWu dd=new DongWu() {@Overridepublic void sleep() {System.out.println("动物要睡觉");}@Overridepublic void eat() {System.out.println("动物要吃东西");}};dd.eat();dd.sleep();}}interface DongWu{String name="dada";int id=007;public void eat();public void sleep();}class ren implements DongWu {@Overridepublic void eat() {System.out.println("它饿了就要吃东西");}@Overridepublic void sleep() {System.out.println("它累了就要睡觉");}}class niao implements DongWu {@Overridepublic void eat() {System.out.println("鸟用嘴啄食");}@Overridepublic void sleep() {System.out.println("鸟站着睡觉");}}这个里面就是这个意思,在main()方法中实例化了接口,然后呢就必须对接口所定义的方法去重写,写好了之后这个用接口new出来的实例就可以用自己的方法去做事情了.在遍历容器中的对象的时候为什么用iterator接口来定义一个对象呢?注意:iterator()这个对象只有实现了Collection接口的类才会有的,为什么Map没有呢?因为Map它是地图的意思,也就是说根据指定的位置你就能够找到指定的地点,,它是键值集合,所以就是它本身已经有了指针了,根本不需要你iterator来给我提供这种方法了.是这样的,上面已经说过了,所有的collection类都重写了iterator()方法了,所以就是这些类都实现了这个接口,所以在用这个接口去new一个实例的时侯,就是父类引用指向子类对象,因此呢用iterator()实例化后的对象其实是一个具体类的对象,而且这个对象它根据自己的实际情况实现了自己的iterator()方法,因此用iterator()来实例化一个对象就是相当于父类引用指向子类对象,而且在iterator()一旦被实例化了之后呢,它就会对当前的集合实行锁定,所以其他的对象都访问不了这个集合中的元素了,包括这个集合的本身也不行,这时只有iterator()实例化的对象一人可以操作集合里面的元素,比如remove(),add()还有就是hasNext();这三个方法.这个地方完全可以用具体的某个容器类来实例化自己的对象,但是就达不到多态的效果了.还有一点就是iterator的返回值也是object类型的,为什么呢?这个原则也叫做接口最小化原则因为一个容器它可以放任何引用类型的东西,那么既然上面都可以放的话,它里面就没有标准,也不知道你要放什么东西,但是有一点是肯定的,就是无论放什么它们都是object的子类,那既然这样的话,就是说你放的时候我不知道是什么东西,但是我容器可以把你看成是object类型来放进去,而且在你取出来的时候我还是以object类型返回给你,你可以对这个object类型去强制转换成为它原来的面貌就行了,看下面的实例:public class TestIterator {public static void main(String[] args) {Collection c=new ArrayList();c.add(new String("java"));c.add(new String("C#"));c.add(new String("c++"));for(Object o:c){System.out.println(c);}//for(String s:c){//System.out.println(s);//}}}这个说明了说明呢?下面被注释掉的代码是有错误的代码,为什么for(Object o:c)它没有错误呢?因为c里面能够返回的就是object类型的对象,所以没有错误,而for(String s:c)有错是因为,Object类型的对象它不一定就是String类型的,这个范围给搞错了,尽管它实际上就是String类型,但是对不起容器不清楚,它只认识Object类型,也只返回object类型的对象,但是为什么打印就打印的是String类型的呢?因为String已经重写了toString()方法,这里又是父类引用指向子类对象,所以才会正确的打印的,如果这个地方是一个另外的自定义的类,而且它也重写了toString()的方法,那么你让它去打印,结果肯定也是正确的.由上面可以知道,其实就是抽象类也不是不能被实例化只要你在实例化的时候把它所定义的方法给实现了就可以,示例如下:public class TestAbstract {public static void main(String[] args) {A a=new A() {String name="jack";int age=20;@Overridepublic void display() {System.out.println(this.age+this.name);}};a.display();}}abstract class A{public String name;public int age;public abstract void display();}增强的for循环主要用来方便遍历数组,集合,不过它的内部执行的机制依然是iterator()的方法,当然在此过程中集合也是被锁定的.它的缺点是不能够方便的访问一些指定下标的对象,对一个ArryList()对象和LinkedList()进行逆向的排序的话,哪个效率更高呢?答案是LinkedList因为LinkedList它只需要把指针给掉一个个旧过来了,而ArrayList就要去copy,把后面的copy到前面,它的速度是比较慢的,String类实现了compareable接口,所以呢它肯定重写了compareTo()方法,因此,可以直接的调用它的compareTo()方法.它重写的方式是按照字母的顺序一个个来比较两个字符串的大小的.泛型的意义:在集合中往往要对对象进行处理,重写compareTo()方法要注意的问题就是一定要实现comparable这个接口,重写这个方法是为了在以后对这种类进行排序的时候用于比较用的,之前重写过equals()方法,以及toString()方法,这些方法不能为集合的排序提供依据,所以需要有compareTo()这个方法来承担这个责任.public class TestCompareTo {public static void main(String[] args) {List c=new LinkedList();c.add(new ZhiYuan("b", "shasha"));c.add(new ZhiYuan("A", "jack"));c.add(new ZhiYuan("A", "lilei"));c.add(new ZhiYuan("c", "shasha"));System.out.println(c);Collections.sort(c);//这里边有个问题就是被排序的对象必须是事先list接口才行System.out.println(c);}}class ZhiYuan implements Comparable{String firstName;String lastName;public ZhiYuan(String f,String l){this.firstName=f;this.lastName=l;}public int compareTo(Object obj){ZhiYuan zy=(ZhiYuan)obj;int firstCompare=this.firstName.compareTo(zy.firstName);int secondCompare=this.lastName.compareTo(zy.lastName);int result = 0;if(firstCompare!=0){result = firstCompare;}if(firstCompare==0){result = secondCompare;}return result;}/*注意在重写compareTo()方法的时候,要 * 先实现comparable这个接口再去重写它 * 里面的方法,否则系统不能识别. * (non-Javadoc) * @see java.lang.Object#toString() */public String toString(){return "Name:"+this.firstName+this.lastName;}}使用容器的选择标准:要看容器的效率高低主要就是在数据被装进容器之后,在后续对容器中的数据进行操作的时候读取的效率和修改的效率,ArrayList读快改慢LinkedList读慢该快Hash两者之间.ArrayList的底层实现是数组实现,所以呢它们的每个元素所占的空间都是一样的,而且是连续的,比如一个ArrayList对象arr它的每个元素所占空间为4个字节,那么如果你想要读第5个元素跟读第50000个差不多,因为数组里面只要找到了引用就找到了对象了,那么对于arr来说找第5个元素的引用就是找指定的堆内存区域里面的5X4=20的字节就对了而第50000也就是50000X4=200000,计算机计算的不要太快,所以读对于它来说特别的快,但是该对于它就很慢了,为什么呢?因为它在内存中的空间都是连续的,所以如果要是改其中一个元素,修改还好,比如要移除或者添加,那内部执行的过程就是在此元素之前的所有元素位置下标都要动,这个效率自然就很低了.而LinkedList则正好相反,LinkedList是彼此之间在内存的分配上不连续,它们只是首尾相连,所以在读的时候你就要一个个的向下读,比如找第5个就是沿着第1个向下找第2个.......最后找到第5个,如果要找第50000个的话那就要向下找50000次,这个效率低得很呐,但是你要是去修改它那就很简单了,因为它们虽然内存分配上不连续,但是它们都是首尾相连的,所以呢就是如果你要去修改其中一个值的话就只要修改它自己就行了,无论是增删改,都一样,等你改好之后它们又会首尾相连,其他的没有涉及到的元素根本不用动,所以它的修改效率是很高的,而hashSet的效率处于这两者之间. vector,hashTable这些都是原来遗留下来的容器,它们对线程都是锁定的,执行的效率极低,所以不推荐使用它们而是使用HashSetList或者Map二叉树:一个节点只有两个子元素的树就要二叉树.重写了equals方法的类一般都要重写hashCode()方法,为什么呢?因为在集合Map对对象进行增加的时候,因为Map要求键值不可以重复,所以在对Map对象增加的时候其实要调用一个equals方法来确定集合里面是否已经存在了这个键值,但是呢调用equals方法又太慢,所以它就会自动的调用hashCode()方法,那么这个方法就快多了,所以呢要调用它就要对它进行重写所以,对于重写了equals方法的类就要重新hashCode()这个方法.在Map中有个put(Object key,Object  value)方法.它的作用是向集合里面传入一个键对象,一个值对象,但是这个key可能会已经存在了,但是你这么一put就会把原来的元素给覆盖掉的,就相当于原来的值已经没有了,但是不要担心,如果你想把被覆盖掉的对象给拿出来没问题,put()方法本身就有一个Object类型的返回值,它就是那个被覆盖掉的value值.所以Map里面是不可以传入基本类型数据的,因为它们都不是Object类型的.但是:你会发现如果你把一个基本类型的数据传入里面的话它是没有错误的,为什么呢?因为jdk1.5之后呢,Map类就已经提供了为基本数据类型自动打包和解包的方法,也就是你什么都不用管,只要往里面去put自己的基本数据类型就行了,java自动的帮你完成打包过程,不仅仅是put这个方法,其他的contains等等方法都是一样的,不过在解包的过程就是拿出对象的时候,还是要加上强制的转换这个步骤的,否则也不行,不过它给你省去了一个过程就是转换的过程例如在Map  m中有这样的一个键值: ("one",1);那你要把里面的1拿出来的话,如果系统没有自动的解包过程你就要这样的写:int  i=((Integer)m.get("one")).intValue();但是呢现在你只要这样写就可以了int  i=(Integer)m.get("one");意思也就是,当你告诉它你要强制转换的类型之后,java就已经知道了它要把它转化为什么基本类型的数据了类型.看下面的实例:public class TestMap {public static void main(String[] args) {Map m=new HashMap();m.put("one", 3);  m.put("two", 5);System.out.println((Integer)m.get("one")+1);/* * 这个程序的输出结果为4说明了(Integer)m.get("one") * 的放回值类型为int类型否则就应该返回31了,由此可见 * java的确对解包也自动封装了进去,只不过你得告诉它类型 */}}泛型:出问题越早越好,就是最好能在编译期间就把问题给找到,但是对于集合来说,jdk1.4之前的时候因为object不明确,装入集合的类都被当做object类型来对待,从而失去了自己的个性,从集合中向外取出对象的时候因为不知道类型是什么,所以经常会出错,而这个时候如果出错的话肯定就是处在运行期间,很难找,而且即便是找的对的但是要加上强制的转换,而进行强制转换的时候效率很低.解决这个问题的方法就是泛型:就是在定义集合的时候同时定义集合中的对象类型.指定可以在Collection时指定也可以在Iterator循环时指定.不过在这两个地方都制定是最好的.泛型的好处在于:可以自动的检查你放入集合的是不是指定类型的对象,如果不是对不起就会出错,在你把对象拿出来的时候对于实现了List的容器来说,你不需要加上强制的转换,我已经知道你要转换为什么了,对于Set类型来说它没有下标值所以没有可以得到指定位置对象的功能,但是呢它可以通过iterator来遍历所有的值,这个时候呢,只要对iterator也指定泛型的类型就可以了,得到的对象也不要进行强制的转换.程序的可读性增强.稳定性也会增强.用到集合的时候就要用泛型,这样给程序带来最大的可读性.什么样的类后面可以跟<>泛型的标志呢?只要一个类在Api中后面跟了<E>(e代表的是Element,即元素就是这个类里面的元素的类型的意思.)当然如果后面没有跟的话那么自己也不可以跟的.泛型的另外一个应用就是在一个类实现comparable接口的时候它的后面也有一个<T>它代表的是什么呢?T代表的是Type的意思,因为要实现这个接口,所以呢就要实现它里面的方法,而compareTo()方法它其实要比较只是自身类型的对象所以呢再这个地方直接就给他限定好了,不是我这种类型的就直接的不要传进来,传进来也是错的,我只要自己这一种类型额对象,而相对与原来的时候,如果你不限定传入的类型的话,那么任何的Object类型都可以传进来的,这个时候,对的错的都会有,而且不管它们是否是你所需要的类型,在你比较之前你都要把它们给转换成自己所要的类型,想想,如果传进来的不是自己需要的类型,而且又进行了强制转换了,可是结果是错误的,这个就给些程序带来的莫大的麻烦,而泛型就很好的解决了这个问题.看原来实例的改写:public class TestCompareTo {public static void main(String[] args) {List<ZhiYuan> c=new LinkedList<ZhiYuan>();c.add(new ZhiYuan("张", "shasha"));c.add(new ZhiYuan("李", "jack"));c.add(new ZhiYuan("赵", "lilei"));c.add(new ZhiYuan("王", "shasha"));System.out.println(c);Collections.sort(c);System.out.println(c);//这里边有个问题就是被排序的对象必须是事先list接口才行}}class ZhiYuan implements Comparable<ZhiYuan>{String firstName;String lastName;public ZhiYuan(String f,String l){this.firstName=f;this.lastName=l;}//public int compareTo(Object obj){//ZhiYuan zy=(ZhiYuan)obj;//int firstCompare=this.firstName.compareTo(zy.firstName);//int secondCompare=this.lastName.compareTo(zy.lastName);//int result = 0;//if(firstCompare!=0){//result = firstCompare;//}//if(firstCompare==0){//result = secondCompare;//}//return result;//}/*注意在重写compareTo()方法的时候,要 * 先实现comparable这个接口再去重写它 * 里面的方法,否则系统不能识别. * (non-Javadoc) * @see java.lang.Object#toString() */public String toString(){return "Name:"+this.firstName+this.lastName;}@Overridepublic int compareTo(ZhiYuan o) {int firstCompare=this.firstName.compareTo(o.firstName);int secondCompare=this.lastName.compareTo(o.lastName);int result = 0;if(firstCompare!=0){result = firstCompare;}if(firstCompare==0){result = secondCompare;}return result;}}