List

List接口

一个List是一个有序的集合(也可以叫作序列)。List可以包含重复元素。除去从Collection继承下来的操作之外, List接口还有以下操作:

  • 位置访问 - 根据元素在List中的数字下标来访问它们。方法有:get, set, add, addAll,remove
  • 查找 - 在List查找指定元素, 并且返回数字下标。方法有indexOflastIndexOf
  • 迭代 - 继承迭代的语义并且利用到List的连续性。方法有:listIterator
  • 区间视图 - sublist方法可以对List进行任意的区间操作。

Java包含两个List接口的通用实现。 ArrayList在实现上有更好的性能, LinkedList在一些特定的场合有更好的性能。

集合操作

假如你已经熟悉了集合操作, 那么从集合继承下来的操作,在List这里和之前你看的集合操作是一样的,没有什么不同。 如果你还不熟悉集合操作,现在就去看。remove 操作会移去在List中第一次出现的元素 (因为List允许重复元素)。addaddAll操作总是把新的元素加在List的末尾,因此下面的语句就是把一个List和另外一个连接起来:

list1.addAll(list2);

下面是一个不破坏原有结构的操作,新创建第三个list,把第一个和第二个连接起来:

List<Type> list3 = new ArrayList<Type>(list1);
list3.addAll(list2);

注意一下上面的语句,它还用到了ArrayList标准的转换式构造方法。
下面的例子(JDK 8或者更高级版本)是用聚合方法把名字放到一个list中去:

List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());

和Set一样,List也重新定义了equalshashCode方法,这样就能逻辑上比较两个List而不用关心他们的实现类。如果两个List包含的元素相同,顺序也相同,它们就是相等的。

位置访问和查找操作

基本的位置访问操作包括:get,set, addremove (setremove操作会返回将要被覆盖或者删除的元素)。其它的操作(indexOflastIndexOf)返回指定元素第一次或者最后一次出现的位置。

addAll操作可以把一个集合的所有元素插入到list的指定位置处。元素的插入顺序是和集合迭代返回的顺序一致的。该操作实际上是模拟Collection的addAll。

下面的方法是用来交换List的两个元素(已经知道它们的索引)

public static <E> void swap(List<E> a, int i, int j) {
    E tmp = a.get(i);
    a.set(i, a.get(j));
    a.set(j, tmp);
}

这是一个多态的算法,它可以交换任意List的两个元素,不管这个List的实现类是什么样的。下面还有一个多态的例子,它用到了我们刚刚看到的swap:

public static void shuffle(List<?> list, Random rnd) {
    for (int i = list.size(); i > 1; i--)
        swap(list, i - 1, rnd.nextInt(i));
}

这个方法,在Java Collection的类中就有,它根据你指定的随机函数把list里面的元素顺序随机一下。有一点微妙之处:算法是从List的末尾元素开始,重复的把随机位置的元素和当前元素交换。不像其它原生的改变顺序,这个方法平稳(假定提供的随机函数很好,所有的元素都几乎会被改序)而快速(刚好需要长度减1次交换)。下面的程序会用到这个方法, 它把运行参数的所有单词随机输出出来:

import java.util.*;

public class Shuffle {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (String a : args)
            list.add(a);
        Collections.shuffle(list, new Random());
        System.out.println(list);
    }
}

实际上上面的程序可以修改成代码更少性能更好的版本。Arrays类有一个静态工厂方法叫做asList,可以把数组当成list来查看。此方法不会复制数组, 因此list上的改动也会改动数组,反之亦然(改动数组也会改动list)。并且此方法返回的list不是一个正常的list,你不能对这个返回的list进行addremove操作, 因为数组本身就是长度不可变的。我们利用Arrays.asList的优势, 再用默认的随机函数调用一下shuffle方法,程序就可以修改下面的精简版:

import java.util.*;

public class Shuffle {
    public static void main(String[] args) {
        List<String> list = Arrays.asList(args);
        Collections.shuffle(list);
        System.out.println(list);
    }
}

迭代

正如你所料,List's iterator方法返回的迭代器包含了List的所有元素的正确序列。List还提供了一个更加丰富的迭代器,那就是ListIterator, 它可以让你从list的不同方向来遍历,可以在迭代的过程中修改list,还可以获取当前迭代的位置。

ListIteratorIterator继承的三个方法hasNext, next,remove, 在功能上没有什么变动,和原来的一样。 ListIterator还有hasPreviousprevious方法,这两个方法就是完全模拟hasNextnext。前者是指向当前游标(隐式的)的前一个元素,而后者是指向当前游标的后一个元素。 previous是把当前游标后退, 而next是把当前游标前进。

下面是标准的反向迭代一个list的操作:

for (ListIterator<Type> it = list.listIterator(list.size()); it.hasPrevious(); ) {
    Type t = it.previous();
    ...
}

注意这个例子中listIterator方法的参数。在list接口中,方法listIterator有两种形式,一种是无参数的,会返回一个ListIterator 游标指向list的开始位置;有一种是有一个整型的参数,返回一个ListIterator 游标指向list的整型参数位置, 通过调用方法next, 会返回游标位置所对应的元素;通过调用方法previous,会返回游标位置减1所对应的元素。如果一个list长度是n,那么就有n+1个有效的游标位置:0到n.

更直观的讲,游标总是处在两个元素之间:调用previous返回的那个元素和调用next返回的那个元素之间。n+1个有效的游标位置就对应着n个元素间的空隙,从第一个元素之前到最后一个元素之后。下图就展示了一个含有4个元素list的5个有效游标位置:
这里写图片描述

虽然nextprevious方法可以混合调用,但是你最好仔细点。第一次调用previous和最后一次调用next返回的元素是同一个。同样的,第一次调用next和最后一次调用previous返回的元素也是同一个。

以下的方法,你也就不会觉得稀奇了。nextIndex方法会返回接下来调用next所返回的元素的位置;previousIndex方法会返回接下调用previous所返回的元素的位置。这种调用经常会用在:1.找到了特定的东西,需要返回当前位置 2. 记录当前ListIterator位置,以便创建另外一个拥有相同位置的ListIterator

你应该也不会惊讶,nextIndex返回的值总是比previousIndex大1。这暗示了这种行为有两个边界情况:1.当游标在第一个元素之前的时候, previousIndex返回-1 2.当游标在最后一个元素之后的时候,nextIndex就返回list.size(). 为了使这些更具体详细一些, 下面是List.indexOf的一种可能的实现方式:

public int indexOf(E e) {
    for (ListIterator<E> it = listIterator(); it.hasNext(); )
        if (e == null ? it.next() == null : e.equals(it.next()))
            return it.previousIndex();
    // Element not found
    return -1;
}

注意,方法indexOf返回的是it.previousIndex(), 虽然我们是从前向后遍历的list. 这是因为,it.nextIndex()会返回我们将要处理的元素的位置, 而it.previousIndex()会返回 我们刚刚处理过的元素位置。ListIterator还提供了两个用于修改list的方法-setaddset方法会把最后调用next或者previous方法返回的元素修改掉。下面的多态方法,会把所有出现的指定的元素替换成另外一个:

public static <E> void replace(List<E> list, E val, E newVal) {
    for (ListIterator<E> it = list.listIterator(); it.hasNext(); )
        if (val == null ? it.next() == null : val.equals(it.next()))
            it.set(newVal);
}

这个例子唯一特殊的地方就是处理valit.next()之间的比较, 需要特殊处理null,不然会报空指针异常。
add方法会紧贴着游标前面插入一个新元素,下面的多形方法就实现了功能:把一个list中指定的元素用另外一个list中的一串元素替换掉:

public static <E> 
    void replace(List<E> list, E val, List<? extends E> newVals) {
    for (ListIterator<E> it = list.listIterator(); it.hasNext(); ){
        if (val == null ? it.next() == null : val.equals(it.next())) {
            it.remove();
            for (E e : newVals)
                it.add(e);
        }
    }
}

区间视图

区间视图或者局部视图的操作是subList(int fromIndex, int toIndex), 它会返回list的一部分, 这部分从fromIndex开始(包括这个位置)到toIndex结束(不包括这个位置), 这种半开区间(因为只包含fromIndex的元素,不包含结束的)和下面的for循环效果一样的:

for (int i = fromIndex; i < toIndex; i++) {
    ...
}

就如同视图的含义一样,返回的区间是原list的一部分,没有复制出来, 也就说修改原来的list, 相应的视图也会变化。

有了这个方法, list上的很多区间操作能够直接借用它而不需要额外指明区间再来操作。例如下面的代码就是移除list上的一个区间段:

list.subList(fromIndex, toIndex).clear();

下面的代码是只在指定的区间内查找某个元素:

int i = list.subList(fromIndex, toIndex).indexOf(o);
int j = list.subList(fromIndex, toIndex).lastIndexOf(o);

请注意,上面这段代码返回的下标位置是元素在subList的中的位置, 不是整个List的位置。

任何能在List上进行多态运算如replace, shuffle,都能在subList进行。

下面是一个利用sublist的多态算法, 它实现了如何从一幅牌里发一手出去。具体点说, 该方法会返回一个新的list(一手牌),这个新的list是从另外一个List(整幅牌)的尾部取出来的包含一定数量元素。 所有在手牌里的元素会从整幅牌里除去(拿掉就存在原来的牌堆中了)

public static <E> List<E> dealHand(List<E> deck, int n) {
    int deckSize = deck.size();
    List<E> handView = deck.subList(deckSize - n, deckSize);
    List<E> hand = new ArrayList<E>(handView);
    handView.clear();
    return hand;
}

请注意,这手幅是从整幅牌的尾部拿的。对于大部分的List的实现类,如ArrayList,从尾部移除元素要比从头部移除性能上要好一些。

下面的程序结合方法dealHandCollections.shuffle能够从一幅52张的牌里随机生成几手牌。程序有两个运行参数:(1)生成几手 (2)每一手的牌数

import java.util.*;

public class Deal {
    public static void main(String[] args) {
        if (args.length < 2) {
            System.out.println("Usage: Deal hands cards");
            return;
        }
        int numHands = Integer.parseInt(args[0]);
        int cardsPerHand = Integer.parseInt(args[1]);

        // Make a normal 52-card deck.
        String[] suit = new String[] {
            "spades", "hearts", 
            "diamonds", "clubs" 
        };
        String[] rank = new String[] {
            "ace", "2", "3", "4",
            "5", "6", "7", "8", "9", "10", 
            "jack", "queen", "king" 
        };

        List<String> deck = new ArrayList<String>();
        for (int i = 0; i < suit.length; i++)
            for (int j = 0; j < rank.length; j++)
                deck.add(rank[j] + " of " + suit[i]);

        // Shuffle the deck.
        Collections.shuffle(deck);

        if (numHands * cardsPerHand > deck.size()) {
            System.out.println("Not enough cards.");
            return;
        }

        for (int i = 0; i < numHands; i++)
            System.out.println(dealHand(deck, cardsPerHand));
    }

    public static <E> List<E> dealHand(List<E> deck, int n) {
        int deckSize = deck.size();
        List<E> handView = deck.subList(deckSize - n, deckSize);
        List<E> hand = new ArrayList<E>(handView);
        handView.clear();
        return hand;
    }
}

运行程序如下:

% java Deal 4 5

[8 of hearts, jack of spades, 3 of spades, 4 of spades,
    king of diamonds]
[4 of diamonds, ace of clubs, 6 of clubs, jack of hearts,
    queen of hearts]
[7 of spades, 5 of spades, 2 of diamonds, queen of diamonds,
    9 of clubs]
[8 of spades, 6 of diamonds, ace of spades, 3 of hearts,
    ace of hearts]

尽管subList操作非常强大,但是在使用的时候还是有一些要注意的。当你对返回了subList之后 , 对原有的List进行添加、删除操作而不是在subList上操作, subList在语义上就不成立了(开始结束位置就不和最初设想的一样了)。因此强烈建议你只把sublist当成一个瞬时的对象来用,也就是执行原有List的一区间操作时用到它。你使用保留subList实例的时间越长,就越有可能原有List被修改了(或者被其它sublist修改了)。请注意,修改一个subList的subList是合法的并且你还可以继续使用原来subList(虽然不是同步的)。

List的算法

大部分Collections的多态算法也适用于List。合理使用这些算法可以非常容易的操纵List, 下面是对这些地算法的总结 ,如果你想要看更多细节,请参照算法章节

  • sort — 用合并排序算法对List排序,是一种快速稳定的算法(稳定的排序算法就是相等的元素不会被打乱)
  • shuffle — 随机打乱List里的元素
  • reverse — 对List的元素反向排序
  • rotate — 根据指定的距离对List元素翻转
  • swap — 把List指定位置的元素交换
  • replaceAll — 把所有出现过的某个元素替换成另外一个
  • fill — 把List的每一个元素用另外一个替换
  • copy — 把一个List的元素复制到另外一个List中去
  • binarySearch — 在一个排序好的List中用二分法查找指定元素
  • indexOfSubList — 查找字串第一次出现的位置
  • indexOfSubList — 查找字串最后一次出现的位置

http://docs.oracle.com/javase/tutorial/collections/interfaces/list.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值