Collection接口

1、集合

在集合中分为单例集合和双列集合

1、集合和数组的区别

  • 集合

  • 数组

相同点:都是容器,都能够用来放入数据;

不同点:

1、数组中既可以放基本类型数据,也可以放入引用类型数据;而集合中只能够放入引用类型数据;

2、数组长度固定;而集合的长度不是固定的;

3、数组中放入的数据都是相同数据类型的;而集合中放入的数据可以不是相同类型的(除去泛型约定之外)

既然接口中定义了所有子类必须要实现的方法,那么不妨来看一下Collection集合中的方法。

2、Collection中的方法

Collection接口继承了Iterable接口,自然而然的也继承了其中的方法。

* add(Object obj); 向集合中添加元素
* addAll(Collection coll1); 将coll1集合中的元素添加到当前的集合中
* size(); 获取添加元素的个数
* isEmpty(); 判断当前集合是否为空
* clear(); 清空集合元素
* contains(Object obj); 判断当前集合是否包含obj
* containsAll(Collection coll1); 判断形参coll1中的所有元素是否都存在于当前集合中
* remove(Object obj); 移除某个元素,会先进行equals()判断
* removeAll(Collection coll1); 从当前集合中移除与coll1中相同的元素
* retainAll(Collection coll1); 获取当前集合和coll1集合的交集,并修改当前集合
* equals(Object obj); 判断当前集合和形参集合的元素是否相同
* hashCode(); 返回当前对象的哈希值
* toArray(); 集合转换为数组
* Iterator<T> iterator(); 获取得到迭代器
* void forEach(Consumer<? super T> action):遍历

增(一个或多个)

删(一个或多个)

判断(判断是否为空、集合中是否包含一个或多个、一个集合和另外一个集合是否相同)

转换(转换成数组)

获取(哈希值、集合大小)

比较(比较两个集合是否存在交集)

画个图来记忆一下:

3、迭代器

1、什么叫迭代

Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。

2、迭代器方法

迭代器实际上是一个对象。这个对象中有三个方法:

Iterator:
	boolean hasNext() : 判断是否有下一个元素
    E next() : 获取下一个元素
    void remove()    

当遍历集合时,首先通过调用t集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。

Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,为了让初学者能更好地理解迭代器的工作原理,接下来通过一个图例来演示Iterator对象迭代元素的过程:

在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。

3、迭代器和迭代器

其实迭代器这种思想在很多其他的容器思想中也有具体的体现。

那么容器和迭代器的关系是什么样子的?

可以将迭代器看成是容器中的一个副本,在操作副本的同时,也在操作Collection集合。所以二者是同步操作,但是不能够在操作副本的同时,又在操作集合。

在获取得到迭代器的那一刻开始,迭代器就是容器集合的一个副本。所以我们可以在获取得到迭代器之前,来对容器中的元素来进行操作;而在获取得到迭代器之后,不允许再来对迭代器进行操作了。

下面通过一段代码来进行演示:

    @Test
    public void testCollection(){
        Collection collection = new ArrayList();
        collection.add("hello");
        collection.add(false);
        collection.add('A');
        final Iterator iterator = collection.iterator();
        while (iterator.hasNext()){
            final Object next = iterator.next();
            if (Objects.equals("hello",next)){
                collection.remove("hello");
            }
        }
    }

这里会在控制台上显示:

java.util.ConcurrentModificationException

并发修改异常

而直接使用迭代器来进行操作,则是没有问题的:

    @Test
    public void testCollection(){
        Collection collection = new ArrayList();
        collection.add("hello");
        collection.add(false);
        collection.add('A');
        final Iterator iterator = collection.iterator();
        while (iterator.hasNext()){
            final Object next = iterator.next();
            if (Objects.equals("hello",next)){
//                collection.remove("hello");
                iterator.remove();
            }
        }
        System.out.println(collection);
    }

此时看控制台:

[false, A]

则是没有任何影响的。但是在我们使用集合在遍历的时候来进行操作的时候,可能会因为删除操作而出现很多问题。

所以在做删除操作的时候,使用迭代器是最好的选择

4、使用迭代器注意情况

(1) NoSuchElementException

  • 迭代器遍历的过程中, 已经没有元素可以获取了, 仍然继续使用next()方法

(2) ConcurrentModificationException

  • 并发修改异常
    • 产生的原因: 使用迭代器遍历集合的同时, 使用集合的方法修改了集合
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("java");
        list.add("c");
        list.add("d");

        // 迭代器遍历
        Iterator<String> it = list.iterator();

        // 判断是否有下一个元素
        while (it.hasNext()) {
            // 获取下一个元素
            String s = it.next();
            // 判断: 如果获取到的是java, 就删除
            if ("java".equals(s)) {
                // 使用集合的删除方法, 会出现并发修改异常
                // list.remove(s);
                // 使用迭代器的删除方法
                it.remove();
            }
        }

        System.out.println(list);

特殊情况, 不会出现并发修改异常:

要删除的元素, 在集合的倒数第二个位置

5、三种遍历选择

  • 普通for循环 : 使用索引(针对list集合)
  • 迭代器 : 删除集合中元素, 使用迭代器自己的remove()
  • 增强for: 只做遍历(本质上也是迭代器,但是表面上没有提供出来而已)

4、泛型

提到集合不得不提的一点,泛型。

泛型在我们使用集合的时候是最常用用到的。

1、泛型的好处

主要来解决了两个问题:

  • 将运行时期问题提前到编译期间
  • 避免了强转的麻烦

比如下面这段代码:

    @Test
    public void testCollection(){
        Collection collection = new ArrayList();
        collection.add("hello");
        collection.add(false);
        collection.add('A');
        final Iterator iterator = collection.iterator();
        while (iterator.hasNext()){
            final Object next = iterator.next();
            System.out.println("获取得到字符串的长度:"+((String)next).length());
        }
        System.out.println(collection);
    }

这里在编译的时候,添加到集合的数据都是Object类型的,但是在进行遍历的时候,就会遇到强制类型转换的问题。

那么就可能会存在着ClassCastException异常问题。

所以使用泛型来解决问题的时候,可以使用下面这种方式:

    @Test
    public void testCollection(){
        Collection<String> collection = new ArrayList();
        collection.add("hello");
        collection.add(false);
        collection.add('A');
        final Iterator iterator = collection.iterator();
        while (iterator.hasNext()){
            final Object next = iterator.next();
            System.out.println("获取得到字符串的长度:"+((String)next).length());
        }
        System.out.println(collection);
    }

那么只需要声明泛型之后,再来进行操作的时候,在编译阶段,就不会出现添加进去其他类型的数据。所以这里是将运行阶段的做的事情提前到了编译期间,那么再次在迭代器中来进行操作的时候,只需要直接进行操作即可。

因为在泛型确定的时候,已经确定下来了集合中的容器的元素类型了。从而避免了强制类型转换可能带来的隐患。

2、泛型的声明

1、泛型类

参考ArrayList即可

泛型的确定时机:在创建对象的时候

2、泛型接口

参考Collection集合

泛型的确定时机,分为了两种情况

1、子类实现接口的时候确定;

2、子类实现接口的时候不确定,但是在确定对象的时候确定;

定义一个泛型接口:

public interface GenericInterface <E>{
        E getE(E e);
}

第一种方式:

/**
 * 第一种使用方式
 * @param <String>
 */
public class GenericInterfaceImpl<String> implements GenericInterface<String> {
    @Override
    public String getE(String string) {
        return null;
    }
}

在第一种方式中,可以看到在确定了泛型之后,再次来重写其中的方法时,就已经在确定好了方法的类型

第二种方式:

/**
 * 第二种使用方式
 */
public class GenericInterfaceImpl1<E> implements GenericInterface<E> {
    @Override
    public E getE(E e) {
        return null;
    }
}

然后再来创建对应的测试类来进行测试:

public class TestGeneric {
    public static void main(String[] args) {
        GenericInterfaceImpl1<Integer> t1 = new GenericInterfaceImpl1<>();
        t1.getE()
        GenericInterfaceImpl1<String> t2 = new GenericInterfaceImpl1<>();
        t2.getE()
        GenericInterfaceImpl1<Double> t3 = new GenericInterfaceImpl1<>();
        t3.getE()
    }
}

在创建对象的时候确定要使用的具体的数据类型,那么在进行添加的时候就需要来使用对应的泛型类型了。

3、泛型方法

参考Arrays.setAll方法

泛型的确定时机:在传递变量值的时候

泛型方法更多的是用来做一种通用性的操作。比如说Arrays.sort方法,在不知道对应的数据类型的情况下来进行遍历方法等等操作。

    public static <T> T testOne(T t){

        return t;
    }

4、泛型的上下边界

在泛型上添加:

<? extends TMP>

这里使用的时候只能够是TMP本身类型或者是TMP的子类

<? super TMP>

这里使用的时候只能够是TMP本身类型或者是TMP的父类。但是一般见到这种方式的时候,使用的都是其本身,也就是TMP数据类型。

public class GenericMethod {
    public static void main(String[] args) {
        Collection<Number> c1 = new ArrayList<>();
        Collection<Object> c2 = new ArrayList<>();
        Collection<Integer> c3 = new ArrayList<>();
        testOne(c3);
        testOne(c2); // 编译报错
        testOne(c1);
    }


    public static void testOne(Collection<? extends Number> t) {

    }
}
public class GenericMethod {
    public static void main(String[] args) {
        Collection<Number> c1 = new ArrayList<>();
        Collection<Object> c2 = new ArrayList<>();
        Collection<Integer> c3 = new ArrayList<>();
        testOne(c3);  // 编译报错
        testOne(c2);
        testOne(c1); 
    }


    public static void testOne(Collection<? super Number> t) {

    }
}

5、工具类的使用

这些工具在平常使用的时候都没有怎么在意,但是现在想在这里来记录总结一下:

1、Objects

java中万物皆是对象,java在JDK7之后,提供了来对对象操作的工具类。方法比较少,我们一个一个的来进行总结即可。

  • boolean equals(Object a, Object b)

    比较两个对象是否相等。可以看一下对应的源码

        public static boolean equals(Object a, Object b) {
            return (a == b) || (a != null && a.equals(b));
        }
    

    首先判断两个对象的地址值是否是相等或者是调用对象的equals方法判断是否是相等的(在此之前对a进行判空操作)。

    其中这里利用了多态的特性,如果是两个类重写了equals方法,那么运行阶段调用的就是特定类型的equals方法。

    那么这里来看一下Object对象中的equals方法:

    public boolean equals(Object obj) {return (this == obj);}
    
    • boolean deepEquals

    通过字面意思来看的话,深入判断是否相等:

        public static boolean deepEquals(Object a, Object b) {
            if (a == b)
                return true;
            else if (a == null || b == null)
                return false;
            else
                return Arrays.deepEquals0(a, b);
        }
    

    也就是 用来比较两个数组中的元素是否都是相同的,如果有一个不同,那么返回FALSE。

  • int hashCode(Object o)

获取得到对象的哈希Code的值:

public static int hashCode(Object o) {
        return o != null ? o.hashCode() : 0;
    }

这里的判断也很简单,如果对象不为空,那么直接返回对象的hashCode的值;如果对象为空,那么对应的哈希值就是0;

  • String toString(Object o)
public static String toString(Object o) {
        return String.valueOf(o);
    }
    public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

直接调用String.valueOf方法来输出对象的值。如果对象为空,那么输出Null;如果不为空,那么直接调用对象的toString方法来打印输出即可

  • String toString(Object o, String nullDefault)

如果对象为空,那么给一个指定的字符串的值,这里还是比较直接的。

public static String toString(Object o, String nullDefault) {
    return (o != null) ? o.toString() : nullDefault;
}
  • int compare(T a, T b, Comparator<? super T> c)

对象的比较方法。这里可以看到传入的是一个函数式接口,那么对应的compare方法我们是可以自己来进行书写的。

还可以看到,这里传入的对象的类型是相同类型的。

    public static <T> int compare(T a, T b, Comparator<? super T> c) {
        return (a == b) ? 0 :  c.compare(a, b);
    }
  • T requireNonNull

如果对象是空的,那么直接抛出空指针异常;如果不是空的,那么返回对象本身;

    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

通过这种方式,我们一般放在参数校验的第一行即可。

  • T requireNonNull(T obj, String message)
    public static <T> T requireNonNull(T obj, String message) {
        if (obj == null)
            throw new NullPointerException(message);
        return obj;
    }

这里是判断是否为空对象,如果是空对象,还可以给出异常信息,这个方法比较方便使用;如果不是,返回对象本身;

  • boolean isNull和boolean nonNull
public static boolean isNull(Object obj) {        return obj == null;    }
public static boolean nonNull(Object obj) {        return obj != null;    }

直接进行的判空操作,相当于是我们自己写的

if(obj == null){    doSomething;}else{    doOtherthing;}
  • T requireNonNull(T obj, Supplier messageSupplier)

相当于是给出是空对象的异常信息;如果不是,那么直接返回对象本身;

    public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {        if (obj == null)            throw new NullPointerException(messageSupplier.get());        return obj;    }
  • int hash(Object... values)

直接计算出来对象的哈希值

    public static int hash(Object... values) {        return Arrays.hashCode(values);    }

2、Arrays

可以看到Objects类中的方法,那么现在一探究竟。结果打开类中的方法一看,发现很多方法都是重载的,那么看一下这里的静态方法

  • 1Arrays.sort(数组名);

对数组进行排序,默认是升序排序

        int [] arr1={12,21,13,24,0};        Arrays.sort(arr1);        for(int i=0;i<arr1.length;i++){            System.out.print(arr1[i]+" ");        }

当然,在看了之后,发现还有另外一种排序方式。可以从指定的下标开始、到指定的下标结束:

        int [] arr1={12,21,13,24,0};        Arrays.sort(arr1,2,arr1.length);        for(int i=0;i<arr1.length;i++){            System.out.print(arr1[i]+" ");        }

发现这种方式以后万一可以用到了呢。

  • void fill()

把数组array所有元素都赋值为指定数

        int [] arr3={12,21,13,24};        Arrays.fill(arr3,22);        for(int i=0;i<arr3.length;i++){            System.out.print(arr3[i]+" ");        }

当然除了这种方式,也有和上面的sort方法一样的方式,从指定下标开始:

        int [] arr3={12,21,13,24};
        Arrays.fill(arr3,0,2,22);
        for(int i=0;i<arr3.length;i++){
            System.out.print(arr3[i]+" ");
        }

但是这里需要注意的是,fill的第三个参数的意思是到第几个索引,但是不包括。也就是左开右闭原则。

  • copyOf()

把数组array复制成一个长度为length的新数组,返回类型与复制的数组一致

        int[] arr3={12,21,13,24};
        int[] arr=Arrays.copyOf(arr3, 6);
        for(int i=0;i<arr.length;i++){
            System.out.print(arr[i]+" ");
        }
        if (Objects.equals(arr3,arr)){
            System.out.println("二者相同");
        }

当然,不用想,肯定也有另外一个从指定下标开始拷贝的方法,但是发现不是这个方法名称了

  • copyOfRange

从范围来进行copy

        int[] arr3 = {12, 21, 13, 24};        int[] arr = Arrays.copyOfRange(arr3, 0, 3);        for (int i = 0; i < arr.length; i++) {            System.out.print(arr[i] + " ");  // 12 21 13         }        if (Objects.equals(arr3, arr)) {            System.out.println("二者相同");        }

主要注意的是:同样遵守左开右闭原则

  • int binarySearch()

查询元素值在数组中的下标(要求数组中元素已经按升序排列,所以在使用的时候记得排序

        int [] arr3={12,21,13,24};        Arrays.sort(arr3);        int bin=Arrays.binarySearch(arr3, 21);        System.out.println(bin); // 2

3、Collections

专门针对collection集合来进行的操作

  • sort(List list)

从这里可以看到尽管是Collections,但是这里做到的是对list集合的排序,而没有考虑到其他集合

        List c = new ArrayList();        c.add("l");        c.add("o");        c.add("v");        c.add("e");        System.out.println(c);        Collections.sort(c);        System.out.println(c);

但是通过这里也可以看到,我们可以指定对应的排序规则。如果需要指定,那么我们需要重写对应的Comparator接口。

那么这个来写也很简单。

看一下对应的规则即可

int compare(T o1, T o2);

1-2-------------------->升序;

2-1-------------------->降序;

  • shuffle(List<?> list)

对集合进行随机排序

        List c = new ArrayList();        c.add("l");        c.add("o");        c.add("v");        c.add("e");        System.out.println(c);  // [l, o, v, e]        Collections.shuffle(c);        System.out.println(c);  // [e, v, o, l]
  • int binarySearch(List<? extends Comparable<? super T>> list, T key)

查找指定集合中的元素,返回所查找元素的索引

        List c = new ArrayList();
        c.add("l");
        c.add("o");
        c.add("v");
        c.add("e");
        System.out.println(c);  // [l, o, v, e]
        final int o = Collections.binarySearch(c, "o");
        System.out.println("对应的下标是:"+o); // 对应的下标是:1
  • boolean replaceAll(List list, T oldVal, T newVal)

替换批定元素为某元素,若要替换的值存在刚返回true,反之返回false

        List c = new ArrayList();
        c.add("l");
        c.add("o");
        c.add("o");
        c.add("v");
        c.add("e");
        System.out.println(c);  // [l, o, v, e]
        Collections.replaceAll(c, "o", "my");
        System.out.println(c);
  • void reverse(List<?> list)

反转集合中元素的顺序

        List c = new ArrayList();        c.add("l");        c.add("o");        c.add("o");        c.add("v");        c.add("e");        System.out.println(c);  // [l, o, o, v, e]        Collections.reverse(c);        System.out.println(c);  // [e, v, o, o, l]
  • rotate(List<?> list, int distance)

集合中的元素向后移m个位置,在后面被遮盖的元素循环到前面来

        List c = new ArrayList();
        c.add("l");
        c.add("o");
        c.add("o");
        c.add("v");
        c.add("e");
        System.out.println(c);  // [l, o, o, v, e]
        Collections.rotate(c,2);
        System.out.println(c);  // [v, e, l, o, o]
  • copy(List<? super T> dest, List<? extends T> src)

将集合n中的元素全部复制到m中,并且覆盖相应索引的元素

        List m = Arrays.asList("one two three four five six siven".split(" "));
        System.out.println(m); // [one, two, three, four, five, six, siven]
        List n = Arrays.asList("我 是 复制过来的哈".split(" "));
        System.out.println(m); // [one, two, three, four, five, six, siven]
        Collections.copy(m,n);
        System.out.println(m); // [我, 是, 复制过来的哈, four, five, six, siven]
  • swap(List<?> list, int i, int j)

交换集合中指定元素索引的位置

        List m = Arrays.asList("one two three four five six siven".split(" "));
        System.out.println(m);  // [one, two, three, four, five, six, siven]
        Collections.swap(m, 2, 3);
        System.out.println(m);  // [one, two, four, three, five, six, siven]
  • fill(List<? super T> list, T obj)

用对象o替换集合list中的所有元素

        List m = Arrays.asList("one two three four five six siven".split(" "));
        System.out.println(m);  // [one, two, three, four, five, six, siven]
        Collections.fill(m, "2");
        System.out.println(m);  // [2, 2, 2, 2, 2, 2, 2]
  • 求最大值、最小值
        List<Integer> list = new ArrayList<>();
        list.add(5);
        list.add(55);
        list.add(512);
        Integer max = Collections.max(list);
        Integer min = Collections.min(list);
        System.out.println("最大值是:"+max); // 最大值是:512
        System.out.println("最小值是:"+min); // 最小值是:5

4、collection和array相互转换

        List<Integer> list = new ArrayList<>();
        list.add(5);
        list.add(55);
        list.add(512);
        Integer[] integers = list.toArray(new Integer[list.size()]);
        for (Integer integer : integers) {
            System.out.println(integer);
        }
        final List<Integer> integers1 = Arrays.asList(integers);
        System.out.println(integers1);
posted @ 2021-08-17 17:29  雩娄的木子  阅读(215)  评论(0编辑  收藏  举报