Java中的List集合
集合概述
为了在程序中保存数目不确定的对象,JDK中提供了一系列的特殊类,这些类可以存储任意类型的对象,并且长度可变,在Java中这些类被统称为集合。集合类都位于java.util包中。
集合按照其存储类型分为两大类,即单列集合Collection和双列集合Map,这两种集合的特点如下:
Collction:单列集合类的根接口,用于存储一系列符合某种规则的元素,它由两个重要的子接口,分别是List和Set。其中List的特点是元素有序、可重复。Set的特点是元素无序,而且不可重复。List接口的朱啊哟实现类有ArrayList和LinkedList。Set接口的主要实现类有HashSet和TreeSet
Map:双列集合类的根接口,用于存储具有键(key)、值(value)映射映射关系的元素,每个元素包含一对键值,在使用Map集合时可以通过指定的Key找到对应的Value,Map接口的主要实现类有HashMap和TreeMap。
Collection接口
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合(查询API文档)
1. add(E e) 将指定对象存储到容器中 2. addAll() 将指定集合中的元素添加到调用该方法的集合中 3. size() 返回列表中的元素数。 4. contains(Object o) 判断集合中是否包含参数指定的单个元素 5. containsAll(Collection<?> c) 表示判断集合中是否包含指定元素的整体 6. remove(int index) 移除列表中指定位置的元素 7. removeAll(Collection<?> c) 从列表中移除指定 collection 中包含的其所有元素 8. clear() 从列表中移除所有元素(可选操作)。 9. isEmpty() 如果列表不包含元素,则返回 true。 10. set(int index, E element) 将集合中指定位置元素进行替换 11. toArray() 将集合转换成数组 12. retainAll(Collection<?> c) 计算两个集合的交集
List接口
List接口继承自Collection接口,是单列集合的一个重要分支,习惯性的实现了List接口的对象称为List集合。在List集合中允许出现**重复**的元素,所有的元素是以一种线性方式存储的,在程序中可以通过索引来访问集合中的指定元素,另外,List集合还有一个特点就是元素**有序**,即元素的存入顺序和取出顺序一致。
List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法(查询API文档),所有的List实现类都可以通过调用这些方法来对集合元素进行操作。
add(E e) - 向列表末尾追加元素 add(int index,E e) - 在指定位置上添加一个对象 addAll(Collection<? extends E> c) - 将集合元素添加到指定集合的末尾 get(int index) 返回指定位置的对象 remove(int index) 删除指定位置的对象 set(int index, E element) - 用指定元素替换列表中指定位置的元素(可选操作)。 indexOf(Object o) - 返回第一个匹配对象的位置 lastIndexOf(Object o) - 返回最后一个匹配对象的索引 size() - 返回此列表中的元素数。
ArrayList集合
ArrayList是List接口的一个实现类,它是程序中最常见的一种集合。在ArrayList内部封装了一个长度可变的数组对象,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来存储这些元素,因此可以将ArrayList集合看作一个长度可变的数组。
ArrayList集合中大部分方法都是从父类Collection和List继承过来的,其中,add()方法和get方法用于实现元素的存取。接下来通过一个案例来学习ArrayList集合如何存取元素。
public class Example01 { public static void main(String[] args) { ArrayList list = new ArrayList(); //创建ArrayList集合 list.add("老大"); //向集合中添加元素 list.add("老二"); list.add("老三"); list.add("老四"); System.out.println("集合的长度是: "+list.size()); System.out.println("第二个元素是: "+list.get(1)); } }
由于ArrayList 集合的底层是使用一个数组来保存元素的,在增加或删除指定位置的元素时,会导致创建新的数组,效率比较低,因此不适合做大量的增删操作。但这种数组的结构允许程序通过索引的方式来访问数组,因此使用ArrayList集合查找元素很便捷。
LinkedList集合
由于ArrayList 集合在查询元素时速度很快,但在增删元素时效率较低。为了克服这种局限性,可以使用List接口的另一个实现类LinkedList。该集合内部维护了一个双向循环链表,链表中的每一个元素都使用引用的方式来记住它的前一个元素和后一个元素,从而可以将所有的元素彼此连接起来。当插入一个新元素时,只需要修改元素之间的这种引用关系即可,删除一个节点也是如此。正因为这样的存储结构,LinkedList集合对于元素的增删操作具有很高的效率。
void add(index,E element) 在此列表中指定的位置插入指定的元素 void addFirst(Object o) 将指定元素插入次列表的开头 void addLastt(Object o) 将指定元素插入次列表的结尾 Object getFirst() 返回此列表中的第一个元素 Object getLast() 返回此列表中的最后一个元素 Object Remove(int idnex) 移除指定下标的元素 Object removeFirst() 移除并返回次列表中的第一个元素 Object removeFirst() 移除并返回次列表中的最后一个元素
针对集合中的元素进行增加、删除和获取操作示例:
public class Example02 { public static void main(String[] args) { LinkedList link = new LinkedList(); link.add("老大"); link.add("老二"); link.add("老三"); link.add("老四"); System.out.println(link.toString()); //取出并打印该集合中的元素 link.add(3, "Student"); //向该集合中指定位置插入数据 link.addFirst("First"); //向该集合第一个位置插入元素 System.out.println(link); System.out.println(link.getFirst()); //取出该集合中第一个元素 link.remove(3); link.removeFirst(); System.out.println(link); } }
ArrayList和LinkedList区别:
ArrayList
数组实现,查找快、增删慢
由于是数组实现,在增加和删除的时候会牵扯数组扩容以及拷贝元素,所以慢。数组是可以直接按索引查找,所以在查找的时候较为快。
LinkedList
链表实现,增删块、查找慢
由于链表实现,增加时候只要让前一个元素记住自己就可以,删除时候让前一个元素记住后一个元素,后一个元素记录前一个元素这样的增删效率比较高,但查询时需要一个一个的遍历,所以效率较低。
Iterator接口
Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器
迭代器遍历步骤:
1. 调用Iterator()得到一个指向集合序列第一个元素的迭代器
2. 用循环调用hasNext()方法,如果有元素,返回true
3. 在循环中,使用next()方法获取集合中的下一个元素
学习Iterator迭代集合中的元素
public class Example03 { public static void main(String[] args) { ArrayList list = new ArrayList(); //创建ArrayList集合 list.add("老大"); //向集合中添加字符串 list.add("老二"); list.add("老三"); list.add("老四"); Iterator it = list.iterator(); //获取集合的Iterator对象 while(it.hasNext()) { //判断ArrayList中是否存在下一个元素 Object obj = it.next(); //取出ArrayList集合中的元素 System.out.println(obj); } } }
需要特别说明的是,当通过迭代器获取ArrayList 集合中的元素时,都会将这些元素当做Object类型来看待,如果想得到特定类型的元素,则需要进行强制转换
JDK5.0新特性——foreach循环
foreach循环时一种更加简洁的for循环,也称增强for循环。foreach循环用于遍历数组或集合中的元素,其具体语法格式如下:
for(容器中元素类型 临时变量:容器变量){ 执行语句 }
与for循环相比,foreach循环不需要获取容器的长度,也不需要根据索引访问容器中的元素,但它会自动遍历容器中的每个元素。下面通过一个案例对foreach循环进行详细讲解
public class Example04 { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add("小红"); list.add("小明"); list.add("小绿"); for (Object object : list) { System.out.println(object); } } }
脚下留心:
1. foreach循环虽然书写起来很简洁,但是使用时也存在一定的局限性,当使用foreach循环遍历集合和数组时,只能访问集合中的元素,不能对其中的元素进行修改
2. 在使用Iterator迭代器对集合中的元素进行迭代时,如果调用了集合对象的remove()方法去删除元素之后,继续使用迭代器遍历元素,就会出现异常。
为了解决这个问题可以采用两种方式:
第一种方式:找到需要删除的元素后跳出循环不再迭代即可
if("Annie".equals(obj)){ list.remove(obj); break; }
第二种方式:如果需要在集合的迭代期间对集合中的元素进行删除,可以使用迭代器本身的删除方法。
if("Annie".equals(obj)){ it.remove() }
由此可以得出结论,调用迭代器对象的remove()方法删除元素所导致的迭代次数变化,对于迭代器对象本身来说是可预知的。