Java中的List

        List是一个有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。

        与 set 不同,列表通常允许重复的元素。更确切地讲,列表通常允许满足 e1.equals(e2) 的元素对 e1 和 e2,并且如果列表本身允许 null 元素的话,通常它们允许多个 null 元素。难免有人希望通过在用户尝试插入重复元素时抛出运行时异常的方法来禁止重复的列表,但我们希望这种用法越少越好。

        综上所述List是一个有序、元素可重复的Collection接口实现,List的主要实现类为:AbstractList, AbstractSequentialList, ArrayList, AttributeList, CopyOnWriteArrayList, LinkedList, RoleList, RoleUnresolvedList, Stack, Vector

        其中ArrayList使我们最常见也是最多使用的,其他的诸如LinkedList、Vector、Stack也会在特殊场合用到。

        在java.util.List接口中定义了很多方法,通过这些方法我们可以对List进行添加元素、清除元素、设置元素、比较元素、计算元素数量等等操作,方法如下:

boolean add(E e) 
          向列表的尾部添加指定的元素(可选操作)。 
void add(int index, E element) 
          在列表的指定位置插入指定元素(可选操作)。 
boolean addAll(Collection<? extends E> c) 
          添加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序(可选操作)。 
boolean addAll(int index, Collection<? extends E> c) 
          将指定 collection 中的所有元素都插入到列表中的指定位置(可选操作)。 
void clear() 
          从列表中移除所有元素(可选操作)。 
boolean contains(Object o) 
          如果列表包含指定的元素,则返回 true。 
boolean containsAll(Collection<?> c) 
          如果列表包含指定 collection 的所有元素,则返回 true。 
boolean equals(Object o) 
          比较指定的对象与列表是否相等。 
E get(int index) 
          返回列表中指定位置的元素。 
int hashCode() 
          返回列表的哈希码值。 
int indexOf(Object o) 
          返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。 
boolean isEmpty() 
          如果列表不包含元素,则返回 true。 
Iterator<E> iterator() 
          返回按适当顺序在列表的元素上进行迭代的迭代器。 
intlastIndexOf(Object o) 
          返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。 
ListIterator<E> listIterator() 
          返回此列表元素的列表迭代器(按适当顺序)。 
ListIterator<E> listIterator(int index) 
          返回列表中元素的列表迭代器(按适当顺序),从列表的指定位置开始。 
E remove(int index) 
          移除列表中指定位置的元素(可选操作)。 
boolean remove(Object o) 
          从此列表中移除第一次出现的指定元素(如果存在)(可选操作)。 
boolean removeAll(Collection<?> c) 
          从列表中移除指定 collection 中包含的其所有元素(可选操作)。 
boolean retainAll(Collection<?> c) 
          仅在列表中保留指定 collection 中所包含的元素(可选操作)。 
E set(int index, E element) 
          用指定元素替换列表中指定位置的元素(可选操作)。 
int size() 
          返回列表中的元素数。 
List<E> subList(int fromIndex, int toIndex) 
          返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。 
Object[] toArray() 
          返回按适当顺序包含列表中的所有元素的数组(从第一个元素到最后一个元素)。 
<T> T[] toArray(T[] a) 
          返回按适当顺序(从第一个元素到最后一个元素)包含列表中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。

 

        以上就是List这个接口所有的方法,对于它的实现类需要实现这些方法,其中像add,get,set,size等都是最经常用到的,下面就对这些方法进行逐一讲解与深入。

 

        1.add方法

        在List中有add(E e)、add(int index, E element) 、addAll(Collection<? extends E> c) 、addAll(int index, Collection<? extends E> c) 这四个方法。

 

    1)add方法可以向列表的尾部或指定位置添加指定的元素,实例如下: 

List list = new List某种实现();
//添加元素
list.add("第一个元素");
//添加元素
list.add("第二个元素");
//添加元素至index=2的位置
list.add(2, "第三个元素");
for (int i = 0, j = list.size(); i < j; i++)
	System.out.println(list.get(i));
//打印结果:
第一个元素
第二个元素
第三个元素

 

        2)add(int index, E element)方法可以在列表的指定位置插入指定元素。将当前处于该位置的元素(如果有的话)和所有后续元素向右移动(在其索引中加 1),实例如下:

List list = new List某种实现();
list.add(1);
list.add(3);
list.add(1,2);
for (int i = 0, j = list.size(); i < j; i++)
	System.out.println(list.get(i));
//结果:
1
2
3

    插入元素后,之前此位置的元素自动后移一位。

    这里需要注意的是如果index索引值超出范围 (index < 0 || index > size())将会抛出IndexOutOfBoundsException异常。

 

        3)addAll方法可以添加指定 collection 中的所有元素到此列表的结尾或指定起始位置,实例如下:

List list = new List某种实现();
List listTemp = new List某种实现();
list.add("list第一个元素");
list.add("list第二个元素");
list.add("list第三个元素");
listTemp.add("listTemp第一个元素");
listTemp.add("listTemp第二个元素");
listTemp.add("listTemp第三个元素");

//将listTemp中的元素全部添加至list尾部
list.addAll(listTemp);
for (int i = 0, j = list.size(); i < j; i++)
	System.out.println(list.get(i));
//打印结果:
list第一个元素
list第二个元素
list第三个元素
listTemp第一个元素
listTemp第二个元素
listTemp第三个元素

 

        某些列表实现对列表可能包含的元素有限制。例如,某些实现禁止 null 元素,而某些实现则对元素的类型有限制。试图添加不合格的元素会抛出未经检查的异常,通常是 NullPointerException 或 ClassCastException。试图查询不合格的元素是否存在可能会抛出异常,也可能简单地返回 false;某些实现会采用前一种行为,而某些则采用后者。概括地说,试图对不合格元素执行操作时,如果完成该操作后不会导致在列表中插入不合格的元素,则该操作可能抛出一个异常,也可能成功,这取决于实现的选择。此接口的规范中将这样的异常标记为“可选”,也就是api中的“(可选操作)”。

        还需要注意的是,尽管列表允许把自身作为元素包含在内,但建议还是不要这样做,在这样的列表上,equals 和 hashCode 方法不再是定义良好的。

 

        2.get和set方法

    get(int index)、set(int index, E element),这两个方法相辅相成,都是非常重要的方法之一。

 

    1)set方法用来指定元素替换列表中指定位置的元素,当指定index(位置、索引)超出了列表的大小时则会抛出IndexOutOfBoundsException异常,实例如下:

List list = new ArrayList();
list.add("list第一个元素");
list.add("list第二个元素");
list.add("list第三个元素");

//设置索引(index)为1的元素
list.set(1, "list第二个元素(新)");
for (int i = 0, j = list.size(); i < j; i++)
	System.out.println(list.get(i));
打印结果:
list第一个元素
list第二个元素(新)
list第三个元素

    这里需要注意的是index为要替换的元素的索引;element 为要在指定位置存储的元素。

    set的返回值为以前在指定位置的元素,也就是代码中的“list第二个元素”。

 

    2)get方法用来返回列表中指定位置的元素,当指定index(位置、索引)超出了列表的大小时则会抛出IndexOutOfBoundsException异常,实例如下:

List list = new List某种实现();
list.add("第一个元素");
list.add("第二个元素");
list.add("第三个元素");
//获取指定index索引位置的元素内容
System.out.println(list.get(0));
System.out.println(list.get(1));
System.out.println(list.get(2));
//打印结果:
第一个元素
第二个元素
第三个元素

        注意:index参数的类型为int,所以你只能获取到index为2147483647的元素。

 

        3.remove方法

        remove方法与add方法类似:remove(int index)remove(Object o)removeAll(Collection<?> c)retainAll(Collection<?> c)

 

        1)remove(int index) 方法用来移除列表中指定位置的元素,实例如下:

List list = new List某种实现();
list.add(1);
list.add(2);
list.add(3);
//移除索引为1(位置为2)的元素
list.remove(1);
for(int i=0,j=list.size();i<j;i++){
	System.out.println(list.get(i));
}
//结果:
1
3

 

        2)remove(Object o)方法用来从此列表中移除第一次出现的指定元素(如果存在),实例如下:

List list = new List某种实现();
list.add(new Integer(1));
list.add(new Integer(2));
list.add(new Integer(3));
//移除值为3的Integer对象
list.remove(new Integer(3));
for(int i=0,j=list.size();i<j;i++){
	System.out.println(list.get(i));
}
//结果:
1
2

        当参数为包装类的时候会直接移除值相同对象,然而当参数为普通对象时:

List<User> list = new List某种实现<User>();
list.add(new User("小芳"));
list.add(new User("小明"));
list.add(new User("小丽"));
//移除name为小明的用户
list.remove(new User("小明"));
for(int i=0,j=list.size();i<j;i++){
	System.out.println(list.get(i).getName());
}
//结果:
小芳
小明
小丽

        从打印结果发现采用之前的方法无法移除姓名为“小明”的对象,然后将代码改进:

List<User> list = new ArrayList<User>();
User xiaoming=new User("小明");
list.add(new User("小芳"));
list.add(xiaoming);
list.add(new User("小丽"));
//移除name为小明的用户
list.remove(xiaoming);
for(int i=0,j=list.size();i<j;i++){
	System.out.println(list.get(i).getName());
}
//结果:
小芳
小丽

        移除成功!可见移除并不是直接比较对象的属性值而是对象的本身,或者说是否为同一对象,更简单的来讲就是是否为同一引用地址。

 

        3)removeAll(Collection<?> c)retainAll(Collection<?> c)两个方法用来移除参数中的元素和只保留参数中的元素对象,比较简单这里就不讲了。

 

        4.size方法

        size() 方法用来返回该列表的长度,也就是元素个数,实例如下:

List list = new List某种实现();
list.add("第一个元素");
list.add("第二个元素");
list.add("第三个元素");
//获取指定index索引位置的元素内容
System.out.println(list.size());
//打印结果:
3

        值得注意的是如果列表包含多于 Integer.MAX_VALUE(2147483647) 个元素,则返回 Integer.MAX_VALUE,也就是说一个List的size方法只会返回大于等于0小于等于2147483647的整数,然而我们是否可以添加更多的元素呢,下面就通过一个例子来分析这一点:

List list = new List某种实现();
for(long i=0;i<2147483648l;i++){
	list.add(i);
}
System.out.println(list.size());
System.out.println(list.get(2147483647));
//结果:
2147483647
抛出异常IndexOutOfBoundsException

        这里的结果可能根据不同的实现会不同,也许有的实现会直接无法添加超过Integer.MAX_VALUE个元素,从而直接抛出异常。

    有的实现可能还可以继续添加,但是你却无法获取索引大于Integer.MAX_VALUE之后的元素内容。

    总而言之List的元素数量可能并不一定等于size,但是你无法获取索引为(size-1)之后的元素。

        我们将在下一篇文章中深入讨论这个问题。

 

        5.equals方法

        equals(Object o) 方法用来比较指定的对象与列表是否相等,当且仅当指定的对象也是一个列表、两个列表有相同的大小,并且两个列表中的所有相应的元素对相等时才返回 true( 如果 (e1==null ? e2==null :e1.equals(e2)),则两个元素 e1 和 e2 是相等的)。换句话说,如果所定义的两个列表以相同的顺序包含相同的元素,那么它们是相等的。该定义确保了 equals 方法在 List 接口的不同实现间正常工作。

        虽然equals方法并不常用,但是通过下面的例子可以了解一下:

List list1 = new List某种实现1();
List list2 = new List某种实现2();
list1.add(1);
list1.add(2);
list1.add(3);
list2.add(1);
list2.add(2);
list2.add(3);
System.out.println(list1.equals(list2));
//结果:
true

        通过上面的代码与打印结果可以了解到:无论实现是否相同,只要是List的元素顺序和值相同,equals方法就认为两个比较对象相同,也就是说equals比较的是List中的元素而不是实现对象本身(list1,list2),这与String的equals方法类似。

        当列表中元素为对象时:

List list1 = new List某种实现1();
List list2 = new List某种实现2();
list1.add(new User("小明"));
list1.add(new User("小张"));
list1.add(new User("小李"));
list2.add(new User("小明"));
list2.add(new User("小张"));
list2.add(new User("小李"));
System.out.println(list1.equals(list2));
//结果:
false

        从结果发现两个列表比较结果并不相同,这是因为在两个列表中其实创建了6个不同的对象,之后将代码改进:

List list1 = new List某种实现1();
List list2 = new List某种实现2();
User xiaoming=new User("小明");
User xiaozhang=new User("小张");
User xiaoli=new User("小李");
list1.add(xiaoming);
list1.add(xiaozhang);
list1.add(xiaoli);
list2.add(xiaoming);
list2.add(xiaozhang);
list2.add(xiaoli);

System.out.println(list1.equals(list2));
//结果:
true

        结果为相同,说明两个列表中添加的元素是相同的。

 

        6.iterator() 方法 

        List 接口提供了特殊的迭代器,称为 ListIterator,除了允许 Iterator 接口提供的正常操作外,该迭代器还允许元素插入和替换,以及双向访问。还提供了一个方法来获取从列表中指定位置开始的列表迭代器。

        List 接口提供了两种搜索指定对象的方法。从性能的观点来看,应该小心使用这些方法。在很多实现中,它们将执行高开销的线性搜索。

 

        1)iterator() 方法返回按适当顺序在列表的元素上进行迭代的迭代器,实例如下:

List list = new List某种实现();
list.add(1);
list.add(2);
list.add(3);
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
	System.out.println(iterator.next());
}
//结果:
1
2
3

 

        2)listIterator() 方法返回此列表元素的列表迭代器,listIterator方法与iterator方法类似,通过查看api或源代码可知listIterator方法的返回类型ListIterator继承自Iterator并在其上做了扩展,增加了很多List相关的实用方法,诸如:hasPrevious(是否含有上一个元素,便于逆向遍历List时使用);set(用指定元素替换 nextprevious 返回的最后一个元素);remove(从列表中移除由 nextprevious 返回的最后一个元素)等。

        利用迭代最大的好处就程序员不用再去为了索引越界等等异常所苦恼了,只需在迭代过程中对列表元素进行操作即可,以下为一个简单实例:

List list = new List某种实现();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);

ListIterator<Integer> iterator = list.listIterator();
while (iterator.hasNext()) {
	int value = iterator.next();
	//移除偶数
	if (value % 2 == 0)
		iterator.remove();
}
for (int i = 0, j = list.size(); i < j; i++) {
	System.out.println(list.get(i));
}
// 结果:
1
3
5

        代码比较简单,主要功能为清除列表中的偶数元素,虽然利用for循环也可以实现同样的功能,但是却不如利用listIterator那么优雅迅速。

 

        3)listIterator(int index) 方法从列表的指定位置开始返回列表中元素的列表迭代器,实例如下:

List list = new List某种实现();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);

ListIterator<Integer> iterator = list.listIterator(list.size());
while (iterator.hasPrevious()) {
	int value = iterator.previous();
	//移除偶数
	if (value % 2 == 0)
		iterator.remove();
}
for (int i = 0, j = list.size(); i < j; i++) {
	System.out.println(list.get(i));
}
// 结果:
1
3
5

 

        7.toArray()方法

        toArray方法可以将列表转换成数组,在某些代码中还是非常实用的,包括:toArray()toArray(T[] a) 两个方法。

 

        1)toArray()方法返回按适当顺序(从第一个元素到最后一个元素)包含列表中的所有元素的数组。

    由于被转换列表不维护对返回数组的任何引用,因而它将是“安全的”。(换句话说,即使数组支持此列表,此方法也必须分配一个新数组)。因此,调用者可以随意修改返回的数组。此方法充当基于数组的 API 与基于 collection 的 API 之间的桥梁。

    由此可见toArray是非常重要的一个方法,它会直接返回一个新的数组,所以原列表不会受到影响。

    下面是一个简单的实例:

List list = new List某种实现();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);

Object[] intArray=list.toArray();
for (int i = 0, j = intArray.length; i < j; i++) {
	System.out.println(intArray[i]);
}
// 结果:
1
2
3
4
5
        此方法的返回类型为Object[],在列表为基本数据类型时尚可直接使用,绝大部分情况下列表元素均为普通对象类型,这样对于这个数组操作起来就比较麻烦了,然而利用另一个重构方法可以解决这个问题。
 
        2) toArray (T[] a) 方法按照指定数组的运行时类型返回按适当顺序(从第一个元素到最后一个元素)包含列表中所有元素的数组。如果指定数组能容纳列表,则在其中返回该列表。否则,分配具有指定数组的运行时类型和此列表大小的新数组。

        如果指定数组能容纳列表,并剩余空间(即数组的元素比列表的多),那么会将数组中紧随列表尾部的元素设置为 null。(只有 在调用者知道列表不包含任何 null 元素时此方法才能用于确定列表的长度)。

toArray()方法一样,此方法充当基于数组的 API 与基于 collection 的 API 之间的桥梁。更进一步说,此方法允许对输出数组的运行时类型进行精确控制,在某些情况下,可以用来节省分配开销。

        假定 x 是只包含字符串的一个已知列表。以下代码用来将该列表转储到一个新分配的 String 数组:

     String[] y = x.toArray(new String[0]);

       注意:toArray(new Object[0])toArray() 在功能上是相同的。

        以下是代码实例:

List list = new List某种实现();
list.add(new User("小明"));
list.add(new User("小丽"));
list.add(new User("小红"));

User[] userArray=(User[]) list.toArray(new User[0]);
for (int i = 0, j = userArray.length; i < j; i++) {
	System.out.println(userArray[i].getName());
}
// 结果:
小明
小丽
小红

        此种转换方法中指定数组类型时参数为 new User[0],虽然这里指定了一个长度为0的数组,但是该方法会自动扩容,也就是说指定参数数组长度不够的情况下toArray方法会自动处理,自动增加数组长度;相反的当参数数组长度过长时,将自动填充为null:

List list = new List某种实现();
list.add(new User("小明"));
list.add(new User("小丽"));
list.add(new User("小红"));
User[] userArray = (User[]) list.toArray(new User[5]);
for (int i = 0, j = userArray.length; i < j; i++) {
	if (userArray[i] != null)
		System.out.println(userArray[i].getName());
	else
		System.out.println(userArray[i]);
}
// 结果:
小明
小丽
小红
null
null

 

        进而我们再次将代码修改:

List list = new List某种实现();
list.add(new User("小明"));
list.add(new User("小丽"));
list.add(new User("小红"));
User[] userArrayTemp=new User[3];
User[] userArray=(User[]) list.toArray(userArrayTemp);
for (int i = 0, j = userArrayTemp.length; i < j; i++) {
	System.out.println(userArrayTemp[i].getName());
}
System.out.println(userArrayTemp);
System.out.println(userArray);
System.out.println(userArray.equals(userArrayTemp));

// 结果:
小明
小丽
小红
[LUser;@67f1fba0
[LUser;@67f1fba0
true

        根据结果可知userArrayTemp与userArray两个数组指向了同一个引用,也就是说toArray在转换过程中如果参数数组长度与输出数组长度相同时则直接返回。

 

        至此List接口的主要方法已经讲解过了,更深入的内容感兴趣的朋友可以一起探讨,下一篇文章将主要讲解List的几个实现,他们之间有什么不同,又都主要用在那些场合。

你可能感兴趣的:(java)