List
List承诺可以将元素维护在特定的序列中。List接口在Collection的基础上添加了大量的方法,使得可以在List的中间插入和移除元素。
有两种基本的List:
- 基本的ArrayList,它长于随机访问元素,但是List的中间插入和移除元素时比较慢。
- LinkedList,它通过代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问方面比较慢,但是它的特性集较ArrayList更大。
ArrayList
package p10;
import java.util.*;
public class ListFeatures {
public static void main(String[] args) {
Random random = new Random(47);
List<Pet> petList = Pets.arrayList(7);
System.out.println("1: " + petList);
Hamster h = new Hamster();
petList.add(h); // Automatically resizes
System.out.println("2: " + petList);
System.out.println("3: " + petList.contains(h));
petList.remove(h);
Pet p = petList.get(2);
System.out.println("4: " + p + " " + petList.indexOf(p));
Pet cymric = new Cymric();
System.out.println("5: " + petList.indexOf(cymric));
System.out.println("6: " + petList.remove(cymric));
// Must be the exact object
System.out.println("7: " + petList.remove(p));
System.out.println("8: " + petList);
petList.add(3,new Mouse());
System.out.println("9: " + petList);
List<Pet> sub = petList.subList(1,4);
System.out.println("subList: " + sub);
System.out.println("10: " + petList.containsAll(sub));
Collections.sort(sub); // In-place sort
System.out.println("sorted subList: " + sub);
// Order is not important in containsAll
System.out.println("11: " + petList.containsAll(sub));
Collections.shuffle(sub,random);
System.out.println("shuffled subList: " + sub);
System.out.println("12: " + petList.containsAll(sub));
List<Pet> copy = new ArrayList<>(petList);
sub = Arrays.asList(petList.get(1),petList.get(4));
System.out.println("sub: " + sub);
copy.retainAll(sub);
System.out.println("13: " + copy);
copy = new ArrayList<>(petList);
copy.remove(2);
System.out.println("14: " + copy);
copy.removeAll(sub);
System.out.println("15: " + copy);
copy.set(1,new Mouse());
System.out.println("16: " + copy);
copy.addAll(2,sub);
System.out.println("17: " + copy);
System.out.println("18: " + petList.isEmpty());
petList.clear(); // Remove all elements
System.out.println("19: " + petList);
System.out.println("20: " + petList.isEmpty());
petList.addAll(Pets.arrayList(4));
System.out.println("21: " + petList);;
Object[] o = petList.toArray();
System.out.println("22: " + o[3]);
Pet[] pa = petList.toArray(new Pet[0]);
System.out.println("23: " + pa[3].id());
}
/**
* 1: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug]
* 2: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Hamster]
* 3: true
* 4: Cymric 2
* 5: -1
* 6: false
* 7: true
* 8: [Rat, Manx, Mutt, Pug, Cymric, Pug]
* 9: [Rat, Manx, Mutt, Mouse, Pug, Cymric, Pug]
* subList: [Manx, Mutt, Mouse]
* 10: true
* sorted subList: [Manx, Mouse, Mutt]
* 11: true
* shuffled subList: [Mouse, Manx, Mutt]
* 12: true
* sub: [Mouse, Pug]
* 13: [Mouse, Pug]
* 14: [Rat, Mouse, Mutt, Pug, Cymric, Pug]
* 15: [Rat, Mutt, Cymric, Pug]
* 16: [Rat, Mouse, Cymric, Pug]
* 17: [Rat, Mouse, Mouse, Pug, Cymric, Pug]
* 18: false
* 19: []
* 20: true
* 21: [Manx, Cymric, Rat, EgyptianMau]
* 22: EgyptianMau
* 23: 14
*/
}
你可以用contains()方法来确定某个对象是否在列表中。如果你想移除一个对象,则可以将这个对象的引用传递给remove()方法。同样,如果你有一个对象的引用,则可以使用indexOf()来发现该对象在List中所处位置的索引编号,就像你在输出行4所见一样。
当确定一个元素是否属于某个List,发现某个元素的索引,以及从某个List中移除一个元素时,都会用到equals()方法(它是根类Object的一部分)。每个Pet都被定义为唯一的对象,因此即使在列表中已经有两个Cymric,如果我再新创建一个Cymric,并把它传递给indexOf()方法,其结果仍会是-1(表示未找到它),而且尝试调用remove()方法来删除这个对象,也会返回false,对于其他的类,equals()的定义可能有所不同。例如,两个String只有在内容完全一样的情况下才会是等价的。因此为了防止意外,就必须意识到List的行为根据equals()的行为而有所变化。
在输出行7和8中,展示了对精确匹配List中某个对象的对象进行移除是成功的。 在List中间插入元素是可行的,就像你在输出行9和它前面的代码中所看到的一样。但是这带来了一个问题:对于LinkedList,在列表中间插入和删除都是廉价操作(在本例中,除了对列表中间进行的真正的随机访问),但是对于ArrayList,这可是代价高昂的操作。这是否意味着你应该永远都不要在ArrayList的中间插入元素,并最好是切换到LinkedList?不,这仅仅意味着,你应该意识到这个问题,如果你开始在某个ArrayList的中间执行很多插入操作,并且你的程序开始变慢,那么你应该看看你的List实现有可能就是罪魁祸首。优化是一个很棘手的问题,最好的策略就是置之不顾,直到你发现需要担心它了(尽管理解这些问题总是一种好的思路)。 subList()方法允许你很容易地从较大的列表中创建出一个片断,而将其结果传递给这个较大的列表的containsAllO方法时,很自然地会得到true。还有一点也很有趣,那就是我们注意到顺序并不重要,你可以在输出行11和12中看到,在sub上调用名字很直观的Collections.sort()和Collection.shuffle()方法,不会影响containsAll()的结果。subList()所产生的列表的幕后就是初始列表,因此,对所返回的列表的修改都会反映到初始列表中,反之亦然。(在下面粘个代码验证这句话) retainAll()方法是-一种有效的“交集”操作,在本例中,它保留了所有同时在copy与sub中的元素。请再次注意,所产生的行为依赖于equals0方法。
set()方法的命名显得很不合时宜,因为它与Set类存在潜在的冲突。在此处,replace可能会显得更适合,因为它的功能是在指定的索引处(第一个参数),用第二个参数替换整个位置的元素。
输出行17表明,对于List,有一个重载的addAll()方法使得我们可以在初始List的中间插入新的列表,而不仅仅只能用Collection中的addAll()方法将其追加到表尾。 输出行18-20展示了isEmpty()和clear()方法的效果。
输出行22-23展示了你可以如何通过使用toArray()方法,将任意的Collection转换为一个数组。这是一个重载方法,其无参数版本返回的是Object数组,但是如果你向这个重载版本传递目标类型的数据,那么它将产生指定类型的数据(假设它能通过类型检查)。如果参数数组太小,存放不下List中的所有元素(就像本例一样),toArray()方法将创建一个具有合适尺寸的数组。Pet对象具有一个id()方法,如你所见,可以在所产生的数组中的对象上调用这个方法。
package p10;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for(int i = 10;i >=0;i--){
list.add(i);
}
List<Integer> subList = list.subList(1,5);
Collections.sort(subList);
System.out.println(subList);
System.out.println(list);
/**
* [6, 7, 8, 9]
* [10, 6, 7, 8, 9, 5, 4, 3, 2, 1, 0]
*/
}
}
LinkedList
LinkedList也像ArrayList一样实现了基本的List接口,但是它执行某些操作(在List的中间插入和移除)时比ArrayList更高效,但在随机访问操作方面却要逊色一些。
LinkedList还添加了可以使其用作栈、队列或双端队列的方法。
这些方法中有些彼此之间只是名称有些差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加适用(特别是在Queue中)。例如,getFirst()和element()完全一样,它们都返回列表的头(第一个元素),而并不移除它,如果List为空,则抛出NoSuchElement-Exception。peek()方法与这两个方式只是稍有差异,它在列表为空时返回null。
removeFirst()与remove()也是完全一样的,它们移除并返回列表的头,而在列表为空时抛出NoSuchElementException。poll()稍有差异,它在列表为空时返回null。
addFirst()与add()和addLast()相同,它们都将某个元素插入到列表的尾部。removeLast()移除并返回列表的最后一个元素。
package p10;
import java.util.LinkedList;
public class LinkedListFeatures {
public static void main(String[] args) {
LinkedList<Pet> pets = new LinkedList<>(Pets.arrayList(5));
System.out.println(pets);
// Identical
System.out.println("pets.getFirst(): " + pets.getFirst());
System.out.println("pets.element(): " + pets.element());
// Only differs in empty-list behavior
System.out.println("pets.peek(): " + pets.peek());
// Identical: remove and return the first element
System.out.println("pets.remove(): " + pets.remove());
System.out.println("pets.removeFirst(): " + pets.removeFirst());
// Only differs in empty-list behavior
System.out.println("pets.poll() " + pets.poll());
System.out.println(pets);
pets.addFirst(new Rat());
System.out.println("After addFirst(): " + pets);
pets.offer(Pets.randomPet());
System.out.println("After offer(): " + pets);
pets.add(Pets.randomPet());
System.out.println("After add(): " + pets);
pets.addLast(new Hamster());
System.out.println("After addLast(): " + pets);
System.out.println("pets.removeLast(): " + pets.removeLast());
/**
* [Rat, Manx, Cymric, Mutt, Pug]
* pets.getFirst(): Rat
* pets.element(): Rat
* pets.peek(): Rat
* pets.remove(): Rat
* pets.removeFirst(): Manx
* pets.poll() Cymric
* [Mutt, Pug]
* After addFirst(): [Rat, Mutt, Pug]
* After offer(): [Rat, Mutt, Pug, Cymric]
* After add(): [Rat, Mutt, Pug, Cymric, Pug]
* After addLast(): [Rat, Mutt, Pug, Cymric, Pug, Hamster]
* pets.removeLast(): Hamster
*/
}
}