Java Review (二十七、集合----- List 集合)
@
List 集合代表一个元素有序 、可重复的集合,集合中每个元素都有其对应的顺序索引 。 List 集合允许使用重复元素 , 可以通过索引来访问指定位置的集合元素 。 List 集合默认按元素的添加顺序设置元素的索引 。
Java8 改进的List 接口和Listlterator 接口
相比较父接口 Collection,由于 List 是有序集合 , 因此 List 集合里增加 了一些根据索引来操作集合元素的方法。
- void add(int index, Object element): 将元素 element 插入到 List 集合的 index 处 。
- boolean addAll(int index, Collection c): 将集合 c 所包含的所有元素都插入到 List 集合的 index处。
- Object get(int index): 返回集合 index 索引处的元素。
- int indexOf(Object 0): 返回对象 。 在 List 集合中第 一次出现的位置索引。
- int lastlndexOf(Object 0): 返 回 对象 。 在 List 集合中最后 一 次出现的位置索引 。
- Object remove(int index): 删除并返回 index 索引处的元素 。
- Object set(int index, Object element): 将 index 索引处的元素替换成 e lement 对象,返回被替换的旧元素 。
- List subList(int fromIndex, int toIndex): 返回从索引 fromlndex (包含)到索引 to Index (不包含)处所有集合元素组成的子集合。
所有 的 List 实现类都可以调用这些方法来操作集合元素。与 Set 集合相比, List 增加了根据索引来插入、替换和删除集合元素 的方法。除此之外 , Java 8 还为 List 接口添加了如下两个默认方法 :
- void replaceAll(UnaryOperator operator): 根据 operator 指定的计算规则重新设置 List 集合的所有元素。
- void sort(Comparator c): 根据 Comparator 参数对 List 集合的元素排序 。
下面程序示范了 List 集合的常规用法:
public class ListTest
{
public static void main(String[] args)
{
List books = new ArrayList();
// 向books集合中添加三个元素
books.add(new String("轻量级Java EE企业应用实战"));
books.add(new String("疯狂Java讲义"));
books.add(new String("疯狂Android讲义"));
System.out.println(books);
// 将新字符串对象插入在第二个位置
books.add(1 , new String("疯狂Ajax讲义"));
for (int i = 0 ; i < books.size() ; i++ )
{
System.out.println(books.get(i));
}
// 删除第三个元素
books.remove(2);
System.out.println(books);
// 判断指定元素在List集合中位置:输出1,表明位于第二位
System.out.println(books.indexOf(new String("疯狂Ajax讲义"))); //①
//将第二个元素替换成新的字符串对象
books.set(1, new String("疯狂Java讲义"));
System.out.println(books);
//将books集合的第二个元素(包括)
//到第三个元素(不包括)截取成子集合
System.out.println(books.subList(1 , 2));
}
}
运行结果:
①行代码处,程序试图返回新字符串对象在 List集合中的位置,实际上 List 集合中并未包含该字符串对象 。 因为 List 集合添加宇符串对象时 ,添加的是通过 new 关键宇创建的新字符串对象,①行代码处也是通过 new 关键宇创建的新字符串对象,两个字符串显然不是同一个对象,但 List 的 indexOf 方法依然可以返回 1 。
List 判断两个对象相等只要通过 equals()方法比较返回 true 即可 。
class A
{
public boolean equals(Object obj)
{
return true;
}
}
public class ListTest2
{
public static void main(String[] args)
{
List books = new ArrayList();
books.add(new String("轻量级Java EE企业应用实战"));
books.add(new String("疯狂Java讲义"));
books.add(new String("疯狂Android讲义"));
System.out.println(books);
// 删除集合中A对象,将导致第一个元素被删除
books.remove(new A()); // ①
System.out.println(books);
// 删除集合中A对象,再次删除集合中第一个元素
books.remove(new A()); // ②
System.out.println(books);
}
}
运行结果:
执行①行代码时 ,程序试图删除一个 A 对象 , List 将会调用该A对象的equals()方法依次与集合元素进行比较,如果该 equalsO方法 以某个集合元素作为参数时返回 true , List将会删除该元素——A 类重写了 equalsO方法 , 该方法总是返回 true。所 以每次从 List 集合中删除 A 对象时 ,总是删除 List 集合中的第一个元素 。
与 Set 只提供了 一个 iterator()方法不同, List 还额外提供了 一个 listIterator()方法,该方法返回 一个Listlterator 对象, ListIterator 接口继承了Iterator 接口,提供了专门操作 List 的方法 。 ListIterator 接口在Iterator 接口基础上增加了如下方法 。
- boolean hasPreviousO: 返回该法代器关联的集合是否还有上一个元素 。
- Object previous(): 返回该迭代器的上一个元素。
- void add(Object 0): 在指定位置插入一个元素 。
API:java.util.List
ArrayList 和 Vector 实现类
ArrayList 和 Vector 类都是基于数组实现的 List 类,所以 ArrayList 和 Vector 类封装了一个动态的、允许再分配的 Object[]数组 。 ArrayList 或 Vector 对象使用 initialCapacity 参数来设置该数组的长度, 当向 ArrayList 或 Vector 中添加元素超出了该数组的长度时,它们的 initialCapacity 会自动增加 。
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 如果传入的初始容量大于0,就新建一个数组存储元素
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 如果传入的初始容量等于0,使用空数组EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 如果传入的初始容量小于0,抛出异常
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
对于通常的编程场景,程序员无须关心 ArrayList 或 Vector 的 initialCapacity 。 但如果向ArrayList或 Vector 集合中添加大 量 元素时,可使用ensureCapacity(int minCapacity) 方法一次性地增加initialCapacity 。 这可以减少重分配 的 次数 ,从而提高性能 。
如果开始就知道 ArrayList 或 Vector 集合需要保存多少个元素,则可以在创建它们时就指定initialCapacity 大小 。 如果创建空的 ArrayList 或 Vector 集合时不指定 initialCapacity 参数 ,则 Object[] 数组的长度默认为 10 。
/**
* 默认容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空数组,如果传入的容量为0时使用
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 空数组,传传入容量时使用,添加第一个元素的时候会重新初始为默认容量大小
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储元素的数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 集合中元素的个数
*/
private int size;
除此之外,ArrayList 和 Vector 还提供了如下两个方法来重新分配 Object[]数组:
- void ensureCapacity(int minCapacity): 将 ArrayList 或 Vector 集合的 Object[]数组长度增加大于或等于 minCapacity 值。
- void trimToSize(): 调整 ArrayList 或 Vector 集合 的 Object[]数组长度为 当前元素 的个数 。 调用该方法可减少 ArrayList 或 Vector 集合对象占用 的存储空间 。
ArrayList 和 Vector 在用法上几乎完全相同 ,Vector十分古老,那时候 Java 还没有提供系统的集合框架,所以 Vector 里提供了 一 些方法名很长的方法。
除此之外, ArrayList 和 Vector 的 显著区别是 :
ArrayList 是线程不安全的,当多个线程访问同一个ArrayList 集合时,如果有超过一个线程修改了 ArrayList 集合,则程序必须于动保证该集合的同步性;
但 Vector 集合则是线程安全的,无须程序保证该集合的同步性。因为 Vector 是线程安全的,所以 Vector的性能比 ArrayList 的性能要低 。
API:java.util.Vector
LinkedList 实现类
LinkedList 类是 List 接口的实现类 一它是一 个 List 集合 ,可以根据索引来随机访问集合中的元素 。
除此之外, LinkedList 还实现了 Deque 接口,可以被当成双端队列来使用,还可以被当成"栈"来使用 。
下面程序简单示范了 LinkedList 集合的用法:
public class LinkedListTest
{
public static void main(String[] args)
{
LinkedList books = new LinkedList();
// 将字符串元素加入队列的尾部
books.offer("疯狂Java讲义");
// 将一个字符串元素加入栈的顶部
books.push("轻量级Java EE企业应用实战");
// 将字符串元素添加到队列的头部(相当于栈的顶部)
books.offerFirst("疯狂Android讲义");
// 以List的方式(按索引访问的方式)来遍历集合元素
for (int i = 0; i < books.size() ; i++ )
{
System.out.println("遍历中:" + books.get(i));
}
// 访问、并不删除栈顶的元素
System.out.println(books.peekFirst());
// 访问、并不删除队列的最后一个元素
System.out.println(books.peekLast());
// 将栈顶的元素弹出“栈”
System.out.println(books.pop());
// 下面输出将看到队列中第一个元素被删除
System.out.println(books);
// 访问、并删除队列的最后一个元素
System.out.println(books.pollLast());
// 下面输出:[轻量级Java EE企业应用实战]
System.out.println(books);
}
}
LinkedList 与 ArrayList 的实现机制完全不同:
- ArrayList 内部以数组的形式来保存集合中的元素 , 因此随机访问集合元素时有较好的性能;
- 而 LinkedList 内部以链表的形式来保存集合中的元素,因此随机访问集合元素时性能较差,但在插入、删除元素时性能比较出色(只需改变指针所指的地址即可)。
参考
【1】:《疯狂Java讲义》
【2】:《Java核心技术 卷一》
【3】:Java技术驿站:【死磕 Java 集合】— ArrayList源码分析
【4】:方志朋的专栏:Java基础:Java容器之ArrayList
【5】:Java技术驿站:【死磕 Java 集合】— LinkedList源码分析
【6】:方志朋的专栏:Java基础:Java容器之LinkedList
【7】:廖雪峰的官方网站:使用List