集合学习

Posted gidybzc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了集合学习相关的知识,希望对你有一定的参考价值。

列表相等只需关心元素数据

下边是一个判断列表相等的例子,代码如下:

1 public static void main(String[] args){
2         ArrayList<String> arr1 = new ArrayList<>();
3         arr1.add("A");
4 
5         Vector<String> arr2 = new Vector<>();
6         arr2.add("A");
7 
8         System.out.print("" + arr1.equals(arr2));
9 }

运行结果为:true

原因分析:二者都是列表(List),实现了List接口,也都继承了AbstractList抽象类,其equals方法是在AbstractList中定义的,其源代码如下:

 1     public boolean equals(Object o) {
 2         if (o == this)
 3             return true;
 4         if (!(o instanceof List))
 5             return false;
 6 
 7         ListIterator<E> e1 = listIterator();
 8         ListIterator<?> e2 = ((List<?>) o).listIterator();
 9         while (e1.hasNext() && e2.hasNext()) {
10             E o1 = e1.next();
11             Object o2 = e2.next();
12             if (!(o1==null ? o2==null : o1.equals(o2)))
13                 return false;
14         }
15         return !(e1.hasNext() || e2.hasNext());
16     }

可以看到,这里只要求实现List接口。只要List中所有的元素且位置相同,就表明两个List是相等的。

其他的集合类型,如Set、Map等与此相同,也是只关心集合元素。

子列表只是原列表的一个视图

List提供了subList方法,用于返回一个列表的子列表。

 1     public static void main(String[] args){
 2         List<String> c = new ArrayList<>();
 3         c.add("A");
 4         c.add("B");
 5 
 6         List<String> c1 = new ArrayList<>(c);
 7         List<String> c2 = c.subList(0, c.size());
 8         c2.add("C");
 9 
10         System.out.println("c == c1?" + c.equals(c1));
11         System.out.println("c == c2?" + c.equals(c2));
12     }

在上边的例子中,c1是通过ArrayList的构造函数创建的,c2是通过subList方法创建的然后添加了一个元素。运行结果如下:

1 c == c1?false
2 c == c2?true

为什么会有上边的结果呢?来看下subList的源码:

1     public List<E> subList(int fromIndex, int toIndex) {
2         return (this instanceof RandomAccess ?
3                 new RandomAccessSubList<>(this, fromIndex, toIndex) :
4                 new SubList<>(this, fromIndex, toIndex));
5     }

subList是由AbstractList实现的,它根据是不是可以随机存取来提供不同的SubList实现方式。由于RandomAccessSubList也是SubList的子类,所以所有的操作都是由SubList类实现的。来看一下SubList类的代码:

class SubList<E> extends AbstractList<E> {
    private final AbstractList<E> l;
    private final int offset;
    private int size;

    SubList(AbstractList<E> list, int fromIndex, int toIndex) {
        if (fromIndex < 0)
            throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
        if (toIndex > list.size())
            throw new IndexOutOfBoundsException("toIndex = " + toIndex);
        if (fromIndex > toIndex)
            throw new IllegalArgumentException("fromIndex(" + fromIndex +
                                               ") > toIndex(" + toIndex + ")");
        l = list;
        offset = fromIndex;
        size = toIndex - fromIndex;
        this.modCount = l.modCount;
    }

    public E get(int index) {
        rangeCheck(index);
        checkForComodification();
        return l.get(index+offset);
    }

    public void add(int index, E element) {
        rangeCheckForAdd(index);
        checkForComodification();
        l.add(index+offset, element);
        this.modCount = l.modCount;
        size++;
    }

    /*其余代码省略,可以在AbstractList.java文件中自行查看*/  
}

不难发现,subList返回的SubList类并没有生成一个数组或者链表,它本身只是原列表的一个视图而已,因此所有的修改动作都反映在了原列表上。

使用subList处理足部列表

看一个简单的需求,一个有100个元素,现在要删除索引位置为20~30的元素。可以通过如下的代码实现:

1     public static void main(String[] args) {
2         List<Integer> initData = Collections.nCopies(100, 0);
3 
4         ArrayList<Integer> list = new ArrayList<>(initData);
5 
6         list.subList(20, 30).clear();
7     }

但是,这个时候就不要再对原列表进行操作了。来看下边的代码:

 1     public static void main(String[] args) {
 2         List<Integer> initData = Collections.nCopies(100, 0);
 3 
 4         ArrayList<Integer> list = new ArrayList<>(initData);
 5         List<Integer> subList = list.subList(20, 30);
 6         list.add(0);
 7 
 8         System.out.print("原列表长度:" + list.size());
 9         System.out.print("子列表长度:" + subList.size());
10     }

  程序运行时,size方法报ConcurrentModificationException。因为subList取出的磊表示原列表的一个视图,原数据集修改了,但是subList取出的子列表不会重新生成,再后面对子列表进行操作时,就会检测到修改计数器与预期的不相同,于是抛出并发修改异常。问题最终还是在子列表提供的size方法的检查上:

    public int size() {
        checkForComodification();
        return size;
    }

    private void checkForComodification() {
        if (this.modCount != l.modCount)
            throw new ConcurrentModificationException();
    }

  modCount是在SubList子列表的构造函数中赋值的,其值等于生成子列表时原列表的修改次数。在生成子列表后再修改原列表,1.modCount必然比modCount大1,不再相等,于是抛出并发修改异常。

  SubList的其他方法也会检查修改计数器。对于子列表操作,子列表的modCount总是跟随原列表进行更新。

  可以在生成子列表后,通过 Collections.unmodifiableList 设置原列表为只读状态,在后续的操作中,对原列表只进行读操作,对子列表进行读写操作。防御式编程就是教我们如此做的。

1     public static void main(String[] args) {
2         List<Integer> initData = Collections.nCopies(100, 0);
3 
4         List<Integer> list = new ArrayList<>(initData);
5         List<Integer> subList = list.subList(20, 30);
6         //设置list为只读状态
7         list = Collections.unmodifiableList(list);
8     }

集合运算

并集

 1     public static void main(String[] args) {
 2         
 3         List<String> list1 = new ArrayList<>();
 4         list1.add("A");
 5         list1.add("B");
 6 
 7         List<String> list2 = new ArrayList<>();
 8         list2.add("A");
 9         list2.add("B");
10         
11         list1.addAll(list2);
12     }

交集

1         list1.retainAll(list2);

  注意:retainAll方法会删除list1中没有在list2中出现的元素

差集

  所有属于A但不属于B的元素组成的集合,叫做A与B的差集。

无重复的并集

1         //去除重复元素
2         list2.removeAll(list1);
3         //取并集
4         list1.addAll(list2);

使用shuffle打乱列表

    Collections.shuffle(list1);

  shuffle方法可以用在一下方面:

  • 程序的“伪装”上

    如:游戏中打怪,修行,宝物分配的分配策略

  • 抽奖程序中
  • 安全传输方面

    发送端发送一组数据,先随机打乱顺序,加密发送;接收端解密后自行排序即可。

 

以上是关于集合学习的主要内容,如果未能解决你的问题,请参考以下文章

金蝶handler中 collection 代码片段理解

Alfred常见使用

比较 C# 中的字符串片段并从集合中删除项目

201621123054《Java程序设计》第九周学习总结

带有红宝石集合/可枚举的酷技巧和富有表现力的片段[关闭]

201621123048《Java程序设计》第九周学习总结