黑马程序员——Java之集合(上)

来源:互联网 发布:php签到思路 编辑:程序博客网 时间:2024/05/16 11:17

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

内容提要:

       集合框架体系概述;

        共性方法;

        迭代器;

        List集合共性方法;

        ListIterator;

        List集合具体对象的特点;

        Vector中的枚举;

        LinkedList集合;

        List集合性能比较

集合框架体系概述

        为什么会出现集合类呢?集合在Java里面起到什么作用?

        在面向对象语言中,对事物的描述都是用对象这个载体,数据多了用对象进行封装;为了方便对多个对象进行操作及存储,集合就被用于存储对象。

        集合是存储对象最常用的一种方式,而另外一种方式是刚接触Java时使用到的——数组。

        Java集合类是一种特别有用的容器类,可用于存储数量不等的对象,并可以实现常用的数据结构,如栈、队列等。除此之外,Java集合类还有可用于保存具有映射关系的数据元素:Map。

        简而言之:Java集合就像一种容器,可以把多个对象(实际上是对象的引用,但习惯上都称为对象)“丢进”这个“罐子”中。

        数组和集合的特点及区别:

        数组和集合都是容器,有何不同?打个比喻:容器就是用来存放多个“节点”的“罐子”。

        对于数组而言:可以存放基本数据类型也可以存放引用类型的对象,但只能存放同一种类型的对象;长度是固定的。

/* * 测试用例:多维数组的使用 * */public class MultiArray {public static void main(String[] args) {// TODO Auto-generated method stubInteger[] i = new Integer[] { 1, 2, 3, 4 }; // 数组是可以存放引用数据类型元素的int[][] array = new int[3][]; // 数组是引用数据类型,二维数组的存在验证了数组可以存放引用数据类型array[0] = new int[] { 1, 2 };for (int index = 0; index < array[0].length; index++)System.out.println(array[0][index]);System.out.println("hello");}}

        对于集合而言:只要是对象就能存放在集合中,而且可以存放不同类型的对象(实际上只是保存对象的引用变量);其长度是可变的。

import java.util.ArrayList;import java.util.Iterator;public class Test {public static void main(String[] args) {// TODO Auto-generated method stubArrayList al = new ArrayList(); // 定义ArrayList集合对象al.add("zhangfeng"); // 向al中添加String类型独享al.add(13); // 向al中添加Integer类型的对象:int-->Integer自动装箱,只能存放对象元素// 遍历集合元素,并显示Iterator it = al.iterator();while (it.hasNext()) {System.out.println(it.next());}//定义String类型的数组,即只能存入String类型的元素String[] arr = new String[]{"abc","123",123}; //cannot convert from int to String//使用增强for循环遍历数组元素for(String str:arr){System.out.println(str);}}}

        集合框架提纲:

        容器分为很多个种类,对容器的分类就构成了集合框架。

        这里有一个疑问:为什么会出现这么多种类型的容器(不同类型的“罐子”)呢?因为每一种容器对数据的存储方式都有不同,而这里所说的存储方式就称为:底层的数据结构,他们自身具备的特点不一样,导致在存入、取出以及存储元素时表现出来的属性特点不同。

        下图中只是Java集合的一小部分。

        集合类,需要弄清楚集合的类体系。Java中的集合框架是jJava工具包中的成员,位于java.util包中。如下图所示:集合框架的构成及分类图。


        其中:虚线框中的内容是接口;实线框中的内容是实现类,而粗实线框是最常用的集合类 。Utilities类是与集合类相关的工具类,包括:Collections类和Arrays类。

        Set集合、List集合以及Map集合的结构示意图,如下图所示。


        图中,List集合相当于是数组,每个元素在集合中都有自己的编号(索引值),因而存入该集合的元素是有序的(取出顺序和存入顺序一样),因此集合中的元素可以重复;Set集合相当于是一个“罐子”,存入该集合的元素是无序的,因而存入的元素不能重复;和Set类似的是Map集合,但不同点在于,元素是key-value对。


        集合体系中各个元素的特点如上图所示,包括底层实现、线程是否安全及其特点等等。

集合的共性方法

        Collection:单列集合。

        集合类就像容器,现实生活中容器的功能,无非就是添加对象、删除对象、清空对象、判断容器是否为空等,集合类就为这些功能提供了对应的方法。

        Collection作为父接口,包含了很多方法:增加、删除、判断、转变为数组形式以及取出容器中元素等操作。

        在学习集合共性之前,将ArrayList类作为例子说明。

import java.util.*;public class CollectionDemo {public static void sop(Object obj) // 定义为Object类型,以接受任意类型的对象{System.out.println(obj);}public static void main(String[] args) {// TODO Auto-generated method stubArrayList al = new ArrayList(); // 创建Collection子类对象ArrayListArrayList al2 = new ArrayList();// 在ArrayList中添加元素al.add("java01");al.add("java02");al.add("java03");al.add("java04");al.add("java05");al2.add("java03");al2.add("java05");al2.add("java06");al.retainAll(al2); // 取出al中和al2共同的元素,相当于是取出“交集”sop("al2:" + al2);// 判断元素sop("java03是否存在?:" + al.contains("java03"));sop("al是否为空:" + al.isEmpty());sop("size = " + al.size()); // 获取集合长度sop("原集合:" + al); // 打印集合内容al.remove("java03"); // 删除指定元素sop(al); // 打印集合内容al.removeAll(al2); // 删除al中和al2中相同的部分,一次删除一堆元素// al.clear(); //清空集合sop("改变后的集合:" + al);sop("size = " + al.size());sop("al是否为空:" + al.isEmpty()); // 判断集合是否为空}}

        代码说明:add()方法的参数是Object类型的,以添加任意类型的对象。在学习泛型之前,add()方法中包含的E都可以看成是Object类型。集合里面存放的是对象引用(或者地址),而不是变量实体。打印集合时,JVM将集合中的元素封装在[…]输出。

迭代器

        什么是迭代器?就是集合的取出元素的方式,和遍历元素相同。

import java.util.*;public class CollectionDemo2 {public static void sop(Object obj) // 定义为Object类型,以接受任意类型的对象{System.out.println(obj);}public static void main(String[] args) {// TODO Auto-generated method stubArrayList al = new ArrayList(); // 创建Collection子类对象ArrayList// 在ArrayList中添加元素al.add("java01");al.add("java02");al.add("java03");al.add("java04");al.add("java05");sop("size = " + al.size());sop("原集合:" + al); // 打印集合内容/* * Iterator it = al.iterator(); //获取迭代器,用于取出集合中的元素 while(it.hasNext()) { * sop(it.next()); } */for (Iterator it = al.iterator(); it.hasNext();) // 但使用for循环的好处在于,迭代器可释放sop(it.next());}}

        代码分析:ArrayList对象al使用iterator()方法获得该对象上的迭代器,通过循环结构依次获得了该对象上的各个元素。

        为什么要使用迭代器呢,而不是一个单独的方法?因为遍历集合元素不像增加操作,不足以用一个方法来描述,在操作之前还进行是否包含元素等等判断行为(参见Java中Iterator类的实现),因此Java封装成为了一个对象。比如集合中涉及到的判断和取出操作,而且每一种集合的取出元素细节都不一样,最后用一个集合所属的内部类Iterator来描述,就可以直接访问集合内部的元素,以此定义取出动作。而每一个容器的数据结构不同,取出的动作细节也不一样,但是都有共性的内容:判断和取出,这样就将共性抽取,就形成了Iterator接口。

        Iterator接口描述的是这些内部类所必须遵守的规则,每一个集合类都通过对外提供的方法iterator()获取该Iterator对象,即每一个集合对象都有自己的迭代器对象。Iterator接口,最常用的方法同时也仅有以下三个方法:hasNext()、next()以及remove()(删除该迭代器所指对象)。

        迭代器最先指向的是容器第一个元素之前的位置,在迭代时循环中next()调用一次,就要hasNext判断一次。迭代器的工作原理可以类比生活中的实例:游戏机室中抓娃娃的机器。

List集合共性方法

        Collection接口的常见子接口List:元素是有序的(怎么存入,就怎么取出),元素可以重复,因为该集合体系有索引。

        和Collection不一致的是,凡是可以操纵脚标index的方法都是List特有的方法;而但凡操作脚标的,都是数组原理。

        特有方法:具备脚标index的方法(可以插入特定位置,使用脚标实现);具有ListIterator()特有迭代器。

        List还有的一个特有方法:set()方法,可以修改元素内容。

        增:Add(index,elements); addAll(index,Collection);(在指定位置增加元素)

        删:remove(index);

        改:set(index,elemet);

        查:get(index);subList(from,to);listIterator();

import java.util.*;public class ListDemo {public static void sop(Object obj) // 定义为Object类型,以接受任意类型的对象{System.out.println(obj);}public static void main(String[] args) {// TODO Auto-generated method stubArrayList<String> al = new ArrayList<String>(); // 创建Collection子类对象ArrayList// 在ArrayList中添加元素al.add("java01");al.add("java02");al.add("java03");al.add("java04");al.add("java05");sop("原集合内容:" + al);al.add(0, "java06"); // 将元素添加在指定位置,含有脚标index的方法sop("第1次修改后集合内容:" + al);al.remove(0);sop("第2次修改后集合内容:" + al);al.set(1, "第二个元素");sop("第3次修改后集合内容:" + al);sop(al.get(1)); // 通过脚标获取特定位置的元素,get()方法返回元素for (int index = 0; index < al.size(); index++) // 通过遍历脚标取出元素sop("al(" + index + ")=" + al.get(index));Iterator it = al.iterator(); // 通过迭代器遍历元素while (it.hasNext())sop(it.next());sop("index = " + al.indexOf("java04")); // 获取特定元素的脚标List sub = al.subList(2, 4); // 获取子列表sop(sub);}}

        代码分析:ArrayList集合底层实现是数组,因此在该类的方法上很有数组的“风范”。

        操作集合中的元素有两种方式:集合的方法以及迭代器方式。这两种方法可能会发生的情况:迭代器在取出集合元素的过程中,同时使用集合方式修改集合元素;一旦这两种方式并发访问集合对象时,就会出现并发修改异常。

        比如:在迭代时,不可以通过集合对象的方法操作集合中的元素,只能通过迭代器的方法操作元素;但Iterator方法是有限的,只能对元素进行判断,取出,删除的操作。这就引出了将要描述的内容:ListIterator。

ListIterator

        如果想要其他的操作如添加、修改等,就需要使用其子接口——ListIterator,该接口只能通过List集合的listIterator方法获取,实现在遍历过程中的增删改查。

        只有List集合才有这种特有的迭代器,其主要原因是:List集合是具备脚标的数组结构。

        迭代器工作原理:

        迭代器指针最先指向第一个元素的之前位置;在取出元素之前,先判断是否有下一个元素;如果有下一个元素,则取出元素,并移动指针到下一个元素;再次循环上述步骤。

import java.util.*;public class ListDemo2 {public static void sop(Object obj) // 定义为Object类型,以接受任意类型的对象{System.out.println(obj);}public static void main(String[] args) {// TODO Auto-generated method stubArrayList al = new ArrayList(); // 创建Collection子类对象ArrayList// 在ArrayList中添加元素al.add("java01");al.add("java02");al.add("java03");al.add("java04");al.add("java05");sop("原集合内容:" + al);/* * Iterator it = al.iterator(); //此时迭代器认为al中有5个元素 while(it.hasNext()) { * Object obj = it.next(); *  * if(obj.equals("java01")) al.add("java09"); * //并发异常,此时要么使用集合方法,要么使用迭代器方法 sop(obj); } */Iterator it = al.iterator(); // Iterator有局限性while (it.hasNext()) {Object obj = it.next();if (obj.equals("java02"))it.remove(); // 使用迭代器方式,将java01的引用从集合中删除}sop("修改后集合内容:" + al);ListIterator ls = al.listIterator(); // 列表迭代器ListIteratorsop("hasPrevious(): " + ls.hasPrevious());while (ls.hasNext()) // 正向遍历ArrayList集合元素{Object obj = ls.next();if (obj.equals("java01"))// ls.add("java009"); //使用ListIterator,方法就增加了ls.set("替换java01");}sop("hasNext(): " + ls.hasNext());sop("修改后集合内容:" + al);while (ls.hasPrevious()) // 已经遍历结束,逆向遍历ArrayList集合元素{Object obj = ls.previous();sop(obj);}}}

        代码分析:列表迭代器ListIterator只有List集合才有这种特有的迭代器。

List集合子类对象的特点

        实现类ArrayList(最常见的使用集合):底层数据结构使用的是数组结构(带有脚标);特点在于:查询速度快,增加、删除操作较麻烦(元素越多,体现得越明显);此线程不同步,较常用。

        实现类LinkedList:底层的数据结构使用的是链表结构;特点在于:增删的速度很快,查询的速度较慢。

        实现类Vector:底层是数组结构(带有脚标);线程同步的。实际开发中已被ArrayList替代了。

        ArrayList和Vector相当于是可变长度的数组,其中ArrayList初始长度是10,之后以50%的增量增加(必须新创建对象),而后者Vector初始长度也是10,以100%增量增加。

Vector中的枚举

        枚举是Vector特有的取出方式。

        Vector有哪几种取出元素的方式:迭代器、普通for循环(get方法)、按脚标索引以及枚举。

        发现迭代器和枚举很像,其实枚举和迭代是一样的,因为枚举的名称以及方法的名称过长,所以被迭代器所取代了。

        在IO中用到了枚举。

import java.util.*;public class VectorDemo {public static void main(String[] args) {Vector v = new Vector();v.add("java01");v.add("java02");v.add("java03");Enumeration en = v.elements(); // 枚举方式实现遍历while (en.hasMoreElements()) {System.out.println(en.nextElement());}}}

        代码分析:通过Vector对象通过elements()方法返回了将该对象中内容转变为枚举形式。

LinkedList集合

        LinkList特有方法:addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast()等等。

        removeFirst()方法删除并返回链表首位元素;getFirst()方法返回链表首位元素。如果集合中没有元素,会出现异常。在JDK1.6出现了替代方法,offerFirst()、offerLast()添加元素;peekFirst()、peekLast()方法获取元素,但不删除元素,如果集合中没有元素,则返回null;pollFirst()、pollLast()方法获取元素,但元素被删除,如果集合中没有元素,则返回null。

import java.util.*;public class LinkedListDemo {public static void sop(Object obj) // 定义为Object类型,以接受任意类型的对象{System.out.println(obj);}public static void main(String[] args) {// TODO Auto-generated method stubLinkedList ll = new LinkedList();ll.addFirst("java01"); // 将元素添加到链表的头部ll.addFirst("java02");ll.addFirst("java03");ll.addFirst("java04");sop(ll);ll.addLast("java04"); // 将元素添加到链表尾部ll.addLast("java03");ll.addLast("java02");ll.addLast("java01");sop(ll);sop(ll.getFirst()); // 获取链表的头部sop(ll.getLast()); // 获取链表的尾部sop(ll.size()); // 获取链表长度ll.removeFirst(); // 删除链表头部第一个元素sop(ll.size());// 不用迭代器的集合遍历while (!ll.isEmpty())// sop(ll.remove()); //利用删除元素的返回值特性sop(ll.removeLast()); // 逆向删除sop(ll.size());}}

        代码分析:LinkedList是具有指针的链式结构。

        练习题:模拟堆栈和队列的数据结构

/*使用LinkedList模拟一个堆栈或队列数据结构堆栈:先进后出,如同杯子队列:先进先出,如同管子,FIFO结构*/import java.util.*;class Queue {private LinkedList link;Queue() {link = new LinkedList();}public void addEle(Object obj) {link.addFirst(obj); // 先进入的,放在最开始}public Object getEle() {// return link.removeLast(); //最先出去的,应该是结构最后面的部分,以此模拟队列return link.removeFirst(); // 最先出去的,应该是结构最开始的部分,以此模拟堆栈}public boolean isEmpty() {return link.isEmpty();}}public class LinkedListTest {public static void sop(Object obj) {System.out.println(obj);}public static void main(String[] args) {// TODO Auto-generated method stubQueue q = new Queue();q.addEle("java01");q.addEle("java02");q.addEle("java03");sop(q.getEle());}}

       练习题:判断元素是否重复

/* * 去出ArrayList集合中的重复元素 * */import java.util.*;public class ArrayListTest {public static void sop(Object obj) {System.out.println(obj);}public static void main(String[] args) {// TODO Auto-generated method stubArrayList al = new ArrayList();al.add("java01"); // add(Object obj)有类型提升al.add("java02");al.add("java03");al.add("java04");al.add("java04");al.add("java03");al.add("java02");al.add("java01");sop(al);al = singleElement(al);sop(al);}public static ArrayList singleElement(ArrayList al) {ArrayList newAl = new ArrayList();Iterator it = al.iterator();while (it.hasNext()) {Object obj = it.next();if (!newAl.contains(obj))newAl.add(obj);}return newAl;}}

        运行结果:为ArrayList类对象添加元素,循环获取元素并进行判断。

       List集合判断元素是否相同(比如:使用contains()或者remove()时),依据是所存放元素的equals()方法,即使用所存入元素(若该对象所属类未覆写Object类的equals()方法,将默认使用Object类的equals()方法)的equals()方法和另外一个对象进行比较。若结果返回真,则相同;若返回假,则不同。

        equals()方法是什么鬼?Object类的equals()方法比较的是地址值(和操作符==,具有相同的底层实现),假如使用new 对象的方式,默认的equals返回的是false。因此,在存入自定义对象时,需要覆写equals()方法,以判断元素是否相同。需要指出的是:其他集合判断方式是和List不一样。

        Java中的==和equals方法比较:当使用==判断两个变量是否相等时,对于基本数据类型,只要值相等,就返回true;对于引用数据类型(比较的是对象的地址值),只有它们指向同一个对象时,==判断才会返回true。

import java.util.*;/* * 将自定义对象作为元素存入ArrayList集合中,并去除重复元素 * 比如,存人对象,同姓名同年龄,视为同一个人,为重复元素 *  * 思路: * 1.对人进行描述,将数据封装进人对象; * 2.定义容器,将人存入; * 3.取出 * */public class ArrayListTest2 {public static void sop(Object obj) {System.out.println(obj);}public static void main(String[] args) {ArrayList al = new ArrayList();// 給集合添加元素al.add(new Person("zhangsan03", 26)); // add(Object// obj)存入元素的时候,有类型提升(多态)al.add(new Person("zhangsan01", 21)); // 调用add()方法时,底层不调用equals()方法al.add(new Person("zhangsan02", 23));al.add(new Person("zhangsan03", 26));al = singleElement(al);sop(al.remove(new Person("zhangsan01", 21))); // 同样底层也调用了该对象的equals()方法// 遍历集合并输出Iterator it = al.iterator();while (it.hasNext()) {// 获取当前的Person类元素,此时不能多次调用next()方法Person person = (Person) it.next(); // 向下转型,防止编译失败(Object类中没有getName()方法)sop(person.getName() + "--->" + person.getAge());}}public static ArrayList singleElement(ArrayList al) {ArrayList newAl = new ArrayList();Iterator it = al.iterator();while (it.hasNext()) {Object obj = it.next(); // 使用Object类型变量obj指向取到的元素;if (!newAl.contains(obj)) // 判断对象是否相同,底层使用的是Person类的equals()方法newAl.add(obj);/* * //输出结果集合元素 Iterator it1 = newAl.iterator(); while(it1.hasNext()) * sop(it1.next()); */}return newAl;}}class Person { // Person本身类中有equals()方法,继承于Object类,但Object类的equals()方法比较的是地址值private String name;private int age;Person(String name, int age) // 构造类,不要忘记构造函数{this.name = name;this.age = age;}public boolean equals(Object obj) // Person类中的equals方法在底层自动被调用{System.out.println("调用Person类的equals方法"); // 检测是否是自动被调用if (!(obj instanceof Person))return false;else {Person p = (Person) obj; // 类型System.out.println(this.name + "-->" + p.name); // 测试语句return (this.name.equals(p.name) && this.age == p.age); // 此处的equals()方法是String类的}}public String getName() {return name;}public int getAge() {return age;}}

        代码分析:在List集合中存入自定义类对象时,需要覆写equals()方法,以此判断元素是否相同。

List集合性能比较

        对于开发中到底使用哪个类,还得看具体情况。

        如果取到的元素很多,且涉及到了频繁的增删操作,应该使用的是LinkedList;

        如果涉及增删操作,但不频繁时,可以使用LinkedList,也可以使用使用ArrayList;

        如果涉及到了增删,也涉及到了查询,建议使用ArrayList。

        一般情况下,应用的查询操作较多,因此ArrayList是最常见的容器。

        List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List集合默认按元素的添加顺序设置元素的索引。

        ArrayList和Vector都是基于数组实现的List类,该类封装了一个动态的、允许再分配的Object[]数组。ArrayList和Vector的显著区别:ArrayList是线程不安全的,当多个线程访问同一个ArrayList集合时,如果有超过一个线程修改该集合,必须通过代码保证同步。Vector集合则是线程安全的,无须程序保证该集合的同步性。

        一般来说,由于数组以一块连续内存来保存所有的数组元素,所以数组在随机访问时性能最好,所有的内部数组作为底层实现的集合在随机访问时性能都比较好;而内部以链表作为底层实现的集合在执行插入、删除操作时有较好的性能。

1 0
原创粉丝点击