Java容器List的相关操作

246 阅读7分钟

List

​ List承诺可以将元素维护在特定的序列中。List接口在Collection的基础上添加了大量的方法,使得可以在List的中间插入和移除元素。

​ 有两种基本的List:

  1. 基本的ArrayList,它长于随机访问元素,但是List的中间插入和移除元素时比较慢。
  2. 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
         */
    }
}