美文网首页Android基础Java 基础
Java 基础 08. Java 集合框架

Java 基础 08. Java 集合框架

作者: yjtuuige | 来源:发表于2022-01-03 16:08 被阅读0次

    一、集合概述

    • 概念:对象的容器,定义了对多个对象进行操作的常用方法。可实现数组的功能。
    • 和数组的区别
      1. 数组长度 固定,集合长度 不固定
      2. 数组可以存储基本类型和引用类型,集合只能存储引用类型
        注:基本类型可以通过装箱操作,存储到集合中
    • 位置: java.util.*;

    二、Collection 体系集合

    三、Collection 父接口

    • 特点:代表一组任意类型的对象,无序、无下标、不能重复。
    • 方法
    方法名 说明
    boolean add(Object obj) 添加一个对象
    boolean addAll(Collection c) 将一个集合中的所有对象,添加到此集合中
    void clear() 清空集合中的所有对象
    boolean contains(Object o) 检查集合中是否包含 o 对象
    boolean equals(Object o) 比较集合是否与指定对象相等
    boolean isEmpty() 判断集合是否为空
    boolean remove(Object o) 在集合中移除 o 对象
    int size() 返回集合中的元素个数
    Object[] toArray() 将集合转换成数组
    • Collection 接口的使用(一)
    package com.base.demo09;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    
    /*
     * Collection 接口的使用
     * 1.添加元素
     * 2.删除元素
     * 3.遍历元素
     * 4.判断
     * */
    public class Demo01 {
        public static void main(String[] args) {
            // 创建集合
            Collection collection = new ArrayList();
            // 1.添加元素
            collection.add("苹果");
            collection.add("梨");
            collection.add("西瓜");
            System.out.println("元素个数:" + collection.size());
            System.out.println(collection); // collection 已自动重写了 toString()方法
            // 2.删除元素
    //        collection.remove("西瓜");
            // 清空
    //        collection.clear();
            System.out.println("删除后元素个数:" + collection.size());
    
            // 3.遍历元素(重点)
            // 3.1增强for(不能使用for,因为没有下标)
            System.out.println("--------------3.1增强for-------------");
            for (Object o : collection) {
                System.out.println(o);
            }
            // 3.2使用迭代器(专门用来遍历集合的一种方式,是一个接口)
            // hasNext();   是否有下一个元素
            // next();  获取下一个元素
            // remove();    删除当前元素
            System.out.println("--------------3.2使用迭代器-------------");
            Iterator it = collection.iterator();    // 接口
            while (it.hasNext()) {
                String s = (String) it.next();  // 强转到String
                System.out.println(s);
                // collection.remove(s); 引发错误:并发修改异常。迭代时,不能使用 collection.remove(s)
    //            it.remove();    // 可以通过迭代内部方法,进行删除
            }
            System.out.println("元素个数:" + collection.size());
            // 4.判断
            // contains()是否包含
            System.out.println(collection.contains("西瓜"));
            // 是否为空
            System.out.println(collection.isEmpty());
        }
    }
    
    • Collection 接口的使用(二)
    package com.base.demo09;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    
    // Collection 的使用:保存学生信息
    public class Demo02 {
        public static void main(String[] args) {
            // 1.创建 Collection 对象
            Collection collection = new ArrayList();
            // 创建学生对象
            Student s1 = new Student("张三", 20);
            Student s2 = new Student("李四", 19);
            Student s3 = new Student("王五", 21);
            // 2.添加数据
            collection.add(s1);
            collection.add(s2);
            collection.add(s3);
            System.out.println("元素个数:" + collection.size());
            System.out.println(collection.toString());
            // 3.删除
    //        collection.remove(s1);
    //        collection.remove(new Student("李四", 21));  需重写 equals(this==obj) 方法,才能实现
            // 从集合中删除,并没有删除对象
    //        collection.clear();
            System.out.println("删除之后:" + collection.size());
    
            // 4.遍历
            // 4.1增强 for
            // 遍历后为对象类型,需要转换
            System.out.println("-----------4.1增强 for------------");
            for (Object o : collection) {
                Student s = (Student) o;  // 将对象类型,转换为学生类型
                System.out.println(s.toString());
            }
            // 4.2迭代器 hasNext(); next(); remove(); 迭代过程不能使用 collection 的删除方法
            System.out.println("-----------4.2迭代器------------");
            Iterator it = collection.iterator();
            while (it.hasNext()) {
                Student s = (Student) it.next();    // 类型转换
                System.out.println(s);
            }
            // 5. 判断
            // 是否包含对象
            System.out.println(collection.contains(s1));
            // 是否为空
            System.out.println(collection.isEmpty());
        }
    }
    
    • 实例:学生类 Student
    package com.base.demo09;
    
    // 学生类
    public class Student {
        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 String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    四、Collection 子接口

    List 集合

    • 特点:有序、有下标、元素可以重复。
    • 方法:继承 Collection 的方法外,自己的部分方法
    方法名 说明
    void add(int index,Object o) 在 index 位置插入对象 o
    boolean addAll(index,Collection c) 将一个集合中的元素,添加到此集合中的 index 位置
    Object get(int index) 返回集合中,指定位置的元素
    List subList(int fromIndex,int toIndex) 返回 fromIndex 和 toIndex 之间的集合元素
    • 实例:List 子接口的使用(一)
    package com.base.demo09;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.ListIterator;
    
    /*
     * List 子接口使用
     * 特点:1.有序 2.有下标 3.可以重复
     * 1.添加元素
     * 2.删除元素
     * 3.遍历元素
     * 4.判断
     * 5.获取位置
     * */
    public class Demo03 {
        public static void main(String[] args) {
            // 创建对象
            List list = new ArrayList<>();
            // 1.添加元素
            list.add("一");
            list.add("二");
            list.add("三");
            System.out.println("元素个数:" + list.size());
            System.out.println(list.toString());
            // 2.删除元素
            /*
            list.remove("二");
            list.remove(0); // 下标方式删除
            System.out.println("删除后:"+list.size());
            System.out.println(list.toString());
            */
            // 3.遍历元素
            // 3.1 for 遍历
            System.out.println("-----------3.1 for 遍历------------");
            for (int i = 0; i < list.size(); i++) {
                // get() 获取集合中的元素,i 为下标
                System.out.println(i + ":" + list.get(i));
            }
            // 3.2 增强 for 遍历
            System.out.println("-----------3.2 增强 for 遍历------------");
            for (Object o : list) {
                System.out.println(o);
            }
            // 3.3 迭代器遍历 Iterator
            System.out.println("-----------3.3 Iterator 迭代器遍历------------");
            Iterator it = list.iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
            // 3.4 列表迭代器遍历:ListIterator 可以双向遍历,添加、删除及修改元素
            System.out.println("-----------3.4 ListIterator 迭代器遍历 从前向后------------");
            ListIterator lit = list.listIterator();
            while (lit.hasNext()) {
                System.out.println(lit.nextIndex() + ":" + lit.next());
            }
            // 3.5 列表迭代器,反向遍历(迭代时,“遍历指针” 需指向末尾)
            System.out.println("-----------3.5 列表迭代器 从后向前------------");
            while (lit.hasPrevious()) {
                System.out.println(lit.previousIndex() + ":" + lit.previous());
            }
            // 4.判断
            System.out.println(list.contains("二")); // 是否包含元素
            System.out.println(list.isEmpty()); // 是否为空
            // 5.获取位置
            System.out.println(list.indexOf("一"));
        }
    }
    
    • 实例:List 子接口的使用(二)
    package com.base.demo09;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /*
     * List 使用(二)
     * */
    public class Demo04 {
        public static void main(String[] args) {
            // 创建对象
            List list = new ArrayList();
            // 1.添加数字数据(自动装箱)
            list.add(20);
            list.add(30);
            list.add(40);
            list.add(50);
            list.add(60);
            System.out.println("元素个数:" + list.size());
            System.out.println(list.toString());
            // 2.删除:remove()内通过下标删除,需要将删除的数字做类型转换
            // list.remove(0);
            // list.remove(20);  异常,数组越界
            list.remove(new Integer(20));   // 或 list.remove((Object) 20);
            System.out.println("删除后:" + list.size());
            System.out.println(list.toString());
    
            // 3.subList() 返回子集合(含头不含尾)
            List list2 = list.subList(0, 3);
            System.out.println(list2.toString());
        }
    }
    

    五、List 实现类

    1. ArrayList【重点】
    • 数组结构 实现,查询块、增删慢;
    • JDK1.2 版本,运行效率快、线程不安全。
    package com.base.demo09;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.ListIterator;
    
    /**
     * ArrayList的使用
     * 存储结构:数组;
     * 特点:查找遍历速度快,增删慢。
     * 1.添加元素
     * 2.删除元素
     * 3.遍历元素(重点)
     * 4.判断
     * 5.查找
     */
    public class Demo05 {
        public static void main(String[] args) {
            // 创建对象  (size 0, 容量 0,每次扩容大小是原来的 1.5 倍)
            ArrayList arrayList = new ArrayList<>();
            // 创建学生类对象
            Student s1 = new Student("学生1", 20);
            Student s2 = new Student("学生2", 21);
            Student s3 = new Student("学生3", 19);
            // 1.添加元素
            arrayList.add(s1);
            arrayList.add(s2);
            arrayList.add(s3);
            System.out.println("元素个数:" + arrayList.size());
            System.out.println(arrayList.toString());
            // 2.删除元素
    //        arrayList.remove(s1);
            // 和 s1,是两个不同的对象,需重写 equals(this==obj) 方法
            arrayList.remove(new Student("学生1", 20)); 
            System.out.println("删除之后:" + arrayList.size());
    
            // 3.遍历元素(重点): for、增强for、迭代器、列表迭代器
            // 3.1迭代器 Iterator
            System.out.println("---------------3.1迭代器 Iterator------------");
            Iterator it = arrayList.iterator();
            while (it.hasNext()) {
                Student s = (Student) it.next();    // 类型转换:对象类转换为 Student 类
                System.out.println(s.toString());
            }
            // 3.2列表迭代器 ListIterator
            System.out.println("---------------3.2列表迭代器 ListIterator------------");
            ListIterator lit = arrayList.listIterator();
            while (lit.hasNext()) {
                Student s = (Student) lit.next();
                System.out.println(s.toString());
            }
            // 3.3列表迭代器(逆序:从后往前遍历)
            System.out.println("---------------3.3列表迭代器(逆序)------------");
            while (lit.hasPrevious()){
                Student s = (Student) lit.previous();
                System.out.println(s.toString());
            }
            // 4.判断
            // 是否包含对象
            System.out.println(arrayList.contains(s2)); // true
            // 重写 equals() 方法后
            System.out.println(arrayList.contains(new Student("学生2", 21))); // true
            System.out.println(arrayList.isEmpty());
            // 5.查找
            System.out.println(arrayList.indexOf(s3));
            System.out.println(arrayList.indexOf(new Student("学生2", 21)));
        }
    }
    

    注:Object 里的 equals(this==obj) 用地址和当前对象比较,如果想实现代码中的问题,可以在学生类中重写 equals 方法:查看 equals 方法重写

    // 重写 equals() 方法
        @Override
        public boolean equals(Object obj) {
            // 1.判断是否是同一对象
            if (this == obj) {
                return true;
            }
            // 2.判断是否为空
            if (obj == null) {
                return false;
            }
            // 3.判断是否是 Student 类型
            if (obj instanceof Student) {
                Student s = (Student) obj;    // 类型转换
                // 4.比较属性
                if (this.name.equals(s.getName()) && this.age == s.getAge()) {
                    return true;
                }
            }
            // 5.不满足条件,返回false
            return false;
        }
    
    //    IDEA 方法1
    //    @Override  
    //    public boolean equals(Object o) {
    //        if (this == o) return true;
    //        if (!(o instanceof Student)) return false;
    //
    //        Student student = (Student) o;
    //
    //        if (getAge() != student.getAge()) return false;
    //        return getName().equals(student.getName());
    //    }
    
    //    IDEA 方法2
    //    @Override
    //    public boolean equals(Object o) {
    //        if (this == o) return true;
    //        if (!(o instanceof Student)) return false;
    //
    //        Student student = (Student) o;
    //
    //        if (age != student.age) return false;
    //        return name.equals(student.name);
    //    }
    

    ArrayList 源码分析

    • 默认容量大小:private static final int DEFAULT_CAPACITY = 10;
      (如果没有向集合中添加元素,容量 0 ,添加第一个元素后,容量 10,每次扩容大小是原来的 1.5 倍)
    • 存放元素的数组:transient Object[] elementData;
    • 实际元素个数:private int size;
    • 创建对象时,调用的无参构造函数:
    //这是一个空的数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 无参构造
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    

    注:没有向集合中添加任何元素时,集合容量为 0。(默认的 10 个容量怎么来的?)查看 add 方法源码:

    // add 方法源码
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    • 假设 new 了一个数组,当前容量为 0,size 当然也为 0。这时调用 add 方法进入到 ensureCapacityInternal(size + 1); 该方法源码如下:
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    • 该方法中的参数 minCapacity 传入的值为 size+1 也就是 1,接着,再进入到 calculateCapacity(elementData, minCapacity) 里面:
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    • 上文说过,elementData 就是存放元素的数组,当前容量为 0,if 条件成立,返回默认容量 DEFAULT_CAPACITY 也就是 10。这个值作为参数,又传入 ensureExplicitCapacity() 方法中,进入该方法查看源码:
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    • 先不要管 modCount 这个变量。因为 elementData 数组长度为 0,所以 if 条件成立,调用 grow 方法(重点),再次进入到 grow 方法的源码中:
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    • 这个方法先声明了一个 oldCapacity 变量,将数组长度赋给它,其值为 0;又声明了一个 newCapacity 变量,其值为 oldCapacity+ 一个增量,可以发现这个增量,是和原数组长度有关的量,当然在这里也为 0。第一个 if 条件满足,newCapacity 的值为 10(这就是默认的容量)。第二个 if 条件不成立,也可以不用注意,因为 MAX_ARRAY_SIZE 的定义如下:
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
    • 这个值太大了,以至于第二个 if 条件没有了解的必要。
    • 最后一句话,就是为 elementData 数组赋予了新的长度,Arrays.copyOf() 方法,返回的数组,是新的数组对象,原数组对象不会改变,该拷贝不会影响原来的数组。copyOf() 的第二个自变量,指定要建立的新数组长度,如果新数组的长度,超过原数组的长度,则保留数组默认值。
    • 这时候再回到 add 的方法中,接着就向下执行 elementData[size++] = e;
    • 当数组长度为 10 的时候,通过源码可知,每次扩容为原来的 1.5 倍。
    1. Vector(使用不多)
    • 数组结构 实现,查询快、增删慢;
    • JDK1.0 版本,运行效率慢、线程安全。
    package com.base.demo10;
    
    import java.util.Enumeration;
    import java.util.Vector;
    
    /*
     * Vector 集合的使用
     * 存储结构:数组
     * */
    public class Demo01 {
        public static void main(String[] args) {
            // 创建对象
            Vector vector = new Vector<>();
            // 1.添加元素
            vector.add("vector01");
            vector.add("vector02");
            vector.add("vector03");
            System.out.println("元素个数:" + vector.size());
            System.out.println(vector.toString());
            // 2.删除
            // vector.remove(0);
            // vector.remove("vector02");
            // vector.clear(); // 清空
            // System.out.println("删除后个数:"+vector.size());
            // 3.遍历 (for、增强for、枚举器)
            // 枚举器遍历
            Enumeration en = vector.elements();
            while (en.hasMoreElements()) {
                String o = (String) en.nextElement();
                System.out.println(o);
            }
            // 4.判断
            System.out.println(vector.contains("vector01"));
            System.out.println(vector.isEmpty());
            // 5.vector 其它方法
            System.out.println(vector.firstElement());  // 获取第一个元素
            System.out.println(vector.lastElement());   // 获取最后一个元素
            System.out.println(vector.elementAt(0));    // 按下标获取元素
            System.out.println(vector.get(0));  // 按下标获取元素
        }
    }
    
    1. LinkedList(双向链表)
    • 链表结构 实现,增删快,查询慢。
    package com.base.demo10;
    
    import com.base.demo09.Student;
    
    import java.util.Iterator;
    import java.util.LinkedList;
    import java.util.ListIterator;
    
    /*
     * LinkedList 的使用
     * 存储结构:双向链表
     * */
    public class Demo02 {
        public static void main(String[] args) {
            // 创建集合
            LinkedList linkedList = new LinkedList<>();
            // 1.添加元素
            // 创建学生类对象
            Student s1 = new Student("学生1", 20);
            Student s2 = new Student("学生2", 21);
            Student s3 = new Student("学生3", 19);
            linkedList.add(s1);
            linkedList.add(s2);
            linkedList.add(s3);
            System.out.println("元素个数:" + linkedList.size());
            System.out.println(linkedList.toString());
            // 2.删除元素
            // linkedList.remove(s1);
            // linkedList.remove(new Student("学生2", 21));  // 需重写equals()方法
            // System.out.println("删除后:" + linkedList.size());
            // 3.遍历
            // 3.1 for遍历
            System.out.println("--------------3.1 for遍历---------------");
            for (int i = 0; i < linkedList.size(); i++) {
                System.out.println(linkedList.get(i));
            }
            // 3.2 增强 for 遍历
            System.out.println("--------------3.2 增强 for 遍历---------------");
            for (Object o : linkedList) {
                Student s = (Student) o;    // 类型转换
                System.out.println(s.toString());
            }
            // 3.3 迭代器遍历
            System.out.println("--------------3.3 迭代器遍历---------------");
            Iterator it = linkedList.iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
            // 3.4 列表迭代器遍历
            System.out.println("--------------3.4 列表迭代器遍历---------------");
            ListIterator lit = linkedList.listIterator();
            while (lit.hasNext()) {
                System.out.println(lit.next());
            }
            // 3.5 列表迭代器遍历(反向) 需集合指针指向末尾
            System.out.println("--------------3.5 列表迭代器遍历(反向)-------------");
            while (lit.hasPrevious()) {
                System.out.println(lit.previous());
            }
            // 4.判断
            System.out.println(linkedList.contains(s1));
            System.out.println(linkedList.isEmpty());
            // 5.获取位置
            System.out.println(linkedList.indexOf(s3));
        }
    }
    

    LinkedList 源码分析

    • LinkedList 三个属性:
      • 链表大小:transient int size = 0;
      • (指向)第一个结点(头结点): transient Node<E> first;
      • (指向)最后一个结点(尾结点):transient Node<E> last;
    • Node 类代码:
    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;
            }
        }
    
    • item 存放的是实际数据;next 指向下一个结点,prev 指向上一个结点。
    • Node 带参构造方法的三个参数,分别是前一个结点、存储的数据、后一个结点,调用这个构造方法时,将它们赋值给当前对象。
    • LinkedList 添加元素,看 add 方法:
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    
    • linkLast 方法源码(重点)
    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++;
        }
    
    • 假设刚开始 new 了一个 LinkedList 对象,first 和 last 属性都为空,调用 add 进入到 linkLast 方法。
    • 首先创建一个 Node 变量 l 将 last (此时为空)赋给它,然后 new 一个 newNode 变量存储数据,并且它的前驱指向 l,后继指向 null;再把 last 指向 newNode。如下图所示:
    • 如果满足 if 条件,说明这是添加的第一个结点,将 first 指向 newNode:
    • 至此,LinkedList 对象的第一个数据添加完毕。假设需要再添加一个数据,可以再来走一遍,图示如下:

    ArrayList 和 LinkedList 区别

    • ArrayList:必须开辟连续空间,查询快,增删慢。
    • LinkedList:无需开辟连续空间,查询慢,增删快。

    六、泛型概述

    • Java 泛型是 JDK1.5 中引入的一个新特性,其本质是 参数化类型,把类型作为参数传递。
    • 常见形式有 泛型类泛型接口泛型方法
    • 语法:<T,…> T 称为类型占位符,表示一种引用类型。
    • 好处:
      • 提高代码的重用性。
      • 防止类型转换异常,提高代码的安全性。
    1. 泛型类
    /**
     * 泛型类
     * 语法:类名<T>
     * T 是类型占位符,表示一种引用类型,编写多个使用逗号隔开 <T,E,K...>
     */
    public class MyGeneric<T>{
        // 1.创建泛型变量
        //不能使用 new 来创建,因为泛型是不确定的类型,也可能拥有私密的构造方法。
        T t;
        // 2.泛型作为方法的参数
        public void show(T t) {
            System.out.println(t);
        }
        // 3.泛型作为方法的返回值
        public T getT() {
            return t;
        }
    }
    
    • 泛型类使用
    package com.base.demo10;
    
    /**
     * 注意:
     * 1.泛型只能使用引用类型
     * 2.不同泛型类型的对象,不能相互赋值
     */
    public class TestGeneric {
        public static void main(String[] args) {
            // 使用泛型类创建对象
    //        MyGeneric<String> myGeneric = new MyGeneric<String>();    后面的类型可省略
            MyGeneric<String> myGeneric = new MyGeneric<>();
            myGeneric.t = "Hello";
            myGeneric.show("hello");
            String s = myGeneric.getT();
            System.out.println(s);
    
            MyGeneric<Integer> myGeneric1 = new MyGeneric<>();
            myGeneric1.t = 100;
            myGeneric1.show(200);
            Integer integer = myGeneric1.getT();
            System.out.println(integer);
        }
    }
    
    1. 泛型接口
    • 接口:
    package com.base.demo10;
    
    /*
     * 泛型接口
     * 语法:接口名<T>
     * 注意:不能创建泛型静态常量
     * */
    public interface MyInterface<T> {
        String name = "Liu";
    
        T server(T t);
    }
    
    • 接口实现类:实现接口时,确定泛型类
    package com.base.demo10;
    
    /**
     * 实现接口时,确定泛型类
     */
    public class MyInterfaceImpl implements MyInterface<String> {
        @Override
        public String server(String t) {
            System.out.println(t);
            return t;
        }
    }
    
    • 测试类
    //测试
    MyInterfaceImpl impl = new MyInterfaceImpl();
    impl.server("泛型接口测试");  // 泛型接口测试
    
    • 接口实现类:实现接口时,不确定泛型类
    package com.base.demo10;
    
    /**
     * 实现接口时,不确定泛型类
     */
    public class MyInterfaceImpl2<T> implements MyInterface<T> {
    
        @Override
        public T server(T t) {
            System.out.println(t);
            return t;
        }
    }
    
    • 测试类
    //测试
    MyInterfaceImpl2<Integer> impl2 = new MyInterfaceImpl2<>();
    impl2.server(2000);  // 2000
    
    1. 泛型方法
    • 方法类
    package com.base.demo10;
    
    /**
     * 泛型方法
     * 语法:<T> 返回类型
     */
    public class MyGenericMethod {
        // 泛型方法
        public <T> T show(T t) {
            System.out.println("泛型方法" + t);
            return t;
        }
    }
    
    • 测试类:调用泛型方法时,类型不需要传递,是由传递的数据来决定
    //测试类:泛型方法
    MyGenericMethod myGenericMethod = new MyGenericMethod();
    myGenericMethod.show("字符串");    // String
    myGenericMethod.show(200);  // Integer
    myGenericMethod.show(3.14); // Double
    
    1. 泛型集合
    • 概念:参数化类型、类型安全的集合,强制集合元素的类型必须一致。
    • 特点:
      • 编译时即可检查,而非运行时抛出异常。
      • 访问时,不必类型转换(拆箱)。
      • 不同泛型之间,引用不能相互赋值,泛型不存在多态。
    • 之前在创建 LinkedList 类型对象的时候,并没有使用泛型,查看源码会发现:
    public class LinkedList<E>
            extends AbstractSequentialList<E>
            implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
        // ......
    }
    
    • 它是一个泛型类,而之前使用的时候并没有传递,说明 java 语法是允许的。这个时候传递的类型是 Object 类,虽然它是所有类的父类,可以存储任意的类型,但是在遍历、获取元素时,需要原来的类型,就要进行强制转换。这个时候就会出现一些问题,假如往链表里存储了许多不同类型的数据,在强转的时候就要判断每一个原来的类型,这样就很容易出现错误。
    • 实例:
    package com.base.demo10;
    
    import com.base.demo09.Student;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    
    public class Demo03 {
        public static void main(String[] args) {
    //        ArrayList<String> arrayList = new ArrayList<String>();    后面的类型可不写
            ArrayList<String> arrayList = new ArrayList<>();
            arrayList.add("字符串1");
            arrayList.add("字符串2");
    //        arrayList.add(100);   报错:不能添加不同类型
    //        arrayList.add(3.14);
            for (String s : arrayList) {    // 直接为 String 类型,不需类型转换
                System.out.println(s);
            }
    
            ArrayList<Student> arrayList1 = new ArrayList<>();
            Student s1 = new Student("学生1", 20);
            Student s2 = new Student("学生2", 21);
            Student s3 = new Student("学生3", 19);
            arrayList1.add(s1);
            arrayList1.add(s2);
            arrayList1.add(s3);
    //        arrayList1.add(100); 报错:不能添加 Student 之外的类型
            Iterator<Student> it = arrayList1.iterator();
            while (it.hasNext()) {
                Student s = it.next();  // 无需强制转换
                System.out.println(s.toString());
            }
    //        arrayList = arrayList1;   报错:不同类型之间的泛型,不能相互赋值
        }
    }
    

    七、Set 集合概述

    Set 子接口

    • 特点:无序、无下标、元素不可重复。
    • 方法:全部继承自 Collection 中的方法。
    package com.base.demo11;
    
    import java.util.HashSet;
    import java.util.Iterator;
    
    /**
     * 测试Set接口的使用
     * 特点:1.无序,没有下标;2.重复
     * 1.添加数据(无序)
     * 2.删除数据
     * 3.遍历【重点】
     * 4.判断
     */
    public class Demo01 {
        public static void main(String[] args) {
            // 创建集合
            HashSet<String> set = new HashSet<>();
            // 1.添加数据(无序)
            set.add("字符串3");
            set.add("字符串1");
            set.add("字符串2");
    //        set.add("字符串2");  不能重复
            System.out.println("数据个数:" + set.size());
            System.out.println(set.toString());
            // 2.删除数据
    //        set.remove("字符串1"); // 无下标,不能通过下标删除
    //        System.out.println(set.toString());
            // 3.遍历 (两种:增强 for、迭代器)
            // 3.1增强 for 遍历
            System.out.println("------------3.1增强 for 遍历----------");
            for (String s : set) {
                System.out.println(s);
            }
            // 3.2迭代器遍历
            System.out.println("------------3.2迭代器遍历----------");
            Iterator<String> it = set.iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
            // 4.判断
            System.out.println(set.contains("字符串1"));
            System.out.println(set.isEmpty());
        }
    }
    

    八、Set 实现类

    1. HashSet【重点】
    • 基于 HashCode 计算元素存放位置。
    • 存储结构:哈希表(数组+链表+红黑树)
    • 存储过程(重复依据):当存入元素的哈希码相同时,会调用 equals 进行确认,如结果为 true,则拒绝后者存入(不同对象,相同属性值,需要重写 hashCode 和 equals 方法来实现)。
    • 实例 1:
    package com.base.demo11;
    
    import java.util.HashSet;
    import java.util.Iterator;
    
    /**
     * HashSet集合的使用
     * 存储结构:哈希表(数组+链表+红黑树)
     * 1.添加元素(无序)
     * 2.删除元素
     * 3.遍历
     * 4.判断
     */
    public class Demo02 {
        public static void main(String[] args) {
            // 创建集合
            // JDK 1.8 后,后面的泛型类型,可以不用写
            HashSet<String> hashSet = new HashSet<>();  
            // 1.添加元素
            hashSet.add("字符串1");
            hashSet.add("字符串2");
            hashSet.add("字符串3");
            hashSet.add("字符串4");
    //        hashSet.add("字符串4"); 不能添加重复元素
            System.out.println("元素个数:" + hashSet.size());
            System.out.println(hashSet.toString()); // 无序
            // 2.删除元素
    //        hashSet.remove("字符串2");
    //        System.out.println("删除后:"+hashSet.size());
            // 3.遍历(两种:增强for、迭代器)
            // 3.1增强 for
            System.out.println("-----------3.1增强 for----------");
            for (String s : hashSet) {
                System.out.println(s);
            }
            // 3.2迭代器
            System.out.println("-----------3.2迭代器----------");
            Iterator<String> it = hashSet.iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
            // 4.判断
            System.out.println(hashSet.contains("字符串3"));
            System.out.println(hashSet.isEmpty());
        }
    }
    
    • 创建 Person 类
    package com.base.demo11;
    
    // 创建 Person 类
    public class Person {
        private String name;
        private int age;
    
        public Person() {
        }
    
        public Person(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 String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    • 实例 2:
    package com.base.demo11;
    
    import java.util.HashSet;
    import java.util.Iterator;
    
    /**
     * HashSet 集合的使用
     * 存储结构:哈希表(数组+链表+红黑树)
     * 1.添加元素(无序)
     * 2.删除元素
     * 3.遍历
     * 4.判断
     */
    public class Demo03 {
        public static void main(String[] args) {
            // 创建集合
            HashSet<Person> persons = new HashSet<>();
            // 创建 Person 对象
            Person p1 = new Person("张三", 25);
            Person p2 = new Person("李四", 23);
            Person p3 = new Person("王五", 26);
            // 1.添加元素
            persons.add(p1);
            persons.add(p2);
            persons.add(p3);
    //        person.add(p3); 重复,不能添加
            // 不同对象,相同属性值,需要重写 hashCode 和 equals 方法
            // 来实现(否则不视为重复,可以添加)
    //        person.add(new Person("王五", 26));
            System.out.println("元素个数:" + persons.size());
            System.out.println(persons.toString());
            // 2.删除元素
    //        persons.remove(p1);
            // 未重写 hashCode 和 equals 方法时,不能删除
    //        person.remove(new Person("张三", 25));
    
            System.out.println("删除后:" + persons.size());
            // 3.遍历
            // 3.1增强for
            System.out.println("----------3.1增强for-----------");
            for (Person person : persons) {
                System.out.println(person.toString());
            }
            // 3.2迭代器
            System.out.println("----------3.2迭代器-----------");
            Iterator<Person> it = persons.iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
            // 4.判断
            System.out.println(persons.contains(p1));
            // 与 p1 属性值相同,未重写 hashCode 和 equals 方法时,结果为 false
            System.out.println(persons.contains(new Person("张三", 25)));
            System.out.println(persons.isEmpty());
        }
    }
    

    注:HashSet 存储过程:(重复依据)

    • 第一步:根据 hashCode 计算保存的位置,如果位置为空,则直接保存,否则执行第二步。
    • 第二步:执行 equals 方法,如果方法返回 true,则认为是重复,拒绝存储,否则形成链表。
    • HashSet 存储过程,实际上就是,判断重复的依据,要实现 相同属性值,视为同一个对象,则不能添加 的问题,需重写 hashCode 和 equals 代码:
    // IDEA 快捷生成 Alt + Insert
    @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Person)) return false;
    
            Person person = (Person) o;
    
            if (age != person.age) return false;
            return name != null ? name.equals(person.name) : person.name == null;
        }
    
        @Override
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + age;
            return result;
        }
    

    HashSet 补充:hashCode 方法里,使用数字 31 的原因:

    1. 31 是一个质数(只能被 1 和它自身整除),这样的数字在计算时,可以尽量减少散列冲突(hashCode 值尽量不一样)。
    2. 可以提高执行效率,31 * i = ( i << 5 ) - i。31 乘以一个数,可以转换成移位操作,这样计算更快。

    2.TreeSet

    • 基于排序顺序,实现不重复
    • 存储结构:红黑树。
    • 实现了 SortedSet 接口,对集合元素 自动排序
    • 元素对象的类型,必须实现 Comparable 接口,指定排序规则。
    • 通过 CompareTo 方法,确定是否为 重复元素
    • 实例 1:
    package com.base.demo11;
    
    import java.util.Iterator;
    import java.util.TreeSet;
    
    /**
     * TreeSet 使用
     * 存储结构:红黑树
     */
    public class Demo04 {
        public static void main(String[] args) {
            // 创建集合
            TreeSet<String> treeSet = new TreeSet<>();
            // 1.添加元素(自动排序)
            treeSet.add("xyz");
            treeSet.add("abc");
            treeSet.add("hello");
    //        treeSet.add("xyz"); 不能重复
            System.out.println("元素个数:" + treeSet.size());
            System.out.println(treeSet.toString()); // 结果自动排序
            // 2.删除元素
    //        treeSet.remove("hello");
    //        System.out.println("删除后:" + treeSet.size());
            // 3.遍历(增强for、迭代器)
            // 3.1增强for
            System.out.println("-----------3.1增强for----------");
            for (String s : treeSet) {
                System.out.println(s);
            }
            // 3.2迭代器
            System.out.println("-----------3.2迭代器----------");
            Iterator<String> it = treeSet.iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
            // 4.判断
            System.out.println(treeSet.contains("xyz"));
            System.out.println(treeSet.isEmpty());
        }
    }
    
    • 实例 2:用之前的 Person 类,保存数据(元素类必须实现 Comparable 接口,及内部抽象方法,compareTo 方法返回 0,认为是重复元素)
    package com.base.demo11;
    
    import java.util.TreeSet;
    
    /**
     * 使用 TreeSet 保存数据
     * 存储结构:红黑树
     * 要求:元素类必须实现Comparable接口,compareTo方法返回 0,认为是重复元素
     */
    public class Demo05 {
        public static void main(String[] args) {
            // 创建集合
            TreeSet<Person> persons = new TreeSet<>();
            // 创建 Person 对象
            Person p1 = new Person("张三", 25);
            Person p2 = new Person("李四", 23);
            Person p3 = new Person("王五", 26);
            Person p4 = new Person("王五", 23);
    //        Person p5 = new Person("王五", 23);   相同属性值 不能添加
            // 1.添加元素(元素类必须实现 Comparable 接口,及内部抽象方法)
            persons.add(p1);
            persons.add(p2);
            persons.add(p3);
            // 位置在 p3 之前:通过重写 compareTo 方法排序
            persons.add(p4);
            System.out.println("元素个数:" + persons.size());
            System.out.println(persons.toString());
            // 2.删除元素
            persons.remove(p1);
            // 可以删除:重写 compareTo 方法后,比较的是属性值是否相同
            persons.remove(new Person("李四", 23));
            System.out.println("删除后:" + persons.size());
            // 3.遍历(略)
            // 4.判断
            System.out.println(persons.contains(p3));
            // true:重写 compareTo 方法后,比较的是属性值是否相同
            System.out.println(persons.contains(new Person("王五", 26)));
        }
    }
    
    • 查看 Comparable 接口源码,只有 compareTo 抽象方法,在 Person 类中实现它:
    public class Person implements Comparable<Person> {
        // 内部代码 略......
        @Override
        public int compareTo(Person o) {
            // 先比较 name(相同为 0)
            int n1 = this.name.compareTo(o.getName());
            // 再比较 age
            int n2 = this.age - o.getAge();
            // name 相同的话,返回 n2,否则返回 n1
            return n1 == 0 ? n2 : n1;
        }
    }
    

    Comparator:实现定制比较(比较器)

    • 除了实现 Comparable 接口里的比较方法,TreeSet 也提供了一个带比较器 Comparator 的构造方法,使用匿名内部类来实现它:
    package com.base.demo11;
    
    import java.util.Comparator;
    import java.util.TreeSet;
    
    /**
     * TreeSet的使用
     * Comparator:实现定制比较(比较器)
     */
    public class Demo06 {
        public static void main(String[] args) {
            // 创建集合,并通过匿名内部类,指定比较规则(Comparator)
            TreeSet<Person> persons = new TreeSet<>(new Comparator<Person>() {
                @Override
                public int compare(Person o1, Person o2) {
                    // 先比较 age
                    int n1 = o1.getAge() - o2.getAge();
                    // 再比较 name
                    int n2 = o1.getName().compareTo(o2.getName());
                    return n1 == 0 ? n2 : n1;
                }
            });
            // 创建 Person 对象
            Person p1 = new Person("张三", 25);
            Person p2 = new Person("李四", 23);
            Person p3 = new Person("王五", 26);
            Person p4 = new Person("王五", 23);
            // 添加元素(元素类必须实现 Comparable 接口,及内部抽象方法)
            persons.add(p1);
            persons.add(p2);
            persons.add(p3);
            persons.add(p4);
            System.out.println("元素个数:" + persons.size());
            // 结果为按 age 排序
            System.out.println(persons.toString());
        }
    }
    
    • 案例:
    package com.base.demo11;
    
    import java.util.Comparator;
    import java.util.TreeSet;
    
    /**
     * 要求:使用 TreeSet集合实现字符串按照长度进行排序
     * helloworld zhangsan lisi wangwu beijing xian nanjing
     * Comparator 接口实现定制比较
     */
    public class Demo07 {
        public static void main(String[] args) {
            // 创建集合,并通过匿名内部类,指定比较规则
            TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    // 先比较字符串长度
                    int n1 = o1.length() - o2.length();
                    // 再比较字符串
                    int n2 = o1.compareTo(o2);
                    return n1 == 0 ? n2 : n1;
                }
            });
            // 添加数据
            treeSet.add("helloworld");
            treeSet.add("zhangsan");
            treeSet.add("lisi");
            treeSet.add("wangwu");
            treeSet.add("beijing");
            treeSet.add("xian");
            treeSet.add("nanjing");
            System.out.println(treeSet.toString());
            // [lisi, xian, wangwu, beijing, nanjing, zhangsan, helloworld]
        }
    }
    

    九、Map 体系集合

    • Map 接口的特点:
      • 用于存储任意键值对(Key-Value)。
      • 键:无序、无下标、不允许重复(唯一)。
      • 值:无序、无下标、允许重复。

    Map 集合概述

    • 特点:存储 键值对Key-Value),无序、无下标,键不可重复。
    • 方法:
    类型 方法名 说明
    V put(K key,V value) 将对象存入到集合中,关联键值。key 重复则覆盖原值。
    V get(Object key) 根据键获取相应的值。
    Set<K> keySet() 返回所有的 key
    Collection<V> values() 返回包含所有值的 Collection 集合。
    Set<Map.Entry<K,V>> entrySet() 键值匹配的 set 集合
    • 实例:
    package com.base.demo12;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Map接口的使用
     * 特点:1.存储键值对 2.键不能重复,值可以重复 3.无序
     */
    public class Demo01 {
        public static void main(String[] args) {
            // 创建 Map 集合
            HashMap<String, String> map = new HashMap<>();
            // 1.添加元素(无序)
            map.put("cn", "中国");
            map.put("uk", "英国");
            map.put("usa", "美国");
    //        map.put("cn", "China"); 相同键名,可以添加,但个数不增加,值会替换 China
            System.out.println("元素个数:" + map.size());
            System.out.println(map.toString()); // {usa=美国, uk=英国, cn=中国}
            // 2.删除元素
    //        map.remove("usa");
    //        System.out.println("删除后:" + map.size());
            // 3.遍历:(1.keySet() 2.entrySet() 效率高)
            // 3.1 keySet(): 返回所有的 key(Set 集合)
            System.out.println("------------3.1 keySet()-----------");
    //        Set<String> keySet = map.keySet();
    //        for (String key : keySet) {
            // 简化
            for (String key : map.keySet()) {
                // map.get(key):通过键获取值
                System.out.println(key + ":" + map.get(key));
            }
            // 3.2 entrySet():返回 Set 集合(Map.Entry)的键值对 K-V
            System.out.println("------------3.2 entrySet()-----------");
    //        Set<Map.Entry<String, String>> entries = map.entrySet();
    //        for (Map.Entry<String, String> entry : entries) {
            // 简化
            for (Map.Entry<String, String> entry : map.entrySet()) {
                // getKey():获取键     getValue():获取值
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
            // 4.判断
            System.out.println(map.containsKey("cn"));
            System.out.println(map.containsValue("中国"));
        }
    }
    

    十、Map 集合的实现类

    1. HashMap【重点】

    • JDK 1.2 版本,线程不安全,运行效率快;允许用 null 作为 key 或是 value。
    • 存储结构:哈希表(数组+链表+红黑树)
    • 使用 key 的 hashCode 和 equals 作为 重复依据(去重,需重写两个方法)
    • 实例:创建 Student 类
    package com.base.demo12;
    
    // 学生类
    public class Student {
        private String name;
        private int stuNo;
    
        public Student() {
        }
    
        public Student(String name, int stuNo) {
            this.name = name;
            this.stuNo = stuNo;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getStuNo() {
            return stuNo;
        }
    
        public void setStuNo(int stuNo) {
            this.stuNo = stuNo;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", stuNo=" + stuNo +
                    '}';
        }
    }
    
    • 实例:HashMap 的使用
    package com.base.demo12;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * HashMap 的使用
     * 存储结构:哈希表(数组+链表+红黑树)
     */
    public class Demo02 {
        public static void main(String[] args) {
            // 创建集合
            HashMap<Student, String> students = new HashMap<>();
            // 创建 Student 对象
            Student s1 = new Student("学生1", 100);
            Student s2 = new Student("学生2", 101);
            Student s3 = new Student("学生3", 102);
            // 1.添加元素(无序)
            students.put(s1, "北京");
            students.put(s2, "上海");
            students.put(s3, "杭州");
            // 添加失败,但会更新值
    //        students.put(s3, "南京");
            // 添加成功:与 s3 属性值相同,但不是同一对象
            // 使用 key 的 hashCode 和 equals 作为重复依据(去重,需重写两个方法)
    //        students.put(new Student("学生3", 102), "杭州");   重写两方法后,无法添加
            System.out.println(students.size());
            System.out.println(students.toString());
            // 2.删除元素
    //        students.remove(s1);
    //        System.out.println("删除后:"+students.size());
            // 3.遍历(两种:keySet、entrySet)
            // 3.1 keySet() 遍历 (获取 key)
            System.out.println("-----------3.1 keySet() 遍历-----------");
            for (Student key : students.keySet()) {
                System.out.println(key.toString() + ":" + students.get(key));
            }
            // 3.2 entrySet() 遍历 (获取 K-V 键值对)
            System.out.println("-----------3.2 entrySet() 遍历-----------");
            for (Map.Entry<Student, String> entry : students.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
            // 4.判断
            System.out.println(students.containsKey(s1));
            // 重写方法后,结果为 true
            System.out.println(students.containsKey(new Student("学生1", 100)));
            System.out.println(students.containsValue("北京"));
        }
    }
    
    • 注:和 HashSet 类似,重复依据是 hashCode 和 equals 方法,重写即可
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Student)) return false;
    
            Student student = (Student) o;
    
            if (stuNo != student.stuNo) return false;
            return Objects.equals(name, student.name);
        }
    
        @Override
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + stuNo;
            return result;
        }
    

    HashMap 源码分析

    1. 默认初始化容量:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    2. 数组最大容量:static final int MAXIMUM_CAPACITY = 1 << 30;
    3. 默认加载因子:static final float DEFAULT_LOAD_FACTOR = 0.75f;
    4. 链表调整为红黑树的链表长度阈值(JDK 1.8):static final int TREEIFY_THRESHOLD = 8;
    5. 红黑树调整为链表的链表长度阈值(JDK 1.8):static final int UNTREEIFY_THRESHOLD = 6;
    6. 链表调整为红黑树的数组最小阈值(JDK 1.8):static final int MIN_TREEIFY_CAPACITY = 64;
    7. HashMap 存储的数组:transient Node<K,V>[] table;
    8. HashMap 存储的元素个数:transient int size;

    默认加载因子

    • 判断数组是否扩容的一个因子。假如数组容量为100,如果 HashMap 的存储元素个数超过了 100*0.75=75,那么就会进行扩容。

    链表调整为红黑树的链表长度阈值

    • 假设在数组中,下标为 3 的位置已经存储了数据,当新增数据时通过哈希码,得到的存储位置又是 3,那么就会在该位置形成一个链表,当链表过长时,就会转换成红黑树,以提高执行效率,这个阈值,就是链表转换成红黑树的最短链表长度;

    红黑树调整为链表的链表长度阈值

    • 当红黑树的元素个数,小于该阈值时,就会转换成链表。
    • 注:并不是只要链表长度大于 8,就可以转换成红黑树,需同时具备(数组的容量,大于等于 64),才会进行转换

    HashMap 的数组 table

    • 存储的是 Node<K,V> 类型,有一对键值,和指向 next 的指针(部分源码):
    static class Node<K,V> implements Map.Entry<K,V> {
          final K key;
          V value;
          Node<K,V> next;
      }
    
    • 之前的代码中在 new 对象时调用的是 HashMap 的无参构造方法,进入到该构造方法的源码查看一下:
    public HashMap() {
          this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
      }
    
    • 只赋值了一个默认加载因子;而且,源码中 table 和 size 都没有赋予初始值,说明刚创建的 HashMap 对象没有分配容量,并不拥有默认的 16 个空间大小,这样做的目的是为了节约空间,此时 table 为 null,size 为 0。
    • 当添加元素时,调用 put 方法:
    public V put(K key, V value) {
          return putVal(hash(key), key, value, false, true);
      }
    
    • put 方法把 key 和 value 传给了 putVal,同时还传入了一个 hash(Key) 所返回的值,这是一个产生哈希值的方法,查看 putVal 方法(部分源码):
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                        boolean evict) {
          Node<K,V>[] tab; Node<K,V> p; int n, i;
          if ((tab = table) == null || (n = tab.length) == 0)
              n = (tab = resize()).length;
          if ((p = tab[i = (n - 1) & hash]) == null)
              tab[i] = newNode(hash, key, value, null);
          else{
              //略
          }
      }
    
    • 这里面创建了一个 tab 数组,和一个 Node 变量 p,第一个 if 实际是判断 table 是否为空,而我们现在只关注,刚创建 HashMap 对象时的状态,此时 tab 和 table 都为空,满足条件,执行内部代码,这条代码,其实就是把 resize() 所返回的结果赋给 tab,n 就是 tab 的长度,resize 顾名思义,就是重新调整大小。查看 resize() 源码(部分):
    final Node<K,V>[] resize() {
          Node<K,V>[] oldTab = table;
          int oldCap = (oldTab == null) ? 0 : oldTab.length;
          int oldThr = threshold;
          if (oldCap > 0);
          else if (oldThr > 0);
          else {               // zero initial threshold signifies using defaults
              newCap = DEFAULT_INITIAL_CAPACITY;
              newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
          } 
          @SuppressWarnings({"rawtypes","unchecked"})
          Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
          table = newTab;
          return newTab;
      }
    
    • 该方法把 table 及其长度,赋值给 oldTab 和 oldCap;threshold 是阈值的意思,此时为 0,所以前两个 if 先不管,最后 else 里 newCap 的值为,默认初始化容量 16;往下创建了一个 newCap 大小的数组,并将其赋给了 table,刚创建的 HashMap 对象,就在这里获得了初始容量。回到 putVal 方法,第二个 if 就是根据哈希码得到的 tab 中的一个位置是否为空,为空时,便直接添加元素,此时数组中无元素所以直接添加。至此 HashMap 对象就完成了第一个元素的添加。当添加的元素超过 16*0.75=12 时,就会进行扩容:
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict){
          if (++size > threshold)
              resize();
      }
    
    • 扩容的代码如下(部分):
    final Node<K,V>[] resize() {
          int oldCap = (oldTab == null) ? 0 : oldTab.length;
          int newCap;
          if (oldCap > 0) {
              if (oldCap >= MAXIMUM_CAPACITY) {//略}
              else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                       oldCap >= DEFAULT_INITIAL_CAPACITY)
          }
      }
    
    • 核心部分是 else if 里的移位操作,也就是说每次扩容都是原来大小的两倍

    HashMap 源码总结

    1. HashMap 刚创建时,table=null,目的是节省空间,当添加第一个元素时,table 容量调整为 16;
    2. 当元素个数大于阈值(16*0.75=12)时,会进行扩容,扩容后大小为原来的 2 倍。目的是减少调整元素的个数;
    3. JDK 1.8:当每个链表长度大于 8,且数组元素个数大于等于 64 时,会调整为红黑树,目的是提高执行效率;
    4. JDK 1.8:当链表长度小于 6 时,调整成链表;
    5. JDK 1.8 以前,链表是头插入,JDK 1.8 以后是尾插入。

    HashSet 源码分析

    • HashSet 实际上,就是调用的 HashMap(部分源码):
    public class HashSet<E>
          extends AbstractSet<E>
          implements Set<E>, Cloneable, java.io.Serializable
      {
          private transient HashMap<E,Object> map;
          private static final Object PRESENT = new Object();
          public HashSet() {
              map = new HashMap<>();
          }
      }
    
    • HashSet 的存储结构,就是 HashMap,那它的存储方式,看 add 方法:
    public boolean add(E e) {
          return map.put(e, PRESENT)==null;
      }
    
    • 调用的是 map 的 put 方法,把元素作为 map 的 key 传入。
    1. HashTable(了解即可,基本不用)
    • JDK 1.0 版本,线程安全,运行效率慢;不允许 null 作为 key 或是value。
    • 初始容量 11,加载因子 0.75。
    • 这个集合在开发过程中已经不用了,稍微了解即可。
    1. Properties:配置文件的读取(和流相关)查看 I/O流
    • Hashtable 的子类,要求 key 和 value 都是 String。通常用于配置文件的读取。
    • 它继承了 Hashtable 的方法,与流关系密切,此处不详解。

    4.TreeMap

    • 实现了 SortedMap 接口(是 Map 的子接口),可以对 key 自动排序。
    • 存储结构:红黑树
    package com.base.demo12;
    
    import java.util.Comparator;
    import java.util.Map;
    import java.util.TreeMap;
    
    /**
     * TreeMap 的使用
     * 存储结构:红黑树
     */
    public class Demo03 {
        public static void main(String[] args) {
            // 创建集合
    //        TreeMap<Student, String> treeMap = new TreeMap<>();
            TreeMap<Student, String> treeMap = new TreeMap<>(new Comparator<Student>() {
                @Override
                public int compare(Student o1, Student o2) {
                    int n1 = o1.compareTo(o2);
                    return n1;
                }
            });
            // 创建 Student 对象
            Student s1 = new Student("学生1", 100);
            Student s2 = new Student("学生2", 101);
            Student s3 = new Student("学生3", 102);
            // 1.添加元素
            treeMap.put(s1, "北京");
            treeMap.put(s2, "上海");
            treeMap.put(s3, "杭州");
            // 添加失败,但会更新值
    //        treeMap.put(new Student("学生3", 102), "南京");
            // 不能直接打印,需要实现 Comparable 接口,因为红黑树需要比较大小
            System.out.println("元素个数:" + treeMap.size());
            System.out.println(treeMap.toString());
            // 2.删除元素
    //        treeMap.remove(s1);
            // 可删除,需实现 Comparable 接口
    //        treeMap.remove(new Student("学生3", 102));
    //        System.out.println("删除后:"+treeMap.size());
            // 3.遍历(两种:keySet、entrySet)
            // 3.1 keySet() 遍历(获取 Key)
            System.out.println("-----------3.1 keySet() 遍历-----------");
            for (Student key : treeMap.keySet()) {
                System.out.println(key + ":" + treeMap.get(key));
            }
            // 3.2 entrySet() 遍历(获取 K-V 键值对)
            System.out.println("-----------3.2 entrySet() 遍历-----------");
            for (Map.Entry<Student, String> entry : treeMap.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
            // 4.判断
            System.out.println(treeMap.containsKey(s1));
            System.out.println(treeMap.containsKey(new Student("学生1", 100)));
            System.out.println(treeMap.containsValue("杭州"));
        }
    }
    
    • 在 Student 类中实现 Comparable 接口:
    public class Student implements Comparable<Student>{
        @Override
        public int compareTo(Student o) {
            int n1 = this.stuNo - o.stuNo;
            return n1;
        }
    }
    
    • 或用比较器:
    TreeMap<Student, String> treeMap = new TreeMap<>(new Comparator<Student>() {
        @Override
        public int compare(Student o1, Student o2) {
            int n1 = o1.compareTo(o2);
            return n1;
        }
    });
    

    TreeSet 源码

    • 和 HashSet 类似(部分代码):
    public class TreeSet<E> extends AbstractSet<E>
        implements NavigableSet<E>, Cloneable, java.io.Serializable
    {
        private transient NavigableMap<E,Object> m;
        private static final Object PRESENT = new Object();
        TreeSet(NavigableMap<E,Object> m) {
            this.m = m;
        }
        public TreeSet() {
            this(new TreeMap<E,Object>());
        }
    }
    
    • TreeSet 的存储结构,实际上就是 TreeMap,再来看其存储方式:
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
    
    • 它的 add 方法,调用的就是 TreeMap 的 put 方法,将元素作为 key传入到存储结构中。

    十一、Collections 工具类

    • 概念:集合工具类,定义了除了存取以外的集合常用方法。
    • 方法
    方法名 说明
    public static void reverse(List<?> list) 反转集合中元素的顺序
    public static void shuffle(List<?> list) 随机重置集合元素的顺序
    public static void sort(List<T> list) 升序排序(元素类型必须实现 Comparable 接口)
    package com.base.demo12;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;
    
    /**
     * Collections 工具类的使用
     */
    public class Demo04 {
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            list.add(20);
            list.add(5);
            list.add(12);
            list.add(30);
            list.add(6);
            // sort 排序
            System.out.println("---------排序前--------");
            System.out.println(list.toString());
            System.out.println("---------排序后--------");
            Collections.sort(list);
            System.out.println(list.toString());
            // binarySearch 二分查找(查找位置)
            int i = Collections.binarySearch(list, 12);
            System.out.println(i);
            // copy 复制
            ArrayList<Integer> list1 = new ArrayList<>();
            // 将新的集合长度等于源集合长度
            for (int i1 = 0; i1 < list.size(); i1++) {
                list1.add(0);
            }
            // 该方法要求目标元素容量,大于等于源目标
            // 后者 list 为复制的集合
            Collections.copy(list1, list);
            System.out.println("复制:" + list1.toString());
            // reverse 反转
            Collections.reverse(list);
            System.out.println("反转后:" + list);
            // shuffle 打乱顺序
            Collections.shuffle(list);
            System.out.println("打乱顺序后:" + list);
    
            // 补充:1.list 转数组
            System.out.println("------------补充:1.list 转数组-----------");
            Integer[] arr = list.toArray(new Integer[0]);
            System.out.println(arr.length);
            System.out.println(Arrays.toString(arr));
    
            // 补充:2.数组转集合
            System.out.println("------------补充:2.数组转集合-----------");
            String[] names = {"张三", "李四", "王五"};
            // 数组转集合:受限集合,不能添加和删除
            List<String> list2 = Arrays.asList(names);
    //        list2.add("aa");  错误,不能添加
    //        list2.remove("张三");   错误,不能删除
            System.out.println(list2);
            // 注:基本类型转成集合时,需要修改为包装类
            Integer[] nums = {100, 200, 300, 400, 500};
            List<Integer> list3 = Arrays.asList(nums);
            System.out.println(list3);
        }
    }
    

    总结:

    • 集合的概念:
      • 对象的容器,和数组类似,定义了对多个对象进行操作的常用方法。
    • List 集合:
      • 有序、有下标、元素可以重复。(ArrayList、LinkedList、Vector
    • Set 集合:
      • 无序、无下标,元素不可重复。(HashSet、TreeSet)
    • Map 集合:
      • 存储一对数据(键值对),无序、无下标,键不可重复,值可重复。(HashMap、HashTable、TreeMap)
    • Collections:
      • 集合工具类,定义了除了存取以外的集合常用方法。

    相关文章

      网友评论

        本文标题:Java 基础 08. Java 集合框架

        本文链接:https://www.haomeiwen.com/subject/niysqrtx.html