十四、集合(Collection、Map、List、Set)

1 集合的概念

集合的作用是用来存储数据。

集合的分类:
在这里插入图片描述

之前学习的数组也是用来存储数据的,可以对比学习:

1.1 数组的优缺点

  1. 优点

    1. 可以存储多个同类型的元素
    2. 存储地址连续
  2. 缺点

    1. 初始化后长度不可变
    2. 只能存储同种类型的数据
    3. 数组存储的元素时有限的

从操作的角度来说,数组的添加和删除效率低,查找的效率比较高(通过索引来获取元素)。

1.2 集合的特点

  1. 长度不限
  2. 只可以存储对象
  3. 元素可以是有序或无序
  4. 元素可以是单列也可以是由映射关系的双列
  5. 可以对存储在其中的元素进行比较

2 Collection(接口)

Collection 是集合的根接口,集合表示一组被称为其元素的对象。一些集合允许重复元素,而其他集合不允许。

  1. Collection 的常用方法:
返回值类型方法
booleanadd(Object e) 添加元素
voidclear() 清空集合
booleancontains(Object o) 如果此集合包含指定的元素,则返回 true 。
booleanisEmpty() 如果此集合不包含元素,则返回 true 。
booleanremove(Object o) 从该集合中删除指定元素的单个实例(如果存在)
booleanretainAll(Collection c) 仅保留此集合中包含在指定集合中的元素
intsize() 返回此集合中的元素数。
Object[]toArray() 返回一个包含此集合中所有元素的数组。

实例:

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;

public class Test1 {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        //添加元素
        c.add(123);
        c.add('I');
        c.add("Love");
        c.add("java");
        System.out.println("是否包含元素Love:" + c.contains("Love"));
        System.out.println("判断集合是否为空:" + c.isEmpty());
        //返回包含此集合中所有元素的数组
        Object[] obj = c.toArray();
        System.out.println("集合中元素:");
        //遍历数组中元素
        for(int i = 0 ; i < c.size() ; i++){
            System.out.println(obj[i]);
        }
        System.out.println("-----------------");

        System.out.println("删除元素123 : " + c.remove(123));
        System.out.println("删除后集合中的元素数:" + c.size());
        Object[] obj1 = c.toArray();
        System.out.println("删除后集合中的元素:");
        for(int i = 0 ; i < c.size() ;i++){
            System.out.println(obj1[i]);
        }

        System.out.println("-----------------");
        System.out.println("清空前集合中元素分数:" + c.size());
        //清空集合
        c.clear();
        System.out.println("清空后集合中元素个数:" + c.size());
        System.out.println("判断集合是否为空:" + c.isEmpty());
    }
}

运行结果:

是否包含元素Love:true
判断集合是否为空:false
集合中元素:
123
I
Love
java
-----------------
删除元素123 : true
删除后集合中的元素数:3
删除后集合中的元素:
I
Love
java
-----------------
清空前集合中元素分数:3
清空后集合中元素个数:0
判断集合是否为空:true
  1. Collection 的高级方法
返回值类型方法
booleanaddAll(Collection c) 将指定集合中的所有元素添加到此集合。
booleancontainsAll(Collection c) 如果此集合包含指定 集合中的所有元素,则返回true。
booleanremoveAll(Collection<?> c) 删除指定集合中包含的所有此集合的元素。
booleanretainAll(Collection<?> c) 仅保留此集合中包含在指定集合中的元素。

实例:

import java.util.ArrayList;
import java.util.Collection;

public class test2{
    public static void main(String[] args) {
        Collection c1 = new ArrayList();
        Collection c2 = new ArrayList();
        c1.add(1);
        c1.add(2);
        c1.add("demo");
        c2.add(1);
        c2.add(2);
        c2.add("java");
        //判断集合c2中是否包含c1中的所有元素
        boolean a = c2.containsAll(c1);
        System.out.println("集合c2中是否包含c1中的所有元素:" + a);
        //将c2中的元素添加到c1中,c2中的元素不受影响
        c1.addAll(c2);
        System.out.println("c2添加到c1,集合c1中的元素:");
        Object[] obj = c1.toArray();
        for(int i = 0 ; i < c1.size() ; i++){
            System.out.println(obj[i]);
        }
        boolean b = c1.removeAll(c2);
        System.out.println("从c1中删除包含c2的元素,集合c1中的元素:");
        Object[] obj1 = c1.toArray();
        for(int i = 0 ; i < c1.size() ; i++){
            System.out.println(obj1[i]);
        }
        //c1重新赋值
        c1.add(1);
        c1.add(2);
        c1.add("demo");
        boolean c = c1.retainAll(c2);
        System.out.println("仅保留c1与c2中都有的元素,集合c1中的元素:");
        Object[] obj2 = c1.toArray();
        for(int i = 0 ; i < c1.size() ; i++){
            System.out.println(obj2[i]);
        }
    }
}

运行结果:

集合c2中是否包含c1中的所有元素:false
c2添加到c1,集合c1中的元素:
1
2
demo
1
2
java
从c1中删除包含c2的元素,集合c1中的元素:
demo
仅保留c1与c2中都有的元素,集合c1中的元素:
1
2
  1. 迭代器
返回值类型方法
Iteratoriterator() 返回此集合中的元素的迭代器。指的是对集合元素的遍历
booleanhasNext() 如果迭代具有更多元素,则返回 true 。
Enext() 返回迭代中的下一个元素。
default voidremove() 从底层集合中删除此迭代器返回的最后一个元素(可选操作)。

实现:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class test4 {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        c.add("java");
        c.add("spring");
        c.add("super");
        //使用while循环迭代
        Iterator iter = c.iterator();
        while(iter.hasNext()){
            Object obj = iter.next();
            System.out.println(obj);
        }
    }
}

运行结果:

java
spring
super
注:在while循环中,不能通过集合对象修改集合的元素

迭代器图示:
在这里插入图片描述

  • 使用Collection保存自定义对象

实现:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class StudentsDemo {
    public static void main(String[] args) {
        Collection collection = new ArrayList();
        Student st1 = new Student("小明",18);
        Student st2 = new Student("小红",20);
        collection.add(st1);
        collection.add(st2);
        Iterator iter = collection.iterator();
        while(iter.hasNext()){
            Object obj = iter.next();
            System.out.println(obj);
        }
    }
}

运行结果:

Student{name='小明', age=18}
Student{name='小红', age=20}

3 List(有序集合)

有序集合也称为序列,该界面的用户可以精确控制列表中每个元素的插入位置。用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。

3.1 List 的特点

  1. 元素是有序的
  2. 列表通常允许重复的元素

3.2 List 方法

返回值类型方法
voidadd(int index, E element) 将指定的元素插入此列表中的指定位置(可选操作)。
booleanaddAll(int index, Collection<? extends E> c) 将指定集合中的所有元素插入到此列表中的指定位置(可选操作)。
Eget(int index) 返回此列表中指定位置的元素。
intindexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
intlastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
ListIterator< E >listIterator() 返回列表中的列表迭代器(按适当的顺序)。
ListIterator< E >listIterator(int index) 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。
ListIterator< E >listIterator() 返回列表中的列表迭代器(按适当的顺序)。
ListIterator< E >listIterator(int index) 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。
Eset(int index, E element) 用指定的元素(可选操作)替换此列表中指定位置的元素。
List< E >subList(int fromIndex, int toIndex) 返回此列表中指定的 fromIndex (含)和 toIndex之间的视图。

3.3 实例

实例一:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class ListDemo {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(6);
        list.add(null);  //可存储null
        list.add("java");
        list.add(6);  //元素可重复
        list.add(2,"test");  //在2号位置添加元素
        list.set(1,'I');  //修改指定位置的元素
        System.out.println("list中的元素:");
        for(int i = 0 ; i < list.size() ; i++){
            System.out.println(list.get(i));  //get(i)返回此列表中指定位置的元素
        }
        System.out.println("元素6第一次出现的位置:" + list.indexOf(6));
        System.out.println("元素6最后一次出现的位置:" + list.lastIndexOf(6));
        //输出指定位置片段的元素
        List list1 = list.subList(1,5);   //区间为左闭右开
        //迭代器迭代
        Iterator iter = list1.iterator();
        System.out.println("-----截取片段输出-----");
        while(iter.hasNext()){
            Object obj = iter.next();
            System.out.println(obj);
        }

        ListIterator listIterator = list.listIterator();
        System.out.println("-----正序输出-----");
        while(listIterator.hasNext()){
            Object obj = listIterator.next();
            System.out.println(obj);
        }
        
        System.out.println("-----倒序输出-----");
        while(listIterator.hasPrevious()){
            Object obj = listIterator.previous();
            System.out.println(obj);
        }
    }
}

运行结果:

list中的元素:
6
I
test
java
6
元素6第一次出现的位置:0
元素6最后一次出现的位置:4
-----截取片段输出-----
I
test
java
6
-----正序输出-----
6
I
test
java
6
-----倒序输出-----
6
java
test
I
6
ListIteratorlistIterator() 返回列表中的列表迭代器(按适当的顺序)。

listIterator方法可以按适当的顺序迭代集合中的元素,就是说可以逆序输出。但是在逆序迭代前要先正序迭代,使指针位于最后一个元素。

实例二:

import java.util.ArrayList;
import java.util.List;

public class ListDemo1 {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("java");
        list.add("Spring");
        list.add("Summer");
        list.add("Day");
        for(int i = 0 ; i < list.size() ; i++){
            if(i == 2){
                list.add("Hello");
                list.set(2 , "World");
            }
            System.out.println(list.get(i));
        }
    }
}

运行结果:

java
Spring
World
Day
Hello

使用普通for循环,可以在循环过程中修改增加元素,所以可用普通for循环解决并发修改异常;同时也能说明增强for循环的底层是迭代器。

3.4 ArrayList (底层实现:数组)

可调整大小的数组的实现List接口。 实现所有可选列表操作,并允许所有元素,包括null 。
除了实现List 接口之外,该类还提供了一些方法来操纵内部使用的存储列表的数组的大小。 (这个类大致相当于Vector,不同之处在于它是不同步的)。

是非线程安全的

源码分析:

  1. 数组默认容量为10
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;  //数组默认容量为10
  1. 容量增长方式
private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);   //容量
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }
  1. 添加方式
private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)  //判断数组的长度是否等于元素个数
            elementData = grow();  //进行扩容
        elementData[s] = e;  //将元素存入数组
        size = s + 1;  //修改size的值
    }

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
  1. 扩容机制
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }  //使用数组的工具类中提供的数组拷贝的方法

    private Object[] grow() {
        return grow(size + 1);
    }

    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        //扩容方法
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);  //新数组的长度=old + old/2
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

实例(遍历的三种方式):

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ArrayListDemo {
    public static void main(String[] args) {
        List list = new ArrayList();
        Student st1 = new Student("小明" , 18);
        Student st2 = new Student("小红" , 20);
        Student st3 = new Student("小黑" , 19);
        list.add(st1);
        list.add(st2);
        list.add(st3);
        //for循环遍历
        System.out.println("普通for循环:");
        for(int i = 0 ; i < list.size() ; i++){
            Object obj = list.get(i);
            System.out.println(obj);
        }
        //增强for循环
        System.out.println("增强for循环:");
        for(Object obj : list){
            System.out.println(obj);
        }
        //使用迭代器
        System.out.println("迭代器迭代:");
        Iterator iter = list.iterator();
        while(iter.hasNext()){
            Object obj = iter.next();
            System.out.println(obj);
        }
    }
}

3.5 Vector

Vector类实现了可扩展的对象数组,它是同步的

是线程安全的

源码分析:

Vector的默认容量为10

    public Vector() {
        this(10);
    }

3.6 LinkedList (底层实现:双向链表)

双链表实现,并允许所有元素(包括null ) ,此实现不同步

源码分析:

  1. 底层是双向链表
    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
  1. 添加元素
    public void addFirst(E e) {
        linkFirst(e);
    }  //头插法

    /**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #add}.
     *
     * @param e the element to add
     */
    public void addLast(E e) {
        linkLast(e);
    }  //尾插法
  1. 头插法的实现:
    private void linkFirst(E e) {
        final Node<E> f = first;  //获取原链表的头节点
        final Node<E> newNode = new Node<>(null, e, f);  //构建一个新的头节点
        first = newNode;  //指定新的头节点为新节点
        if (f == null)  //判读原来链表是否为空
            last = newNode;  //此时的链表中只有一个节点
        else
            f.prev = newNode;  //让原来的头节点的前驱指向新节点
        size++;  //链表元素个数加1
        modCount++;
    }
  1. 尾插法的实现:
    void linkLast(E e) {
        final Node<E> l = last;  //获取尾节点的地址
        final Node<E> newNode = new Node<>(l, e, null);  //构建一个新的节点
        last = newNode;  //让原来的尾节点指向新节点,新节点称为新的尾节点
        if (l == null)
            first = newNode;  //此时链表为空,让新节点成为头节点
        else
            l.next = newNode;  //让新节点成为原尾节点的后继节点
        size++;
        modCount++;
    }

默认采用尾插法:

import java.util.*;

public class ArrayListDemo {
    public static void main(String[] args) {
        LinkedList list = new LinkedList();
        Student st1 = new Student("小明" , 18);
        Student st2 = new Student("小红" , 20);
        Student st3 = new Student("小黑" , 19);
        list.add(st1);
        list.add(st2);
        list.add(st3);
        //使用迭代器
        System.out.println("迭代器迭代:");
        Iterator iter = list.iterator();
        while(iter.hasNext()){
            Object obj = iter.next();
            System.out.println(obj);
        }
        System.out.println("---------------");
        Object first = list.getFirst();  //获取头节点
        System.out.println(first);
        Object last = list.getLast();  //获取尾节点
        System.out.println(last);
    }
}

3.7 Vector、ArrayList与LinkedList的区别

  1. Vector 和 ArrayList 的使用场景
  • 在多线程的环境中使用Vector
  • 在不需要保证数据同步的时候,优先使用ArrayList
  • 因为Vector的效率比ArrayList低,在开发中一般都使用ArrayList
  1. ArrayList 和 LinkedList 的使用场景
  • 如果数据查询的频率高, 而删除和插入的频率低,则使用ArrayList,ArrayList 的内部实现是基于基础的对象数组的,因此,使用get方法访问列表中的任意一个元素时,速度比LinkedList快;LinkedList 中的 get 方法是按照顺序从列表的一端开始检查,直到另一端。
  • 如果查询的频率低,而删除和插入的频率高,则使用LinkedList

ArrayList 使用一个内置的数组来存储元素,初始容量为10 ,按1.5倍增长;它的空间浪费主要体现在 list 列表的结尾预留一定的容量空间。

对 ArrayList 与 LinkedList 而言,在列表末尾增加一个元素的开销是固定的;对ArrayList 而言,在内部数组中添加一项,指向所添加的元素,偶尔可能会导致对数组进行重新分配;而对 LinkedList 而言,开销的统一的,都是分配一个内部 Entry 对象

4 Set 集合的典型实现

Set接口的常用方法:使用同Collection
Set接口的常见实现:HashSet TreeSet LinkedHashSet

4.1 Set

  1. 特点:
  • 元素是不能重复的 (只能有一个null,判断元素是否重复的标准:equals)
  • 元素是无序的
  1. 实例(无序性)
import java.util.HashSet;
import java.util.Set;

public class ArrayListDemo {
    public static void main(String[] args) {
        Set set = new HashSet();
        set.add("Java");
        set.add("is");
        set.add("a");
        set.add("language");
        for(Object obj : set){
            System.out.println(obj);
        }
    }
}
Java
a
is
language

4.2 HashSet (使用hash表实现)

  1. 特点

    1. 底层数据结构的实现:哈希表
    2. 线程不同步
    3. 不保证元素的迭代顺序和存入顺序一致
    4. 元素不能重复(hashCode与equals)
  2. 哈希值:是jdk根据对象的地址或者字符串或数字计算得出的 int 类型的数值。

获取对象的哈希值的方法:Object 提供

哈希值的特点:

  • 同一对象多次获取的 hashCode 值总是相同的
  • 不同对象的 hashCode 值肯定是不同的
  • 在自定义类中,需要重写 hashCode 方法,来保证每一个对象的 hash 值是独有的
  1. HashCode 的底层是借助于 HashMap(hash表) 实现
    HashCode 的初始容量是16,默认的负载因子是0.75

源码:

    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
  1. HashSet 是如何保证元素是不可重复的

在这里插入图片描述

  • 存储在hashSet 中的对象,在hashSet 中具体的存储位置是由对象的 hashSet 值计算所得
  • 判断元素是否重复:hashSet 值相同且 equals 方法返回 true , 此时才会认定这两个对象重复

图示:

在这里插入图片描述

  1. 实例
import java.util.*;

public class ArrayListDemo {
    public static void main(String[] args) {
        Student st1 = new Student("小明" , 18);
        Student st2 = new Student("小红" , 20);
        Student st3 = new Student("小黑" , 19);
        System.out.println(st1.hashCode());
        System.out.println(st2.hashCode());
        System.out.println(st3.hashCode());
        System.out.println("Hello".hashCode());
        System.out.println("World".hashCode());
        //特殊情况,有可能不同的对象返回相同的hashCode值
        System.out.println("重地 = " + "重地".hashCode());
        System.out.println("通话 = " + "通话".hashCode());
        //不同对象的equals返回false,但是它们的hashCode值可能相同
        System.out.println("重地".equals("通话"));
    }
}

运行结果:

23458772
23653826
23909234
69609650
83766130
重地 = 1179395
通话 = 1179395
false
  1. 实例

需求:获取10个1~20的随机数,要求随机数不能重复

思路:hashSet中的元素不能重复,所以可以控制其size来实现

实现:

import java.util.HashSet;
import java.util.Random;
import java.util.Set;
//获取10个1~20的随机数,随机数不能重复
public class SetDemo_1 {
    public static void main(String[] args) {
        //使用set集合
        Set set = new HashSet();
        Random random = new Random();
        while(set.size() < 10){  //判断集合中元素个数
            int randnumber = random.nextInt(20);
            //将随机数保存到集合中
            set.add(randnumber);
        }
        for(Object obj : set){
            System.out.print(obj + " ");
        }
    }
}

运行结果:

1 2 18 3 5 7 8 9 10 11 

4.3 TreeSet(基于TreeMap实现)

TreeSet 中的元素是有序的。此时的有序是指元素的顺序是字典顺序,元素不可重复。

实现:

public class TreeSetDemo_1 {
    public static void main(String[] args) {
        TreeSet set = new TreeSet();
        set.add(20);
        set.add(50);
        set.add(10);
        set.add(80);
        set.add(30);
        for(Object obj : set){
            System.out.println(obj);
        }
    }
}

运行结果:

10
20
30
50
80

TreeSet 存储自定义对象

4.3.1 自然排序

  • 自定义对象需要实现 Comparable 接口,此处体现的是接口的多态
  • 凡是存入到 TreeSet 集合中的对象,都需要实现排序接口
  • Comparable 接口,并实现其中的 compareTo() 方法。
TreeSet()构造一个新的,空的树组,根据其元素的自然排序进行排序。

实例:

学生的属性有姓名与年龄,将姓名与年龄按以下要求排序:

如果字符串是英文,则是按照字典顺序进行排序。

如果是中文,则是按照中文的UniCode码进行排序。

在排序中,如果是中文,如果第一个字相同,则按照第二个字进行排序。

如果是英文,如果第一个字母相同,则按照第二个字母排序。

(1)按年龄排序

实现:
public class Student implements Comparable{
    private String name;
    private int age;

    public Student() {
    }

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

    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;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

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

    @Override
    public int compareTo(Object o) {
        Student st = (Student)o; //强制类型转换
        int age1 = this.getAge();
        int age2 = st.getAge();
        return age1 - age2;
    }
}
public class ComparableDemo {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet();
        Student st1 = new Student("Jone" , 21);
        Student st2 = new Student("Amy" , 20);
        Student st3 = new Student("Mike" , 18);
        Student st4 = new Student("Yom" , 19);
        treeSet.add(st1);
        treeSet.add(st2);
        treeSet.add(st3);
        treeSet.add(st4);
        //增强for循环
        for(Object obj : treeSet){
            System.out.println(obj);
        }
    }
}
运行结果:
Student{name='Mike', age=18}
Student{name='Yom', age=19}
Student{name='Amy', age=20}
Student{name='Jone', age=21}

结果可见为按年龄排序

(2)按姓名排序

实现
//其余代码与上述按姓名排序代码相同
@Override
    public int compareTo(Object o) {
        Student st = (Student)o; //强制类型转换
        String name1 = this.getName();
        String name2 = st.getName();
        return name1.compareTo(name2);
    }
运行结果:
Student{name='Amy', age=20}
Student{name='Jone', age=21}
Student{name='Mike', age=18}
Student{name='Yom', age=19}

(3)优先按照年龄排序,如果年龄相同,则按照姓名排序。

实现
import java.util.Objects;

public class Student implements Comparable{
    private String name;
    private Integer  age;  //int中没有compareTo方法,所以age的类型要改为Integer

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name) &&
                Objects.equals(age, student.age);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

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

    //优先按照年龄排序,如果年龄相同,则按照姓名排序。
    @Override
    public int compareTo(Object o) {
        Student st = (Student) o;  //强制类型转换
        int a = this.getAge().compareTo(st.getAge());  //如果两个人的年龄相同,则输出0
        int n = a == 0 ? this.getName().compareTo(st.getName()) : a;
        return n;
    }
}
import java.util.TreeSet;

public class ComparableDemo {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet();
        Student st1 = new Student("Jone" , 20);
        Student st2 = new Student("Amy" , 20);
        Student st3 = new Student("Mike" , 18);
        Student st4 = new Student("Yom" , 18);
        treeSet.add(st1);
        treeSet.add(st2);
        treeSet.add(st3);
        treeSet.add(st4);
        //增强for循环
        for(Object obj : treeSet){
            System.out.println(obj);
        }
    }
}
运行结果:
Student{name='Mike', age=18}
Student{name='Yom', age=18}
Student{name='Amy', age=20}
Student{name='Jone', age=20}

4.3.2 定制排序

在创建集合的时候,根据其元素的自然排序进行排序;实现Comparable接口,并实现其中的compareTo()方法。

TreeSet(Comparator<? super E> comparator)构造一个新的,空的树集,根据指定的比较器进行排序。

方式一:编写一个普通类,去实现接口

实现:

import java.util.Comparator;
//实现Comparator接口
public class ComparatorStu implements Comparator {
    @Override
    public int compare(Object o1, Object o2) {
        Student st1 = (Student) o1;
        Student st2 = (Student) o2;
        int st = st1.getAge().compareTo(st2.getAge());
        return st == 0 ? st1.getName().compareTo(st2.getName()) : st;
    }
}

运行结果:

Student{name='Mike', age=18}
Student{name='Yom', age=18}
Student{name='Amy', age=20}
Student{name='Jone', age=20}

方式二:使用内部类实现

实现:

import java.util.Comparator;
import java.util.TreeSet;

public class TreeSetDemo_2 {
    public static void main(String[] args) {
        Comparator com = new ComparatorStudent();
        TreeSet treeSet = new TreeSet(com);
        Student st1 = new Student("Jone" , 20);
        Student st2 = new Student("Amy" , 20);
        Student st3 = new Student("Mike" , 18);
        Student st4 = new Student("Yom" , 18);
        treeSet.add(st1);
        treeSet.add(st2);
        treeSet.add(st3);
        treeSet.add(st4);
        //增强for循环
        for(Object obj : treeSet){
            System.out.println(obj);
        }
    }
    //使用内部类实现
    private static class ComparatorStudent implements Comparator{

        @Override
        public int compare(Object o1, Object o2) {
            Student st1 = (Student) o1;
            Student st2 = (Student) o2;
            int st = st1.getAge().compareTo(st2.getAge());
            return st == 0 ? st1.getName().compareTo(st2.getName()) : st;
        }
    }
}

运行结果:

Student{name='Mike', age=18}
Student{name='Yom', age=18}
Student{name='Amy', age=20}
Student{name='Jone', age=20}

方式三:使用匿名内部类(推荐)

实现:

import java.util.Comparator;
import java.util.TreeSet;

public class TreeSetDemo_3 {
    public static void main(String[] args) {
        //使用匿名内部类
        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Student st1 = (Student) o1;
                Student st2 = (Student) o2;
                int st = st1.getAge().compareTo(st2.getAge());
                return st == 0 ? st1.getName().compareTo(st2.getName()) : st;
            }
        });
        Student st1 = new Student("Jone" , 20);
        Student st2 = new Student("Amy" , 20);
        Student st3 = new Student("Mike" , 18);
        Student st4 = new Student("Yom" , 18);
        treeSet.add(st1);
        treeSet.add(st2);
        treeSet.add(st3);
        treeSet.add(st4);
        //增强for循环
        for(Object obj : treeSet){
            System.out.println(obj);
        }
    }
}

运行结果:

Student{name='Mike', age=18}
Student{name='Yom', age=18}
Student{name='Amy', age=20}
Student{name='Jone', age=20}

结论

  • 用TreeSet存储自定义对象,使用无参构造来创建集合,此时默认使用的是自然排序,对元素的排序,需要让元素的所属类实现Comparable接口,并重写compareTo方法
  • 重写的时候,可以自定义排序规则。必须区分清楚主要条件和次要条件,同时要注意排序的要求(升序、降序)
  • 定制排序,在创建集合的时候,需要指定一个Compareator接口的对象

4.4 LinkedHashSet(哈希表+链表实现)

  1. 特点
  • 底层实现是哈希表和链表的实现,具有可预知的迭代顺序
  • 元素是有序的,有序是由链表保障的(迭代顺序与元素添加顺序相同)
  • 元素是不可重复的(哈希表保障了唯一性)
  • 此实现不同步
  1. 实例
import java.util.LinkedHashSet;

public class ArrayListDemo {
    public static void main(String[] args) {
        LinkedHashSet hash = new LinkedHashSet();
        hash.add("Java");
        hash.add("is");
        hash.add("a");
        hash.add("language");
        for(Object obj : hash){
            System.out.println(obj);
        }
    }
}
Java
is
a
language

4.5 面试题分析

import java.util.HashSet;

public class TreeSetDemo_4 {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        Person p1 = new Person(1001,"AA");
        Person p2 = new Person(1002,"BB");
        set.add(p1);//p1在集合中的存储位置是由1001和AA计算所得
        set.add(p2);
        System.out.println(set);
        p1.setName("CC");//将AA改为了CC
        System.out.println(set);
        boolean b = set.remove(p1);//此时会根据p1的hash值来计算需要移除的元素所在的位置
        System.out.println(b);//false
        System.out.println(set);//修改失败
        set.add(new Person(1001,"CC"));//存储的位置是由1001和CC计算所得
        System.out.println(set);//添加成功
        set.add(new Person(1001,"AA"));//虽然计算得到的位置和第二个对象相同,但是hash值不同且equals返回false
        System.out.println(set);
    }
}

运行结果:

[Person{number=1002, name='BB'}, Person{number=1001, name='AA'}]
[Person{number=1002, name='BB'}, Person{number=1001, name='CC'}]
false
[Person{number=1002, name='BB'}, Person{number=1001, name='CC'}]
[Person{number=1002, name='BB'}, Person{number=1001, name='CC'}, Person{number=1001, name='CC'}]
[Person{number=1002, name='BB'}, Person{number=1001, name='CC'}, Person{number=1001, name='CC'}, Person{number=1001, name='AA'}]

5 Map

在这里插入图片描述

  1. Map 的典型实现:HashMap,LinkedHashMap,TreeMap,HashTable(Properties)
  2. Map 的特点
  • 是与Collection 并列的集合
  • 用于保存具有映射关系的对象(映射对象以键值对的形式存在 ket-value)
  • 不能包含重复的键,每个键可以映射到最多一个值
  • 键不能重复,值可重复
  • 元素的存取是无序的

5.1 Map 的常用方法

  1. 常用方法
返回值类型方法
voidclear() 从该Map中删除所有的映射(可选操作)。
Vput(K key, V value) 将指定的值与该映射中的指定键相关联(可选操作)。
voidputAll(Map<? extends K,? extends V> m) 将指定地图的所有映射复制到此映射(可选操作)。
Vremove(Object key) 如果存在(从可选的操作),从该地图中删除一个键的映射。
default booleanremove(Object key, Object value) 仅当指定的密钥当前映射到指定的值时删除该条目。
default Vreplace(K key, V value) 只有当目标映射到某个值时,才能替换指定键的条目。
default booleanreplace(K key, V oldValue, V newValue) 仅当当前映射到指定的值时,才能替换指定键的条目。

实例:

import java.util.HashMap;
import java.util.Map;

public class MapDemo_2 {
    public static void main(String[] args) {
        Map<Integer,String> map = new HashMap<>();
        map.put(1001,"小明");
        map.put(1002,"小红");
        map.put(1003,"小黑");
        map.put(1004,"小李");
        map.remove(1003);  //删除
        boolean a = map.remove(1004,"小田");
        System.out.println("是否删除成功:" + a);
        map.replace(1001,"小晓");  //替换元素
        System.out.println(map);
        Map<Integer,String> map1 = new HashMap<>();
        map1.put(1010,"王一");
        map1.put(1011,"周三");
        map.putAll(map1);  //将map1中的元素添加到map中
        System.out.println(map);  
    }
}

运行结果:

是否删除成功:false
{1001=小晓, 1002=小红, 1004=小李}
{1010=王一, 1011=周三, 1001=小晓, 1002=小红, 1004=小李}
  1. 获取的方法
返回值类型方法
Set<Map.Entry<K,V>>entrySet() 返回此地图中包含的映射的Set视图。
Vget(Object key) 返回到指定键所映射的值,或 null如果此映射包含该键的映射。
SetkeySet() 返回此地图中包含的键的Set视图。
Collectionvalues() 返回此地图中包含的值的Collection视图。

实现:

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapDemo_2 {
    public static void main(String[] args) {
        Map<Integer,String> map = new HashMap<>();
        map.put(1001,"小明");
        map.put(1002,"小红");
        map.put(1003,"小黑");
        map.put(1004,"小李");
        System.out.println("学号1002对应的姓名为:" + map.get(1002));  //通过键获取值

        //获取集合中学生的学号,所有键的Set集合
        System.out.println("学生的学号为:");
        Set<Integer> keySet = map.keySet();
        for(Integer i : keySet){  //遍历集合
            System.out.println(i);
        }
        //获取集合中学生的姓名,所有值的Collection集合
        System.out.println("学生的姓名为:");
        Collection<String> value = map.values();
        for(String va : value){
            System.out.println(va);
        }
        //获取集合中的键值对
        Set<Map.Entry<Integer,String>> entrySet = map.entrySet();
        for(Map.Entry<Integer,String> entry : entrySet){
            System.out.println(entry);
        }
    }
}

运行结果:

学号1002对应的姓名为:小红
学生的学号为:
1001
1002
1003
1004
学生的姓名为:
小明
小红
小黑
小李
1001=小明
1002=小红
1003=小黑
1004=小李
  1. 判断的方法
返回值类型方法
booleancontainsKey(Object key) 如果此映射包含指定键的映射,则返回 true 。
booleancontainsValue(Object value) 如果此map将一个或多个键映射到指定的值,则返回 true 。
booleanisEmpty() 如果此map不包含键值映射,则返回 true 。

实现:

		System.out.println(map.isEmpty());  //判断集合是否为空
        System.out.println(map.containsKey(1001));  //判断集合中是否包含指定的key
        System.out.println(map.containsValue("黑黑"));  //判断集合中是否包含指定的value

运行结果:

false
true
false

5.2 Map 集合的遍历

  1. 方式一:获取Map 集合的键集,在使用map 所提供的通过键获取值的方式来进行遍历所有的元素

实现:

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapDemo_2 {
    public static void main(String[] args) {
        Map<Integer,String> map = new HashMap<>();
        map.put(1001,"小明");
        map.put(1002,"小红");
        map.put(1003,"小黑");
        map.put(1004,"小李");

        System.out.println("方式一:");
        //获取Map集合的键集合,再通过方法获取其值
        Set<Integer> keySet1 = map.keySet();
        for(Integer i : keySet1){
            System.out.println(i + " = " + map.get(i));
        }
    }
}

运行结果:

方式一:
1001 = 小明
1002 = 小红
1003 = 小黑
1004 = 小李
  1. 方式二:借助于Map.Entry 对象

实现:

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapDemo_2 {
    public static void main(String[] args) {
        Map<Integer,String> map = new HashMap<>();
        map.put(1001,"小明");
        map.put(1002,"小红");
        map.put(1003,"小黑");
        map.put(1004,"小李");

        System.out.println("方式一:");
        //获取Map集合的键集合,再通过方法获取其值
        Set<Integer> keySet1 = map.keySet();
        for(Integer i : keySet1){
            System.out.println(i + " = " + map.get(i));
        }

        System.out.println("方式二:");
        //借助于Map.Entry对象
        Set<Map.Entry<Integer,String>> entrySet1 = map.entrySet();
        for(Map.Entry<Integer,String> entry : entrySet1){
            System.out.println(entry);
        }
    }
}

运行结果:

方式二:
1001=小明
1002=小红
1003=小黑
1004=小李
  1. 实例

需求:使用Map集合存储自定义对象

学生对象:姓名 年龄

在map中,以学生对象为值 ,而以学生的学号为键

实现:

import java.util.*;

public class MapTest {
    public static void main(String[] args) {
        Map<String,Student> map = new HashMap<>();
        Student st1 = new Student("小明" , 18);
        Student st2 = new Student("小红" , 19);
        Student st3 = new Student("小黑" , 20);
        Student st4 = new Student("小刘" , 22);
        map.put("20171101" , st1);
        map.put("20171102" , st2);
        map.put("20171103" , st3);
        map.put("20171104" , st4);
        //方式一
        System.out.println("方式一:");
        Set<String> keys = map.keySet();
        Iterator<String> iter = keys.iterator();
        while(iter.hasNext()){
            String key = iter.next();
            Student value = map.get(key);
            System.out.println(key + "-" + value);
        }
        //方式二
        System.out.println("方式二:");
        Set<Map.Entry<String,Student>> entrySet = map.entrySet();
        for(Map.Entry<String,Student> entry : entrySet){
            System.out.println(entry);
        }
    }
}

运行结果:

方式一:
20171101-Student{name='小明', age=18}
20171103-Student{name='小黑', age=20}
20171102-Student{name='小红', age=19}
20171104-Student{name='小刘', age=22}
方式二:
20171101=Student{name='小明', age=18}
20171103=Student{name='小黑', age=20}
20171102=Student{name='小红', age=19}
20171104=Student{name='小刘', age=22}

注意:

  1. 在集合中,如果要使用泛型,都使用泛型;如果不使用泛型,那么都不要使用

  2. 泛型仅在编译期有效 ,在运行期是没有泛型的,称为泛型擦除

5.3 HashMap

  1. 特点:

    • Hash是map所有实现类中使用频率最高的实现类
    • 基于哈希表的实现的Map接口
    • 允许null的值和null键
    • 存取是无序的
    • hashMap的键是不能重复
  2. HashMap 的存储结构

jdk1.8 前 HashMap 使用的是数组+链表的实现

在这里插入图片描述

jdk1.8 及以后,HashMap 采用的是数组+链表+红红黑树的实现
在这里插入图片描述

  1. HashMap 的源码分析
DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16  HashMap的默认容量为16
MAXIMUM_CAPACITY = 1 << 30;//HashMap的最大支持容量2^30
DEFAULT_LOAD_FACTOR = 0.75f//HashMap的默认加载因子
TREEIFY_THRESHOLD = 8// 链表转换为红黑树的默认长度
UNTREEIFY_THRESHOLD = 6;// 红黑树转换为链表的默认长度
MIN_TREEIFY_CAPACITY = 64//被转换为树时的hash表的容量
Node<K,V>[] table;// 存储元素的数组 大小总是2的n次幂
Set<Map.Entry<K,V>> //entrySet;// 键值对集
int size;//键值对个数
int modCount;//hashMap容量改变的次数
int threshold;//扩容的临界值  = 容量 * 负载因子

Jdk8以前:数组+ 链表 = 桶(bucket)

在map中 元素的存储位置是根据键的 hashCode来计算而得

Jdk8以后 采用的 数组+ 链表 + 红黑树

HashMap(jdk8+)的底层实现特点:

  1. HashMap map = new HashMap()默认 创建一个长度为16得数组,但是数组此时并不会立即创建
  2. 当我们第一次去put的时候,才会初始化长度为16的数组
  3. 数组的类型为Node类型
  4. 形成链表的结构时,新添加的键值对在链表的尾部
  5. 当数组中链表的长度>8 时 且map中数组的长度> 64时,此时会将链表转换为红黑树

5.4 LinkedHashMap

  1. LinkedHashMap是HashMap的子类
  2. 在LinkedHashMap中,对于HashMap进行了扩展:使用了双向链表,来记录元素的添加顺序
  3. 基于特殊的结构,迭代顺序和插入顺序是一致的。

HashMap 的 Node:

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

LinkedHashMap 的 Node:

    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

由以上源码可得:在LinkedHashMap中,对于HashMap进行了扩展:使用了双向链表来记录元素的添加顺序

4.5 TreeMap

  1. 需要对存入其中的元素进行排序,TreeMap 可以保证元素处于有序状态,有序指遵循字典顺序;
  2. 排序方式:自然排序,定制排序

5.6 HashTable(了解)

  1. 允许hash 表实现,和HashMap 一样;
  2. 不允许null键,null值
  3. 是线程同步的

6 Collections(工具类)

是操作集合 Set,List,Map 的工具类;工具类中的方法都是静态的。

返回值类型方法
static voidcopy(List<? super T> dest, List<? extends T> src) 将所有元素从一个列表复制到另一个列表中。
static <T extends Object & Comparable<? super T>>Tmax(Collection<? extends T> coll) 根据其元素的 自然顺序返回给定集合的最大元素。
static <T extends Object & Comparable<? super T>>Tmin(Collection<? extends T> coll) 根据其元素的 自然顺序返回给定集合的最小元素。
static booleanreplaceAll(List list, T oldVal, T newVal) 将列表中一个指定值的所有出现替换为另一个。
static voidreverse(List<?> list) 反转指定列表中元素的顺序。
static voidshuffle(List<?> list) 使用默认的随机源随机排列指定的列表。
static <T extends Comparable<? super T>> voidsort(List list) 根据其元素的natural ordering对指定的列表进行排序。
static voidsort(List list, Comparator<? super T> c) 根据指定的比较器引起的顺序对指定的列表进行排序。
static voidswap(List<?> list, int i, int j) 交换指定列表中指定位置的元素。
static CollectionsynchronizedCollection(Collection c) 返回由指定集合支持的同步(线程安全)集合。
static ListsynchronizedList(List list) 返回由指定列表支持的同步(线程安全)列表。
static <K,V> Map<K,V>synchronizedMap(Map<K,V> m) 返回由指定地图支持的同步(线程安全)映射。
static SetsynchronizedSet(Set s) 返回由指定集合支持的同步(线程安全)集。

实例:

import java.util.*;

public class CollectionsDemo {
    public static void main(String[] args) {
        List list1 = new ArrayList();
        list1.add(5);
        list1.add(7);
        list1.add(1);
        list1.add(3);
        list1.add(9);
        List list2 = new ArrayList();
        list2.add(2);
        list2.add(4);
        Collections.copy(list1,list2);  //两个集合的拷贝,会覆盖相应索引的元素
        System.out.println(list1);
        System.out.println(Collections.max(list1));  //返回集合的最大(自然排序)的元素
        System.out.println(Collections.min(list1));  //返回集合的最小(自然排序)的元素
        Collections.reverse(list1);  //反转列表中元素的顺序
        System.out.println(list1);
        boolean a = Collections.replaceAll(list1,9,11);  //替换元素
        System.out.println(list1);
        Collections.shuffle(list1); //对列表中的元素进行随机排序
        System.out.println(list1);
        Collections.swap(list1,3,2);  //交换元素索引为3与2处的位置
        System.out.println(list1);
        Collections.sort(list1);  //对集合进行排序
        System.out.println(list1);
        System.out.println(Collections.synchronizedCollection(list1));  //返回集合
        System.out.println(Collections.synchronizedList(list1));  //返回列表
    }
}

运行结果:

[2, 4, 1, 3, 9]
9
1
[9, 3, 1, 4, 2]
[11, 3, 1, 4, 2]
[1, 11, 4, 3, 2]
[1, 11, 3, 4, 2]
[1, 2, 3, 4, 11]
[1, 2, 3, 4, 11]
[1, 2, 3, 4, 11]

7 集合小结

在这里插入图片描述
在这里插入图片描述

  1. ArrayList:底层数据结构为数组,查询快,增删慢。其中的元素是有序的,允许存储 null 和重复的值。

    Vector 允许存储重复的值,也允许存储 null。

    LinkedList:底层数据结构为双向链表,查询慢,增删快

    TreeSet:底层数据结构为红黑树(有序,唯一)

    HashSet:底层数据结构为哈希表(无序,唯一) 【保证元素的唯一性:依赖两个方法:hashCode()和equals()】

    HashMap :无序,键不重复

  2. (1)集合主要分为单列Collection接口和双列Map接口

    (2)Set接口和List接口继承于Collection接口

    (3)Map接口为独立接口

    (4)List可重复集合:ArrayList,LinkedList等

    (5)Set不可重复集合:HashSet,TreeSet等

    (6)Map集合:HashMap等

  3. List 集合的遍历方式有如下:

    • Iterator 迭代器实现
    • 增强 for 循环实现
    • get()和 size()方法结合实现
//get()和 size()方法结合实现
for(int x= 0;x<list.size();x++) {
		Student g = (Student)list.get(x);
		System.out.println(g.getName()+"----"+g.getAge());
}
  1. Collection 接口存储一组不唯一,无序的对象
    Set 接口继承 Collection 接口,存储一组唯一,无序的对象
    List 接口继承 Collection 接口,存储一组不唯一,有序的对象

  2. 线程安全与线程不安全:

    • 线程安全:Vector、HashTable、Properties
    • 线程不安全:ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等

    值得注意的是:为了保证集合是线程安全的,相应的效率也比较低;线程不安全的集合效率相对会高一些。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BORN(^-^)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值