集合——Collection

本文详细介绍了Java集合框架中的Collection接口、List接口(如ArrayList、LinkedList、Vector)和Set接口(如HashSet、LinkedHashSet、TreeSet)的概念、特点和常用方法,涉及迭代器、添加、删除操作以及面试常见问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、集合介绍

集合、数组都是对多个数据进行存储操作的结构,简称 Java 容器

此时的存储主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi)

  1. 数组存储多个数据的特点:
    1. 一旦初始化,长度就确定了数组
    2. 一旦定义好,其元素的类型就确定了,只能操作指定类型的数据
    3. 比如:String[] arrint[] arrObject[] arr
  2. 数组在存储多个数据方面的缺点
    1. 一旦初始化,长度就不可修改
    2. 数组中提供的方法非常有限,对于添加、删除、插入等操作,不便,效率不高
    3. 获取数组中实际元素的个数(null为空,属于无实际元素),没有现成的属性或方法可用
    4. 数组存储数据的特点:有序、可重复。所以不能满足无需、不可重复的数据需求

集合与数组的区别:
在这里插入图片描述

Java 集合可以分为 CollectionMap 两种体系

Collection:单列数据。定义了存取一组对象的方法的集合
—>List:元素有序、可重复的集合 ——“动态”数组
--------------->ArrayList、 LinkedList、Vector
—> Set :元素无序、不可重复的集合 —— 高中的集合
--------------->HashSet、LinkedHashSet、TreeSet
Map:双列数据。保存具有映射关系 " key – value " 的集合 —— 高中的函数
—>HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

" key – value ":二者的关系相当于是 f(x) = y
多个 key 可以指向同一个 value
但是不可能有多个 value 指向同一个 key

Collection 的继承树

在这里插入图片描述

Map 的继承树

在这里插入图片描述

2、Collection常用方法

Collection的定义以ArrayList为例,创建的是有序、可重复的集合
集合Collection中方法执行后,更改的都是当前的对象,具有可变性

add(Object obj):将元素添加至当前集合中
addAll(Collection c):将集合c中的元素添加至当前集合中

    public void Test01(){
    //ArrayList创建有序的、可重复的集合
        Collection co = new ArrayList();
        //add(Object obj):添加元素至集合中
        co.add(123);
        co.add("xixi");
        co.add(new String("草莓"));
        co.add(new Person("Tom",18));
        co.add(456);
        
        //addAll(Collection c):将集合c中的元素添加至当前集合中
        Collection c1 = Arrays.asList(222,333);
        co.addAll(c1);
        System.out.println(co);  
        //[123, xixi, 草莓, Person{name='Tom', age=18}, 456, 222, 333]
        
        //size():获取集合中的元素个数
        System.out.println(co.size());    //7
    }

size():获取集合中的元素个数
isEmpty():判断当前集合的元素个数是否为 0
clear():清空当前集合的元素,而非为null

System.out.println(co.size());
co.clear();
System.out.println(co.isEmpty());

contain(Object obj):判断当前集合是否包含obj,返回一个boolean值
containAll(Collection c):判断集合c中的所有元素是否都存在当前集合中

会调用方法参数所在类的 equals() 方法

方法执行后,更改的都是当前的对象,具有可变性

    public void Test01(){
        Collection co = new ArrayList();
        co.add(123);
        co.add("xixi");
        co.add(new String("草莓"));
        co.add(new Person("Tom",18));
        co.add(456);

        //contain(Object obj):判断当前集合是否包含obj,返回一个boolean值
//        System.out.println(co.contains(new String("草莓")));           //true
        //此处未重写equals()方法,就返回false
        System.out.println(co.contains(new Person("Tom", 18)));    //重写了equals(),返回true

        //containAll(Collection c):判断集合c中的所有元素是否都存在当前集合中
        Collection c = Arrays.asList(123,4);
        System.out.println(co.containsAll(c));    //false
    }

执行结果如下
在这里插入图片描述

remove(Object obj):判断是否移除obj成功,返回一个boolean值

会调用方法参数所在类的 equals() 方法

方法执行后,更改的都是当前的对象,具有可变性

    public void Test02(){
        Collection co = new ArrayList();
        co.add(123);
        co.add("xixi");
        co.add(new String("草莓"));
        co.add(new Person("Tom",18));
        co.add(456);

        //remove(Object obj):判断是否移除obj成功(会先判断是否包含有这个obj,若没有这个成员,返回false)
        //这里还是通过集合元素中的equals()方法来判断是否有obj
        System.out.println(co.remove(new Person("Tom",18)));   //true
        System.out.println(co);                  //[123, xixi, 草莓, 456]
        System.out.println(co.remove(new String("草莓")));   //true
        System.out.println(co);                  //[123, xixi, 456]
    }

执行结果如下
在这里插入图片描述

removeAll(Collection c):判断是否将集合c中的共有的元素移除成功
retainAll(Collection c):判断是否将集合c中的共有的元素放进当前集合

会调用方法参数所在类的 equals() 方法

方法执行后,更改的都是当前的对象,具有可变性

    public void Test03(){
        Collection co = new ArrayList();
        co.add(123);
        co.add("xixi");
        co.add(new String("草莓"));
        co.add(new Person("Tom",18));
        co.add(456);

        //removeAll(Collection c):判断是否将集合c中的共有的元素移除成功
        //这里还是通过集合元素中的equals()方法来判断是否有obj
        System.out.println(co);     //[123, xixi, 草莓, Person{name='Tom', age=18}, 456]
        Collection c = Arrays.asList(111,123);
        System.out.println(co.removeAll(c));    //true
        System.out.println(co);    //[xixi, 草莓, Person{name='Tom', age=18}, 456]

        //retainAll(Collection c):判断是否将集合c中的共有的元素放进当前集合
        System.out.println(co);    //[xixi, 草莓, Person{name='Tom', age=18}, 456]
        Collection c1 = Arrays.asList(new Person("Tom",18),new Person("Jerry",17));
        System.out.println(co.retainAll(c1));    //true
        System.out.println(co);    //[Person{name='Tom', age=18}]

    }

执行结果如下
在这里插入图片描述

equals(Object obj):要想返回true,就要当前对象与参数对象的元素都相同
hashCode():返回当前对象的哈希值

System.out.println(co.equals(c1));

System.out.println(co.hashCode());

toArray(): 集合 ----> 数组
Arrays.asList(): 数组 ----> 集合

    @org.junit.Test
    public void Test04(){
        Collection co = new ArrayList();
        co.add(123);
        co.add("xixi");
        co.add(new String("草莓"));
        co.add(new Person("Tom",18));
        co.add(456);
        //转为数组
        Object[] o = co.toArray();
        //遍历数组
        for (Object o1 : o) {
            System.out.println(o1);
        }

        //数组转为集合
        Arrays.asList(new String[]{"aa","bb","cc"});
        Arrays.asList("aa","bb","cc");
    }

注意:
在数组转为集合,调用Arrays.asList()的时候,有个特殊情况需要注意

注意当向Arrays的静态方法asList()中传入int类型的一维数组时候,会有特殊情况,这时会将 int型的一维数组看成一个元素

具体情况见以下代码

    public void Test06(){
        //数组转为集合,调用Arrays的静态方法asList()
        List<String> ll = Arrays.asList("aa", "bb", "cc");

        //注意当向Arrays的静态方法asList()中传入int类型的一维数组时候,会有特殊情况
        //这种情况会将 int型的一维数组看成一个元素
        List ll1 = Arrays.asList(new int[]{1,2,3});
        System.out.println(ll1);          //[[I@2c8d66b2]
        System.out.println(ll1.size());   // 1

        //可以写成以下形式
        List ll2 = Arrays.asList(1,2,3);
        System.out.println(ll2);          //[1, 2, 3]
        System.out.println(ll2.size());   // 3

        List ll3 = Arrays.asList(new Integer[]{1,2,3});
        System.out.println(ll3);          //[1, 2, 3]
        System.out.println(ll3.size());   // 3
    }

iterator():返回Iterator接口的实例,可用于遍历集合

其实就是对于当前的集合,创建一个指针,默认游标在第一个元素之前

当集合对象每次调用iterator()方法,都会得到一个全新的从头开始的指针,默认游标在第一个元素之前

结论:
Collection接口的实现类的对象添加数据obj时,要求obj所在类要重写equals()方法

3、迭代器 Iterator

3.1、迭代器遍历

使用迭代器进行遍历集合
会用到两个方法

Iterator i = c.iterator();创建迭代器对象

i.hasNext()返回一个boolean值,判断指针下一个元素是否存在

i.next()先指针下移,再进行输出

    @org.junit.Test
    public void Test01(){
        Collection c = new ArrayList();
        c.add(123);
        c.add("xixi");
        c.add(new Person("Tom",19));
        c.add(456);

        //使用迭代器遍历集合
        //调用方法,返回迭代器对象
        Iterator i = c.iterator();
        while (i.hasNext()){
            System.out.println(i.next());
        }
        //此时迭代器的指针已经指向最后一个元素
        //再接着指针下移会报错
        //抛出异常:java.util.NoSuchElementException
        //System.out.println(i.next());
	}

也可以进行循环遍历

Iterator i = c.iterator();
for (int j = 0; j < c.size(); j++) {
	System.out.println(i.next());
}

迭代器的执行原理
在这里插入图片描述
此时注意两种错误的写法:

第一种情况
当判断i.next()方法,指针下移,拿到了第一个元素不是空,再调用i.next()方法输出了第二个元素;
再次判断i.next()方法,指针下移,拿到了第三个元素不是空,再调用i.next()方法输出了第四个元素;
再次判断i.next()方法,指针下移,拿第五个元素,没拿到,抛异常

    public void Test02(){
        Collection c = new ArrayList();
        c.add(123);
        c.add("xixi");
        c.add(new Person("Tom",19));
        c.add(456);

        //第一种
        Iterator i = c.iterator();
        while (i.next() != null){
            System.out.println(i.next());
        }
    }

执行结果如下
在这里插入图片描述
第二种情况
当调用c.iterator(),会得到一个新的迭代器指针,指向第一个元素之前
然后调用hasNext()方法,判断了第一个元素存在,执行循环体
循环体中调用c.iterator()会得到一个新的迭代器指针指向第一个元素之前
调用next()方法,指针下移拿到了第一个元素,并输出

下一步再判断,调用c.iterator(),得到一个新的迭代器指针
然后调用hasNext()方法,判断了第一个元素存在,执行循环体
循环体中调用c.iterator()会得到一个新的迭代器指针指向第一个元素之前
调用next()方法,指针下移拿到了第一个元素,并输出
。。。

成为了死循环

//第二种
while (c.iterator().hasNext()) {
	System.out.println(c.iterator().next());
}

执行结果如下
在这里插入图片描述

3.2、迭代器remove()

迭代器中的remove()方法,移除的也是当前集合 中的元素,更改的也是当前集合

    @org.junit.Test
    public void Test02(){
        Collection c = new ArrayList();
        c.add(123);
        c.add("xixi");
        c.add(new Person("Tom",19));
        c.add(456);

        Iterator i = c.iterator();
        while (i.hasNext()){
            Object o = i.next();
            if("xixi".equals(o)){
                i.remove();
            }
        }

        System.out.println(c);
    }

执行结果如下
在这里插入图片描述
注意:
如果还未调用next()方法就调用remove()方法,会抛异常
或者调用了一次remove()方法,再调用一次remove()方法,会抛异常java.lang.IllegalStateException

比如在第一步,hasNext()方法判断了第一个元素存在,但是指针还在第一个元素之前,此时调用remove()方法,没有东西可以拿来移除

比如已经移除了元素之后,再次调用remove()方法,没有东西可以拿来移除

Iterator i = c.iterator();
while (i.hasNext()){
	i.remove();
}

执行结果如下
在这里插入图片描述

3.3、新特性for- each

底层其实就是调用迭代器Iterator来实现的

//for(集合类型 自定义的局部变量:集合对象){循环体}
for (Object o : c) {
	System.out.println(o);
}

执行原理:
在后面的集合c中去一个元素,然后赋值给局部变量Object o
执行循环体,输出变量o

此处也可以遍历数组

//for(数组类型 局部变量:数组对象){循环体}
int[] i = new int[]{1,2,3,4,5};
for (int i1 : i) {
	System.out.println(i1);
}

面试题:

判断以下代码执行结果

String[] arr = new String[]{"MM","MM","MM"};
for (int i = 0; i < arr.length; i++) {
	arr[i] = "GG";
}
//遍历一下
for (String o : arr) {
	System.out.println(o);
}

执行结果如下
在这里插入图片描述

String[] arr1 = new String[]{"MM","MM","MM"};
for (String o : arr1) {
	o = "GG";
}
//遍历一下
for (String o : arr1) {
	System.out.println(o);
}

执行结果如下
在这里插入图片描述

第二种 foreach 循环使用局部变量来赋值操作,所以涉及到 String 的不可变性
第一种是直接操作数组

4、List 接口

List集合类中元素是有序的、可重复的

面试题:

指出ArrayListLinkedListVector三者的异同点:

  1. 同:三个类都是实现了List接口,存储数据的特点相同:有序的、可重复的
  2. 异:
    ArrayList:List 接口的主要实现类,jdk 1.2存在。线程不安全,效率高。底层使用Object[] elementData存储。扩容是底层扩1.5倍
    LinkedList:jdk 1.2存在。底层使用双线链表存储,对于频繁的插入、删除操作,效率更高
    Vector:List 接口的古老实现类,jdk 1.0存在。线程安全,效率低。底层使用Object[] elementData存储。扩容是底层扩2倍

比如StringBuffer等的底层使用char[] value存储

双线链表存储、数组存储 区别:

  1. 使用双线链表存储的数据方便插入、删除等,但不方便查询
  2. 使用数组存储的数据方便查询,但不方便进行插入、删除等

双线链表存储、数组存储 原理:

  1. 以下是双向链表的图片演示 在这种情况下, 若需要在第三个位置插入数据,只需要修改第二个和第四个数据的前后引用链接,无需动其他的
  2. 在数组存储数据的情况下,若想在第三个位置插入数据,就需要其后的数据都往后移动一位,给插入的数据腾空。若需要删除数据,就需要后面的数据都往前移动,填补删除的数据的空格

在这里插入图片描述
双向链表结构图解
在这里插入图片描述

4.1、ArrayList底层源码分析

在JDK 7情况下(类似于饿汉式,先造好)

ArrayList
没有赋值的时候底层创建了一个长度为10的Object数组
ArrayList aa1 = new ArrayList();——//Object[] elementData = new Object[10];
aa1.append(123); ——//elementData[0] = new Integer(123);
扩容问题:
如果要添加的数据底层数组放不下,那就需要扩容底层的数组,默认情况下,扩容为原来容量的1.5倍,同时将原有数组中元素复制到新的数组中
对于此种情况,建议使用ArrayList(int initialCapacity)创建数组。指定长度

在JDK 8情况下(类似于懒汉式,先不造)

ArrayList
没有赋值的时候底层没有创建数组,没有实例化数组(节省空间内存)
ArrayList aa1 = new ArrayList();——//Object[] elementData = {};
当第一次添加数据的时候,创建长度为10的Object数组,然后添加
aa1.append(123); ——//elementData = new Object[10]; elementData[0] = new Integer(123);
后续与JDK 7情况下一样

4.2、LinkedList底层源码分析

LinkedList
LinkedList aa1 = new LinkedList();——内部声明了Node类型的firstlast属性,默认值为null
当添加数据的时候
aa1.append(123); ——其实就是最后一个元素的后链接给了新加的元素的前空,新加的元素的前链接给了最后一个元素的后空。(双向指向)新加的元素的后空为null,新加的元素作为整个集合的最后一个元素

Node定义源码如下:体现了LinkedList的双向链表的说法

private static class Node<E> {
	E item;
	LinkedList.Node<E> next;
	LinkedList.Node<E> prev;

	Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
		this.prev = prev;
		this.item = element;
		this.next = next;
	}
}

add操作的源码如下:

void linkLast(E e) {
	LinkedList.Node<E> l = this.last;
	LinkedList.Node<E> newNode = new LinkedList.Node(l, e, (LinkedList.Node)null);
	this.last = newNode;
	if (l == null) {
		this.first = newNode;
	} else {
		l.next = newNode;
	}
	
	++this.size;
	++this.modCount;
}

4.3、Vector底层源码分析

Vector
没有赋值的时候底层创建了一个长度为10的Object数组
Vector aa1 = new Vector();——//Object[] elementData = new Object[10];
扩容问题:
默认情况下,扩容为原来容量的2倍,线程是安全的

4.4、List接口的常用方法

因为List是有序的、可重复的,底层带有索引,所以现在的方法里面包括对索引的操作

add(int i , Object obj):在索引 i 处添加插入数据 obj
addAll(int i , Collection c):在索引 i 处添加插入集合 c

ArrayList a1 = new ArrayList();
a1.add(123);
a1.add(4);
System.out.println(co.size());    //2

ArrayList a2 = new ArrayList();
a2.add(2);
a2.add(3);

//注意以下情况
//a1.addAll(a2);
//System.out.println(co.size());    //4
a1.add(a2);
System.out.println(co.size());    //3

Object get(int i):获取索引 i 处的元素
Object set(int i,Object obj):将指定索引位置的元素替换为 obj
int indexOf(Object obj):返回元素 obj 在集合中首次出现的索引,若没有返回-1
int lastindexOf(Object obj):返回元素 obj 在集合中末次出现的索引,若没有返回-1

Object remove(int i):移除指定索引 i 处的元素,并返回此元素输出。注意此方法是重载了集合中的remove方法,两个都存在,看你要用哪个
默认里面传入的是索引值

    public void Test02(){
        ArrayList a = new ArrayList();
        a.add(1);
        a.add(2);
        a.add(3);
        m(a);
        System.out.println(a);     //[1, 2]
    }
    private static void m(List l){
        l.remove(2);      //这里默认是索引
        //若想要删除的是元素,可封装成对象
        //l.remove(new Integer(2));
    }

List subList(int i,int j):返回截取指定索引位置的集合,左闭右开

总结常用方法:
增:add(Object obj)
删:Object remove(int i)boolean remove(Object obj)
改:Object set(int i,Object obj)
查:Object get(int i)
插:add(int i , Object obj)
长度:size()
遍历:迭代器、for-each循环、普通循环

普通循环遍历

for (int i = 0; i < aa1.size(); i++) {
	System.out.println(aa1.get(i));
}

5、Set 接口

Set集合类中元素是无序的、不可重复的

  1. HashSet:Set 接口的主要实现类。线程不安全,效率高。可以存储 null 值(add方法可以添加null值)
  2. LinkedHashSet:是 HashSet 的子类。
    在遍历元素的时候,可以按照添加的顺序来遍历输出。
    对于频繁的遍历操作,LinkedHashSet效率高于HashSet
  3. TreeSet:可以按照添加的对象的指定属性,来排序。不可以存储null值

Set接口中没有定义新的方法,能使用的方法只有Collection中的方法

结论:
Set中添加数据时,要求其所在类要重写hashSet()方法和equals()方法
重写的hashSet()方法和equals()方法要尽可能保持一致,相等的对象必须有相等的散列码(无序性就是按照散列码找位置)
.
小技巧:对象中作equals()方法比较的属性,都应该来在hashSet()方法中参与计算哈希值

5.1、HashSet

理解Set集合类中的无序和不可重复,以HashSet为例

1. 无序性:

不等于随机性。不管遍历多少次,都会是按照一个顺序输出的
而这个顺序就是根据元素的哈希值决定的。

根据哈希值通过散列排序得到散列码,散列码就是对应数组的位置,所以元素的位置是根据哈希值决定的

public class Test {
    @org.junit.Test
    public void Test01() {
        HashSet h = new HashSet();
        h.add(123);
        h.add("xixi");
        h.add(888);
        h.add(new Student("Tom",18));   //注意需要重写toString()方法

        //遍历
        Iterator iterator = h.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}
class Student{
    private String name;
    private int age;

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

执行结果如下
在这里插入图片描述

2. 不可重复性:

先根据元素的哈希值判断是否重复,若哈希值一样,再根据元素所在类的equals()方法判断是否重复

所以建议在给HashSet添加数据obj的时候,要求obj所在类要重写hashCode()方法和equals()方法

Collection接口的实现类的对象添加数据obj时,要求obj所在类要重写equals()方法

在没有重写hashCode()方法之前,添加两个new Student("Tom",18),遍历输出也会是两个,因为父类Object中的hashCode()方法就是生成随机数,所以每个对象数据的哈希值都不同

代码如下

public class Test {
    @org.junit.Test
    public void Test01() {
        HashSet h = new HashSet();
        h.add(123);
        h.add("xixi");
        h.add(888);
        h.add(new Student("Tom",18));   //注意需要重写toString()方法
        h.add(new Student("Tom",18));

        //遍历
        Iterator iterator = h.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

执行结果如下
在这里插入图片描述

3. 添加元素的过程:

元素的位置是由数据的哈希值决定的,是根据数据obj的哈希值通过计算得出位置在哪里

我们向HashSet中添加元素a,首先调用a所在类的hashCode()方法,计算元素a的哈希值,此哈希值通过某种计算得到HashSet底层数组的存放位置(即为索引位置),判断数组此位置上是否已经有元素
——若位置上没有元素,则元素a添加成功 -------> 情况①
——若位置上有其他元素b(或以链表形式存在多个元素),则比较元素a、b的哈希值
————若哈希值不同,则元素a添加成功 -------> 情况②
————若哈希值相同,则需要调用元素a所在类的equals()方法
————————equals()方法返回true,则属于重复元素,添加失败
————————equals()方法返回false,则元素a添加成功 -------> 情况③

注意:

对于情况②和情况③,元素a 与 已经存在指定位置上的数据以链表的方式存储
JDK 7 中,元素a放入数组中,指向原来的数据
JDK 8 中,原来的数据放在数组中,指向元素a
总结:七上八下

5.2、LinkedHashSet

HashSet的子类,所以存储数据与HashSet区别不大,也是无序的、不可重复的。

区别不过是在添加元素的时候,在原有HashSet的基础上添加了链表,每个元素有两个引用,记录此元素的前一个数据和后一个数据

优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet

5.3、TreeSet

  1. 可以按照添加的对象的指定属性,来排序
  2. TreeSet中添加数据,要求是相同类的对象
  3. TreeSetTreeMap采用红黑树的存储结构,是有序的,查询速度比List快
  4. 注意:当TreeSet调用remove之类的方法时,不是用equals来判断相同元素,而还是用Compare比较

以基本数据类型 int 为例
以下情况会抛异常:java.lang.ClassCastException

class java.lang.Integer cannot be cast to class java.lang.String

//    @org.junit.Test
//    public void Test02(){
//        TreeSet t = new TreeSet();
//        t.add(1);
//        t.add(2);
//        t.add("xixi");
//    }

对于自定义类而言,若TreeSet中数据都是自定义类Student,则类Student中需要实现Comparable接口,重写compareTo(Object obj)方法,否则报错。
如下所示,抛异常:java.lang.ClassCastException

class Student cannot be cast to class java.lang.Comparable

    @org.junit.Test
    public void Test02(){
        TreeSet t = new TreeSet();
        t.add(new Student("Jerry",17));
        t.add(new Student("Tom",20));
        t.add(new Student("Ammiy",15));
        t.add(new Student("Cancy",80));
        t.add(new Student("Ben",25));
    }

!在这里插入图片描述
对于TreeSet有两种排序方式:

  1. 自然排序
    1. 实现Comparable接口,重写compareTo(Object o)方法
    2. 自然排序中,比较两个对象是否重复的标准:compareTo()返回0,(不再是根据equals()方法)
  2. 定制排序
    1. 定义Comparator对象,作为参数传入TreeSet有参构造器
    2. 比较两个对象是否重复的标准:compareTo()返回0,(不再是根据equals()方法)

自然排序:

Student中重写compareTo(Object o)方法,按照姓名从小到大排序

    @Override
    public int compareTo(Object o) {
        if(o instanceof Student){
            Student s = (Student)o;
            return this.name.compareTo(s.name);
        }
        throw new RuntimeException("传入数据类型不一致");
    }

然后遍历输出

    @org.junit.Test
    public void Test02(){
        TreeSet t = new TreeSet();
        t.add(new Student("Jerry",17));
        t.add(new Student("Tom",20));
        t.add(new Student("Ammiy",15));
        t.add(new Student("Cancy",80));
        t.add(new Student("Ben",25));

        Iterator i = t.iterator();
        while (i.hasNext()){
            System.out.println(i.next());
        }
    }

执行结果如下
在这里插入图片描述
注意:

TreeSet中体现不可重复性是根据参与排序方法的元素的重复决定的

比如在以下代码中,传入参数

    public void Test02(){
        TreeSet t = new TreeSet();
        t.add(new Student("Jerry",17));
        t.add(new Student("Tom",20));
        t.add(new Student("Ammiy",15));
        t.add(new Student("Cancy",80));
        t.add(new Student("Ben",25));
        t.add(new Student("Cancy",99999));
        
        Iterator i = t.iterator();
        while (i.hasNext()){
            System.out.println(i.next());
        }
    }

new Student("Cancy",99999)new Student("Cancy",80)只有一个元素name相同,同时,也只有这一个元素name参与了compareTo(Object o)方法的重写

    @Override
    public int compareTo(Object o) {
        if(o instanceof Student){
            Student s = (Student)o;
            return this.name.compareTo(s.name);
        }
        throw new RuntimeException("传入数据类型不一致");
    }

遍历输出结果如下
在这里插入图片描述

而假如此时两个元素都参与了compareTo(Object o)方法的重写,则会根据两个元素是否重复来决定不可重复性

    //先按照姓名从小到大排序,再按照年龄从小到大排序
    @Override
    public int compareTo(Object o) {
        if(o instanceof Student){
            Student s = (Student)o;
            int i = this.name.compareTo(s.name);
            if (i == 0){
                return Integer.compare(this.age,s.age);
            }
            return i;
        }
        throw new RuntimeException("传入数据类型不一致");
    }

遍历输出结果如下
在这里插入图片描述

定制排序:

在这种排序下,传入的数据所在类也可以不实现Comparable接口和重写compareTo(Object o)方法。这样也不会报错

假如实现Comparable接口和重写compareTo(Object o)方法。也只会根据定制排序来实现排序

比如以下代码中,Student类中未实现Comparable接口,直接使用定制排序

    @org.junit.Test
    public void Test03(){
        Comparator c = new Comparator() {
            //按照名字从大到小排序
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof Student && o2 instanceof Student){
                    Student s1 = (Student)o1;
                    Student s2 = (Student)o2;
                    return -s1.getName().compareTo(s2.getName());
                }
                throw new RuntimeException("传入数据类型不一致");
            }
        };
        TreeSet t = new TreeSet(c);
        t.add(new Student("Jerry",17));
        t.add(new Student("Tom",20));
        t.add(new Student("Ammiy",15));
        t.add(new Student("Cancy",80));
        t.add(new Student("Ben",25));
        Iterator i = t.iterator();
        while (i.hasNext()){
            System.out.println(i.next());
        }
    }

执行结果如下
在这里插入图片描述

5.4、Set接口面试题

练习:
List内取出重复的数字值

操作:将ArrayList集合放入HashSet集合里面

public static List dd(List l){
	HashSet h = new HashSet();
	h.addAll(l);
	return new ArrayList(h);
}
public void Test01(){
	List aa = new ArrayList();
	aa.add(new Integer(1));
	aa.add(new Integer(1));
	aa.add(new Integer(2));
	aa.add(new Integer(2));
	aa.add(new Integer(3));
	aa.add(new Integer(3));
	List aa1 = dd(aa);
	for (Object o : aa1) {
		System.out.println(o);
	}
}

问题二:

问以下程序的输出结果(类Person已经重写了hashSet()方法和equals()方法)

public void Test01(){
	HashSet h = new HashSet();
	Person p1 = new Person("AA",18);
	Person p2 = new Person("BB",20);
	
	h.add(p1);
	h.add(p2);
	
	p1.name("CC");
	System.out.println(h);
	
	h.remove(p1);
	System.out.println(h);
	
	h.add(new Person("CC",18));
	System.out.println(h);
	
	h.add(new Person("AA",18));
	System.out.println(h);
}

在执行h.add(p1);h.add(p2);的时候,此时HashSet集合中就根据二者的属性计算出,得出位置1A、2B

然后p1.name("CC");,输出的结果就是"CC",18、“BB”,20

执行h.remove(p1);,因为HashSet集合是根据哈希值找位置,然后来找元素。所以根据此时的p1,即"CC",18计算得到位置1C,此时发现这个位置上没有元素,就删除失败。所以输出的结果还是"CC",18(1A位置)、“BB”,20(1B位置)

执行h.add(new Person("CC",18)),此时HashSet集合中就根据属性计算出,得出位置1C,这个位置是空的,就直接在这个位置放数据。所以输出的结果是"CC",18(1A位置)、“BB”,20(1B位置)、“CC”,18(1C位置)

执行h.add(new Person("AA",18));,此时HashSet集合中就根据属性计算出,得出位置1A,这个位置有数据"CC",18(1A位置),然后比较二者的哈希值,发现哈希值不同,就以链表的方式将两个数据放在一个位置上。所以输出的结果是"CC",18(1A位置)、“AA”,18(1A位置)、“BB”,20(1B位置)、“CC”,18(1C位置)

6、小结

  1. 集合大家庭简介
    1. Collection:单列数据
      1. List:元素有序、可重复的集合
        ArrayListLinkedListVector
      2. Set :元素无序、不可重复的集合
        HashSetLinkedHashSetTreeSet
    2. Map:双列数据
      HashMapLinkedHashMapTreeMapHashtableProperties
  2. Collection常用方法
    修改的都是当前集合本身
    建议添加的元素所在类都重写equals()方法
    1. add(Object obj):将元素添加至当前集合中
    2. addAll(Collection c):将集合c中的元素添加至当前集合中
    3. size():获取集合中的元素个数
    4. isEmpty():判断当前集合的元素个数是否为 0
    5. clear():清空当前集合的元素,而非为null
    6. contain(Object obj):判断当前集合是否包含obj,返回一个boolean值
    7. containAll(Collection c):判断集合c中的所有元素是否都存在当前集合中
    8. remove(Object obj):判断是否移除obj成功,返回一个boolean值
    9. removeAll(Collection c):判断是否将集合c中的共有的元素移除成功
    10. retainAll(Collection c):判断是否将集合c中的共有的元素放进当前集
    11. equals(Object obj):要想返回true,就要当前对象与参数对象的元素都相同
    12. hashCode():返回当前对象的哈希值
    13. toArray(): 集合 ----> 数组
    14. Arrays.asList(): 数组 ----> 集合
    15. iterator():返回Iterator接口的实例,可用于遍历集合
  3. 迭代器
    1. i.hasNext()返回一个boolean值,判断指针下一个元素是否存在
    2. i.next()先指针下移,再进行输出
    3. i.remove()迭代器中的remove()方法,移除的也是当前集合 中的元素,更改的也是当前集合
    4. for(集合类型 自定义的局部变量:集合对象){循环体}
    5. 注意:
      当集合对象每次调用iterator()方法,都会得到一个全新的从头开始的指针,默认游标在第一个元素之前
  4. List接口
    1. ArrayListLinkedListVector三者的异同点
      1. 同:三个类都是实现了List接口,存储数据的特点相同:有序的、可重复的
      2. 异:
        ArrayList:List 接口的主要实现类,jdk 1.2存在。线程不安全,效率高。底层使用Object[] elementData存储(扩容1.5倍)
        LinkedList:jdk 1.2存在。底层使用双线链表存储,对于频繁的插入、删除操作,效率更高
        Vector:List 接口的古老实现类,jdk 1.0存在。线程安全,效率低。底层使用Object[] elementData存储(扩容2倍)
    2. 常用方法
      1. add(int i , Object obj):在索引 i 处添加插入数据 obj
      2. addAll(int i , Collection c):在索引 i 处添加插入集合 c
      3. Object get(int i):获取索引 i 处的元素
      4. Object set(int i,Object obj):将指定索引位置的元素替换为 obj
      5. int indexOf(Object obj):返回元素 obj 在集合中首次出现的索引,若没有返回-1
      6. int lastindexOf(Object obj):返回元素 obj 在集合中末次出现的索引,若没有返回-1
      7. Object remove(int i):移除指定索引 i 处的元素,并返回此元素输出。默认里面传入的是索引值
      8. List subList(int i,int j):返回截取指定索引位置的集合,左闭右开
  5. Set接口
    1. HashSet
      ① Set 接口的主要实现类。线程不安全,效率高
      ② 可以存储 null 值
      ③ 元素所在类需要重写hashSet()方法和equals()方法
    2. LinkedHashSet
      ① 是 HashSet的子类
      ② 在遍历元素的时候,可以按照添加的顺序来遍历输出。
      ③ 对于频繁的遍历操作,LinkedHashSet效率高于HashSet
      ④ 元素所在类需要重写hashSet()方法和equals()方法
    3. TreeSet
      ① 只可以放同一个类型的对象
      ② 采用红黑树的存储结构,是有序的,查询速度比List快
      ③ 比较两个对象是否重复的标准:compareTo()返回0,(不再是根据equals()方法)
      ④ 元素所在类需要有 Java 比较器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值