JavaSE进阶笔记:06 集合

221 阅读23分钟

一、集合概述

概述Java中存储对象数据的一种容器;
特点集合大小不固定,类型不固定,启动后动态变化;
适合元素增删操作;
注意集合只能存储引用类型数据,基本数据类型选用包装类;
集合存储元素对象地址;

二、Collection集合体系特点

1.集合有两种:

  • 单列集合(祖宗接口)Collection
    • 接口(接口层)
    • List(元素有序、可重复、有索引)
      • (实现类层)
      • ArrayList
      • LinkedList
    • Set(元素无序、不重复、无索引)
      • HashSet
        • LinkedHashSet(改良,有序)
      • TreeSet(按照大小默认升序排序,不重复,无索引)
  • 双列集合Map

2集合对于泛型的支持

  • 集合在编译阶段约束集合只能操作某种数据类型;
  • 集合只支持引用数据类型,存储的都是对象;

2.1集合存储基本类型方式

//JDK1.7后,后面<>内容可以省略
Collection<Integer> list = new ArrayList<>();
Collection<Integer> list = new ArrayList<Integer>();
        
Collection<Double>  list = new ArrayList<>();

3. Collection集合API

/**
 * Collection类的API
 */
public class CollectionAPIDemo01 {
    public static void main(String[] args) {
        //1.添加元素
        Collection list = new ArrayList();
        list.add("java");
        list.add("c++");
        System.out.println(list.add("java"));//true
        list.add(520);
        System.out.println(list);//[java, c++, java, 520]
        
        //2.清空集合
        list.clear();
        System.out.println(list);//[]
        
        //3.判断集合是否为空,空返回true
        boolean empty = list.isEmpty();
        //4.获取集合大小
        int size = list.size();
        //5.判断集合是否包含某个元素
        boolean java = list.contains("java");
        //6.删除某个元素:有多个,删除第一个
        boolean java1 = list.remove("java");
        
        //7.集合转换成数组:使用Object接收一切类型
        Object[] arrs = list.toArray();
        System.out.println(Arrays.toString(arrs));

        //元素合并
        Collection c1 = new ArrayList();
        c1.add("a");
        c1.add("b");
        Collection c2 = new ArrayList();
        c2.add("c");
        c1.addAll(c2);
        System.out.println(c1);//[a, b, c]
        System.out.println(c2);//[c]
    }
}

4. Collection集合遍历方式

4.1 迭代器Iterator

迭代器Iterator,是集合专用遍历方式;

/**
 * 迭代器Iterator
 */
public class CollectionDemo01 {
    public static void main(String[] args) {
        Collection<String> list = new ArrayList();
        list.add("java");
        list.add("java2");
        list.add("java3");
        //iterator集合器对象,默认指向索引0,集合第一个元素
        Iterator<String> iterator = list.iterator();
        //iterator.hasNext(),判断当前位存在元素吗
        while (iterator.hasNext()){
            //取出元素,iterator后移一位
            String ele = iterator.next();
            System.out.println(ele);
        }
        //NoSuchElementException元素越界异常 
        String ele = iterator.next();
    }
}

4.2 foreach/增强for循环

  • 既可以遍历集合,也可以遍历数组
    • 不能用for,Collection接口不支持索引
  • JDK5后出现,内部原理是Iterator迭代器,一种简化写法;
  • 实现Iterator接口才可以使用迭代器和增强for,Collection接口实现了

/**
 * 增强for:
 *      数组、集合可用
 */
public class CollectionDemo02 {
    public static void main(String[] args) {
        Collection<String> list = new ArrayList();
        list.add("java");
        list.add("java2");
        list.add("java3");
        for (String ele : list) {
            System.out.println(ele);
        }
        
        //数组可以用
        double[] arrs = {102.5,363.9,888.8};
        for (double arr : arrs){
            System.out.println(arr);
        }
        /**
         * 102.5
         * 363.9
         * 888.8
         */
    }
}

4.3 lambda 表达式

/**
 * 增强forEach:
 *   lambda 表达式
 */
public class CollectionDemo03 {
    public static void main(String[] args) {
        Collection<String> list = new ArrayList();
        list.add("java");
        list.add("java2");
        list.add("java3");
        
        //forEach
        list.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });

        //lambda简化
        list.forEach( s -> System.out.println(s));

        //lambda简化进一步
        list.forEach( System.out::println);
    }
}

三、常见数据结构

1.数据结构概述

  • 数据结构是计算机底层存储、组织数据的方式;数据相互间以什么方式排列;
  • 通常,选择好的数据结构可以更高的运行存储效率;

2.常见数据结构

2.1栈

2.1.1栈数据结构的执行特点

​ 后进先出,先进后出,单向开口,类似弹夹,称为压栈;

2.2队列

2.2.1队列的执行特点

  • 先进先出,双向开口,排队
  • 数据后端进,入队列
  • 数据前端出,出队列;

2.3 数组

  • 查询速度快:
    • 查询数据通过地址值和索引定位,查询任意数据耗时相同
    • 元素内存中连续存储;
  • 删除效率低;
    • 删除数据,后面所有要前移
  • 添加效率低
    • 牵连大量元素;

2.4 链表

2.4.1链表特点

  • 链表中元素在内存中不连续存储,每个元素包含数据值和下一个元素地址;
  • 查询慢,
    • 查每个元素都从头开始
  • 增删快:
    • AC间添加B。
    • B对应的下一个地址指向C
    • A对应的下一个地址指向B

2.4.2 链表种类

  • 单向链表:
  • 双向链表:
    • 每个元素有前一个和后一个地址,数据值
    • 增加首尾元素特别快

2.5 二叉树

2.5.1 二叉树特点

只能有一个根节点每个节点最多支持2个直接子节点;
节点的度节点拥有子节点的个数,不大于2
叶子节点度为0,终端节点;
高度叶子节点高度为1,向上推,根节点最高
根节点第一层,向下推
兄弟节点拥有共同父节点的节点,互称兄弟节点

2.6 二叉查找数

二叉排序树,二叉搜索树
特点:每个节点,最多两个子节点
左子树,所有节点值都小于根节点;
右子树,所有节点值,都大于根节点;
目的提高检索性能
缺陷可能数据集中在一条子树上,成为瘸子;
查询性能与单链表一样,速度变慢

2.7 平衡二叉树

2.7.1特点

​ 满足二叉查找树的规则下,让数尽可能矮小,提高查询性能;

2.7.2 平衡二叉树要求

  • 任意节点的左右两个子树的高度差不超过1;
    • 第一个,10节点,左子树高度0,右子树高度3,超过1,不是;
  • 任意节点左右两个子树都是一颗平衡二叉树;
  • 添加元素后,可能导致不平衡
    • 策略:进行左旋或右旋保持平衡
    • 左左:左子树高,再加左子树,右旋
    • 左右:左子树高,再加左子树的右子树,右旋不行,先以添加父节点为基点,左旋,再右旋;
    • 右右:同上
    • 右左:

130平衡二叉树.png

2.7.3平衡二叉树案例

130(2)平衡二叉树案例.png

2.8 红黑树

2.8.1 红黑树概述

  • 红黑树是一种自平衡的二叉查找树;
  • 1972,称为平衡二叉B树;1978称为红黑树;
  • 每个节点可以是红或者黑;不是通过高度平衡,通过红黑规则实现;

2.8.2 红黑规则

  • 每个节点是红色或黑色,根节点必须是黑色
  • 如果一个节点没有子节点或者父节点,该节点相应的指针属性值为Nil,Nil视为叶节点,叶节点是黑色的;
  • 某一节点是红色,它的子节点必须是黑色(不能出现两个红节点相连
  • 每一个节点,从该节点到所有后代叶节点的简单路径上,均包含相同数目黑色节点;

2.8.3 添加节点

  • 添加节点颜色,可以是红色,也可以是黑色;
  • 默认用红色效率高;
  • 还需要看

130(3)红黑树逻辑演示.png

130(4)红黑树旋转.png

四、List系列集合

1.List集合特点和特有API

1.1特点

  • 有序:存储、取出元素顺序一致;
  • 有索引:通过索引操作元素
  • 可重复:存储元素

1.2 特有API

/**
 * List独有API
 */
public class ListDemo01 {
    public static void main(String[] args) {
        //List 有序,可重复,有索引
        List<String> list = new ArrayList();
        list.add("java");
        list.add("java2");
        list.add("java3");
        //2.在某个索引插入元素
        list.add(1,"java22");
        //[java, java22, java2, java3]
        System.out.println(list);
        //3.根据索引删除元素,返回删除的元素
        //java22
        System.out.println(list.remove(1));
        System.out.println(list);
        //4.根据索引,获取元素
        System.out.println(list.get(1));//java2
        //5.修改指定索引位置元素,返回修改前数据
        System.out.println(list.set(1, "java222"));//java2
        System.out.println(list);//[java, java222, java3]
    }
}

2.List集合遍历方式

/**
 * List循环遍历
 */
public class ListDemo02 {
    public static void main(String[] args) {
        //List 有序,可重复,有索引
        List<String> list = new ArrayList();
        list.add("java");
        list.add("java2");
        list.add("java3");

        //1.for
        for (int i = 0; i < list.size(); i++) {
            String ele = list.get(i);
            System.out.println(ele);
        }
        System.out.println("===========================");

        //2.迭代器Iterator
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            String ele = iterator.next();
            System.out.println(ele);
        }

        //3.forEach
        for (String l : list){
            System.out.println(l);
        }

        //4.Lambda表达式
        list.forEach(s -> System.out.println(s));

        //5.Lambda表达式 再简化
        list.forEach(System.out::println);
    }
}

3.ArrayList集合底层原理

  • 底层基于数组实现;根据索引定位元素,增删需要元素移位操作;
  • 第一次创建集合并添加第一个元素时,底层创建一个默认长度为10的数组;
  • 元素超过容量
    • 扩容,增加为原来的1.5倍
  • 插入新元素到中间位置
    • 倒序遍历,将索引后移,到指定位置,插入元素

4. LinkedList集合

4.1 LinkedList特点

底层数据结构是双链表,查询慢,首尾操作快,有很多首尾操作API

4.2 LinkedList集合特有功能

/**
 * LinkedList API
 */
public class ListDemo03 {
    public static void main(String[] args) {
        //LinkedList可以完成队列结构,栈结构(双链表)
        LinkedList<String> stack = new LinkedList();
        //压栈、入栈
        stack.addFirst("第1颗子弹");
        stack.addFirst("第2颗子弹");
        stack.addFirst("第3颗子弹");
        stack.addFirst("第4颗子弹");
        System.out.println(stack.getFirst());//第4颗子弹
        System.out.println(stack.getLast());//第1颗子弹
        //[第4颗子弹, 第3颗子弹, 第2颗子弹, 第1颗子弹]
        System.out.println(stack);
        //出栈、弹栈
        System.out.println(stack.removeFirst());//第4颗子弹
        System.out.println(stack.removeFirst());
        System.out.println(stack.removeFirst());
        System.out.println(stack.removeFirst());//第1颗子弹
        System.out.println(stack);//[]


        LinkedList<String> queue = new LinkedList();
        //入队,先进先出
        queue.addLast("1号");
        queue.addLast("2号");
        queue.addLast("3号");
        queue.addLast("4号");
        System.out.println(queue);//[1号, 2号, 3号, 4号]
        //出队
        System.out.println(queue.removeFirst());//1号
        System.out.println(queue.removeFirst());
        System.out.println(queue.removeFirst());
        System.out.println(queue.removeFirst());//4号
        System.out.println(queue);

        /**
         * 压栈
         * public void push(E e) {
         *   addFirst(e);
         *    }
         */
        stack.push("压栈");
        /**
         * 弹栈
         * public E pop() {
         *     return removeFirst();
         *  }
         */
        stack.pop();

        /**
         * 入队
         *     public boolean offerLast(E e) {
         *         addLast(e);
         *         return true;
         *     }
         */
        queue.offerLast("入队");
    }
}

五、集合并发修改异常问题

集合中找出某元素并删除的时候,出现并发修改异常;

5.1哪些遍历存在问题?

  • 增强for遍历,直接用集合删除元素可能出现;

    • 不能解决该问题,不能用
  • forEach 不能解决

    5.1.1 迭代器遍历,直接用集合删除元素可能出现;

    /**
     * 并发修改异常
     */
    public class Test {
        public static void main(String[] args) {
            List<String> list = new ArrayList();
            list.add("java");
            list.add("java");
            list.add("java2");
            list.add("java3");
            list.add("java3");
    
            //Iterator
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()){
                String ele = iterator.next();
                if ("java".equals(ele)){
                    //ConcurrentModificationException  并发修改异常
                    //list.remove("java");  后移会漏删,
                    iterator.remove();//删除当前位置元素,,内部删除后返回当前位置
                }
            }
            System.out.println(list);
    
        }
    }
    

    5.1.2 for循环,不会报错,但数据异常,需要使用解决方案

        //for
        //不报错,数据会漏删
    /*        for (int i = 0; i < list.size(); i++) {
                String ele = list.get(i);
                if ("java".equals(ele)){
                    list.remove("java");
                }
            }*/
            //解决方案,倒序删除
            for (int i = list.size()-1; i >= 0; i--) {
                String ele = list.get(i);
                if ("java".equals(ele)){
                    list.remove("java");
                }
            }
            System.out.println(list);
    
        }
    
            //解决方案2,i--
           for (int i = 0; i < list.size(); i++) {
                String ele = list.get(i);
                if ("java".equals(ele)){
                    list.remove("java");
                    i--;
                }
            }
            System.out.println(list);
    
        }
    

六、泛型深入

1.泛型概述

  • 泛型:JDK5中引入特性,可以在编译阶段约束操作的数据类型,并进行检查;
  • 泛型格式:<数据类型>
    • 注意:只能使用引用数据类型,不能用基本数据类型
  • 集合体系的全部接口和实现类都是支持泛型的使用

2.泛型好处

  • 统一数据类型;
  • 运行期问题提前到编译期,避免了强制类型转换可能出现的异常,编译阶段类型就能确定下来

3.自定义泛型类

3.1概述

  • 定义类,同时定义了泛型,就是泛型类;

  • 格式:修饰符 class 类名<泛型变量>{}

    • public class MyArrayList<T>{}
      
  • 泛型变量T可以写为任意表示,常用E T K V

  • 作用:编译阶段指定数据类型

3.2泛型类:原理

把出现泛型变量的地方,全部替换成传输的真实数据类型;

3.3 案例:模拟ArrayList

**装饰模式:**一个对象里,包另一个对象

/**
 * 模拟ArrayList
 *      实现 泛型,简单的添加删除
 */
public class MyArrayList<E> {
    private ArrayList arrayList = new ArrayList();

    public void add(E e){
        arrayList.add(e);
    }

    public boolean remove(E e){
       return arrayList.remove(e);
    }

    @Override
    public String toString() {
        return arrayList.toString();
    }
}

/**
 * 测试 MyArrayList
 */
public class Test {
    public static void main(String[] args) {
        MyArrayList<String> list = new MyArrayList<>();
        list.add("java");
        list.add("java2");
        list.add("java3");
        System.out.println(list.toString());//[java, java2, java3]
        list.remove("java");
        System.out.println(list.toString());//[java2, java3]
    }
}

4.自定义泛型方法

4.1 泛型方法概述

  • 定义方法,同时定义了泛型,就是泛型方法;
  • 格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}
  • 作用:方法中可以使用泛型接收一切实际类型参数,方法更具备通用性

4.2 演示

/**
 * 泛型方法
 * 作用:方法中可以使用泛型接收一切实际类型参数,方法更具备通用性
 */
public class GenericMethodDemo {
    public static void main(String[] args) {
        String[] a = {"saf","fasdf","fw","gtrg","hh"};
        printArray(a);//[saf,fasdf,fw,gtrg,hh]
        Integer[] b = {25,66,999,545,25};//[25,66,999,545,25]
        printArray(b);
        Long[] c= {};//[]
        printArray(c);

        String[] aa = getArr(a);
        Integer[] bb = getArr(b);
    }

    //泛型调用,返回不需要强转
    public static<T> T[] getArr(T[] arr){
        return arr;
    }

    //泛型,输出数组
    public static<T> void printArray(T[] arr){
        if (arr != null) {
            StringBuilder stringBuilder = new StringBuilder("[");
            for (int i = 0; i < arr.length; i++) {
                 stringBuilder.append(arr[i]).append(i == arr.length-1 ? "":",");
            }
            stringBuilder.append("]");
            System.out.println(stringBuilder.toString());
        }else {
            System.out.println(arr);
        }

    }
}

5.泛型接口

接口格式:修饰符 interface 接口名称<泛型变量>

作用:可以让实现类选择当前功能需要操作的数据类型

原理:实现类指定自己操作的数据类型,重写方法针对该类型操作

/**
 * 泛型接口
 * @param <E>
 */
public interface Data<E> {
    void add(E e);
    void delete(int id);
    void update(E e);
    E queryById(int id);
}

//Data<Teacher>  类型Teacher必须自行先定义
public class TeacherData implements Data<Teacher> {
    @Override
    public void add(Teacher teacher) {

    }

    @Override
    public void delete(int id) {

    }

    @Override
    public void update(Teacher teacher) {

    }

    @Override
    public Teacher queryById(int id) {
        return null;
    }
}

6.泛型通配符、上下限

1.通配符

  • 可以在使用泛型时代表一切类型;
  • E T K V 定义泛型时使用

2.上下限

  • ?extends Car:? 必须是Car或者其子类 ,泛型上线;
  • ?super Car:? 必须是Car或者其父类 ,泛型下线;

七、Set集合体系

1.Set系列集合特点

无序存取顺序不一致(第一次无序,后面一样)
不重复可以用来去重
无索引不能使用普通for循环,不能通过索引取元素

2.Set集合实现类特点

HashSet无序、不重复、无索引
LinkedHashSet有序、不重复、无索引
TreeSet排序、不重复、无索引
/**
 * Set系列集合特点
 */
public class SetDemo {
    public static void main(String[] args) {
        //无序,不重复 [java, mysql, 123456qq, 459qq]
        //Set<String> sets = new HashSet<>();
        //有序,不重复 无索引  [java, 123456qq, 459qq, mysql]
        Set<String> sets = new LinkedHashSet<>();
        sets.add("java");
        sets.add("java");
        sets.add("123456qq");
        sets.add("459qq");
        sets.add("mysql");
        sets.add("mysql");
        System.out.println(sets);
    }
}

3.HashSet底层原理

  • HashSet集合底层采取哈希表存储的数据;
  • 哈希表是一种对于增删改查数据性能都较好的结构;

3.1哈希表组成

  • JDK8之前,底层使用数组+链表组成;
  • JDK8之后,底层使用数组+链表+红黑树组成;

3.1.1 哈希值

  • 是JDK根据对象的地址,按照某种规则算出来的int类型数值;

3.1.2 Object类的API

  • public int hashCode();返回对象哈希值;

3.1.3 对象哈希值特点

  • 同一对象多次调用hashCode()方法返回哈希值相同;
  • 默认,不同对象,哈希值不同;
  • 哈希值相同,对象不一定相同(可能不同地址计算出同一哈希值)
  • 哈希值不同,一定不是同一对象;

3.2 JDK7 原理:数组+链表+(哈希算法)

  1. 创建一个默认长度16的数组,数组名table;
  2. 根据元素的哈希值跟数组长度求余(0-15)算出应存入位置(哈希算法);
    1. 存放位置不定,无序原因
  3. 判断当前位置是否null,是,直接存
  4. 如果不为null,有元素,调用equals方法比较值
  5. 一样,值重复不存;不一样,存入数组
    1. JDK7 ,新元素占老元素位置,用链表,指向老元素(放后面)
    2. JDK8,新元素挂在老元素下面;

3.3 JDK8 原理:哈希表(数组、链表、红黑树结合体)

  • 当挂在元素下面的数据过多时,查询性能降低,JDK8之后,链表长度超过8时,自动转为红黑树
    • 红黑树比较大小,根据哈希值比较,左右排

3.4 HashSet去重复原理解析

  • 使用哈希表,下方;
  • 结论:如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法;
  • 原因:通过对象地址计算hashCode();需要改为值计算;

4.哈希表的详细流程

  1. 创建一个默认长度16,默认加载因子0.75的数组,数组名table;
  2. 根据元素的哈希值跟数组长度求余(0-15)算出应存入位置(哈希算法);
  3. 判断当前位置是否null,
    1. 是,直接存;
    2. 如果不为null,有元素,调用equals方法比较值(值包含当前位置值和其下链表挂着的值);
      1. 一样,值重复不存;
      2. 不一样,存入数组,链表挂在当前值下
  4. 当数组存满到16*0.75=12时,自动扩容,每次扩容为原先的两倍

八、LinkedHashSet集合特点

  • 有序、不重复、无索引;
  • 有序:保证存储和取出的元素顺序一致;
  • 原理:底层数据结构依然是哈希表,每个元素额外多了一个双链表的机制记录存储的顺序;
    • 链表顺序按照元素存入顺序,交付前后地址,不是数组顺序

九、TreeSet集合

1.TreeSet集合概述和特点

  • 不重复、无索引、可排序;
  • 可排序:按照元素的大小默认升序(由小到大)排序;
  • TreeSet集合底层是基于红黑树的结构实现排序,增删改性能都较好
  • 注意:TreeSet集合一定要排序,可以将元素指定规则排序;

2.TreeSet集合默认的规则

  • 数值类型:Integer、Double,默认按照大小进行升序排序;
  • 字符串类型:默认按照首字符编号升序排序;
  • 自定义类型:Student对象,无法直接排序;
    • 结论:TreeSet存储自定义类型,需要先制定排序规则

3.自定义规则两种方式

  • 类实现Comparable接口;
  • 集合自定义Comparable比较器对象,重写比较规则;

十、总结

ArrayList集合,基于数组(用的最多)元素可重复,又有索引,索引查询要快
LinkedList集合,基于链表元素可重复,又有索引,增删首尾要快
HashSet集合,基于哈希表;增删改查都快,元素不重复,无序、无索引
LinkedHashSet集合,基于哈希表+双链表;增删改查都快,元素不重复,有序、无索引
TreeSet集合,基于红黑树;
后续可用List集合实现排序;
对对象进行排序

十一、可变参数

1.可变参数概述

  • 在形参中可以接收多个数据;
  • 格式:数据类型...参数名称

2.可变参数作用

  • 传输参数灵活,方便;可以不传输,也可以传输1个或多个,也可以传输数组
  • 注意:可变参数在方法内部就是一个数组:nums

3.可变参数注意事项

  • 一个形参列表中,可变参数只能有一个;
  • 可变参数必须放在形参列表最后面;
/**
 * 可变参数
 */
public class MethodDemo {
    public static void main(String[] args) {
        sum();//不传参
        sum(10);//传参一个
        sum(10,20);//传参多个
        sum(new int[]{10,20,30,40,50});//传数组
    }
    public static void sum(int...nums){
        //注意:可变参数在方法内部就是一个数组:nums
        System.out.println(nums.length);
        System.out.println(Arrays.toString(nums));
    }
}

十二、Collections集合工具类

/**
 * 添加多个public static <T> boolean addAll(Collection<? super T> c, T... elements) 
 * 打乱list顺序;public static void shuffle(List<?> list)
 * 默认规则排序:public static <T extends Comparable<? super T>> void sort(List<T> list) 
 */
public class CollectionsDemo01 {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("老大哥");
        names.add("帝骑哥");
        names.add("大聪明");
        names.add("吃瘪龙");
        //以下两种添加必须分开
        Collections.addAll(names,"网瘾医生","老司机","鬼仔");
        Collections.addAll(names,new String[]{"一人一半","投币"});
        System.out.println(names);

        //2.打乱list顺序;可以适用一切类型
        //洗牌
        //public static void shuffle(List<?> list)
        Collections.shuffle(names);
        System.out.println(names);

        //3.默认规则排序
        //Shift+F6 修改光标下的所有参数
        List<Integer> lists = new ArrayList<>();
        Collections.addAll(lists,15,678,99,42,61);
        Collections.sort(lists);
        System.out.println(lists);//[15, 42, 61, 99, 678]
    }
}

/**
 * 自定义规则排序,List
 * public static <T> void sort(List<T> list, Comparator<? super T> c) 
 */
public class CollectionsDemo02 {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("01", "飞电或人", 24,3546.9));
        students.add(new Student("02", "高桥", 22,478.0));
        students.add(new Student("03", "伊兹", 18,4565.9));
        students.add(new Student("04", "鹤岛", 21,999.9));

        Collections.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return Double.compare(o1.getPrice(), o2.getPrice());
            }
        });
        //优化
        Collections.sort(students, (o1,o2) -> Double.compare(o1.getPrice(), o2.getPrice()));
        System.out.println(students);
    }
}

十三、案例:斗地主

public class Card {
    private String size;
    private String color;
    private int sum;//牌真正的比较点数

    public Card() {
    }

    public Card(String size, String color, int sum) {
        this.size = size;
        this.color = color;
        this.sum = sum;
    }

    public String getSize() {
        return size;
    }

    public void setSize(String size) {
        this.size = size;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getSum() {
        return sum;
    }

    public void setSum(int sum) {
        this.sum = sum;
    }

    @Override
    public String toString() {
        return size + color ;
    }
}
public class GameDemo {
    /**
     * 1.定义静态集合存储54张牌
     */
   public static List<Card> allCards =  new ArrayList<>();
    /**
     * 2.做牌,定义静态代码块,初始化牌数据
     */
    static {
        String[] sizes = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
        String[] colors = {"♠","♥","♣","♦"};
        int sum = 0;
        for (String size : sizes) {
            for (String color : colors) {
                sum++;
                Card card = new Card(size, color,sum);
                allCards.add(card);
            }
        }
        //单独存大小王
        Card c1 = new Card("", "小王",++sum);
        Card c2 = new Card("", "大王",++sum);
        allCards.add(c1);
        allCards.add(c2);

        System.out.println("新牌:"+allCards);
    }

    public static void main(String[] args) {
        //9.洗牌
        Collections.shuffle(allCards);
        System.out.println("洗牌:"+allCards);
        //10.发牌,定义三个玩家
         List<Card> zio =  new ArrayList<>();
         List<Card> giz =  new ArrayList<>();
         List<Card> moon =  new ArrayList<>();

         //11.开始发牌,斗地主留下三张,51张牌,没人17张
        for (int i = 0; i < allCards.size()-3; i++) {
            Card card = allCards.get(i);
            if (i%3==0){
                zio.add(card);
            }else if (i%3==1){
                giz.add(card);
            }else if (i%3==2){
                moon.add(card);
            }
        }

        //12.拿三张底牌截取成一个子集合
        List<Card> lastThreeCards = allCards.subList(allCards.size()-3, allCards.size());

        //13.给每个玩家的牌排序
        sortCards(zio);
        System.out.println("时王:"+zio);
        sortCards(giz);
        System.out.println("盖茨:"+giz);
        sortCards(moon);
        System.out.println("月读:"+moon);
        System.out.println("三张底牌:"+lastThreeCards);
    }

    //给每个玩家的牌排序
    private static void sortCards(List<Card> cards) {
        Collections.sort(cards, new Comparator<Card>() {
            @Override
            public int compare(Card o1, Card o2) {
                return o2.getSum()- o1.getSum();
            }
        });
    }
}

十四、Map集合体系

1.Map集合概述和使用

  • Map集合是一种双列集合,每个元素包含两个数据;
  • 元素格式:key=value(键值对元素)
  • Map集合:键值对集合

1.1集合整体格式

  • Collection集合格式:【元素1,元素2,元素3...】
  • Map集合格式:{key1=value1,key2=value2,key3=value3...}

1.2特点

  • 集合特点:由键决定
  • 键key:无序、不重复,无索引;
  • 值value:可重复,无要求
  • 重复键对应的值,覆盖前面重复键的值
  • 键值对都可以为null

1.3Map集合实现类特点

HashMap元素按照键是无序,不重复,无索引,值不做要求;(Map体系一致)
LinkedHashMap元素按照键是有序,不重复,无索引,值不做要求;
TreeMap元素按照键是排序,不重复,无索引,值不做要求

2.Map集合常用API

/**
 * Map常用API
 */
public class MapAPIDemo {
    public static void main(String[] args) {
        HashMap<String, Integer> maps = new HashMap<>();
        maps.put("神极限",100000);
        maps.put("黄金头皮屑",100);
        maps.put("极限全能X",99);
        maps.put("医生",2);
        maps.put("机车",2);
        maps.put("双人成行",50);
        System.out.println(maps);

        //2.清空集合
        //maps.clear();
        //3.判断集合是否为空,空为true
        maps.isEmpty();
        System.out.println(maps.isEmpty());
        //4.根据键获取值:找不到键值,为null
        Integer value = maps.get("神极限");
        //5.根据键删除元素:会返回删除值
        Integer value2 = maps.remove("黄金头皮屑");
        //6.是否包含键或值
        maps.containsKey("机车");
        maps.containsValue(50);
        //7.获取全部键集合,返回set集合
        Set<String> keys = maps.keySet();
        System.out.println(keys);
        //8.获取全部值集合,返回Collection集合
        Collection<Integer> values = maps.values();

        //9.集合元素
        maps.size();

        //10.合并map集合
        HashMap<String, Integer> map1 = new HashMap<>();
        map1.put("极限全能X",99);
        map1.put("医生",2);
        HashMap<String, Integer> map2 = new HashMap<>();
        map2.put("全能X",1);
        map2.put("医生2",2);
        //map2集合全部添加到map1;map2不变
        map1.putAll(map2);
    }
}

3.Map集合遍历方式

3.1键找值

/**
 * 键找值
 */
public class MapDemo01 {
    public static void main(String[] args) {
        HashMap<String, Integer> maps = new HashMap<>();
        maps.put("神极限",100000);
        maps.put("黄金头皮屑",100);
        maps.put("极限全能X",99);
        maps.put("医生",2);
        maps.put("机车",2);
        maps.put("医生剑士",50);
        Set<String> sets = maps.keySet();
        for (String set : sets) {
            Integer value = maps.get(set);
            System.out.println(set+"=="+value);
        }
    }
}

3.2键值对

/**
 * 键值对
 */
public class MapDemo02 {
    public static void main(String[] args) {
        HashMap<String, Integer> maps = new HashMap<>();
        maps.put("神极限",100000);
        maps.put("黄金头皮屑",100);
        maps.put("极限全能X",99);
        maps.put("医生",2);
        maps.put("医生剑士",50);
        //{机车=2, 极限全能X=99, 黄金头皮屑=100, 神极限=100000, 医生剑士=50, 医生=2}
        System.out.println(maps);
        /**
         * foreach遍历map集合,map集合的键值对元素没有直接类型,不可以直接用
         * 调用map方法 entrySet把Map集合转成Set集合形式,
         * Set<Map.Entry<String, Integer>> entries = maps.entrySet();
         * interface Entry<K,V> 
         * {(机车=2), (极限全能X=99), (黄金头皮屑=100)}
         * 此时可以 foreach遍历
         */
        Set<Map.Entry<String, Integer>> entries = maps.entrySet();
        for(Map.Entry<String,Integer> entry : entries){
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key+"=="+value);
        }
    }
}

3.3 lambda表达式

/**
 * lambda
 */
public class MapDemo03 {
    public static void main(String[] args) {
        HashMap<String, Integer> maps = new HashMap<>();
        maps.put("神极限",100000);
        maps.put("黄金头皮屑",100);
        maps.put("极限全能X",99);
        maps.put("医生",2);
        maps.put("医生剑士",50);
        //{机车=2, 极限全能X=99, 黄金头皮屑=100, 神极限=100000, 医生剑士=50, 医生=2}
        System.out.println(maps);
        /**
         * 内部原理采用了上方键值对的原理
         */
        maps.forEach(new BiConsumer<String, Integer>() {
            @Override
            public void accept(String key, Integer value) {
                System.out.println(key+"=="+value);
            }
        });

        //lambda简化
        maps.forEach((key,value) -> System.out.println(key+"=="+value));
    }
}

4.案例统计人数

/**
 * 统计投票人数
 */
public class MapTest {
    public static void main(String[] args) {
        String[] selects = {"A","B","C","D"};
        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < 80; i++) {
            int x = random.nextInt(4);
            sb.append(selects[x]);
        }

        Map<Character, Integer> maps = new HashMap<>();
        for (int i = 0; i < sb.length(); i++) {
            char c = sb.charAt(i);
            if (!maps.containsKey(c)){
                maps.put(c,1);
            }
            Integer value = maps.get(c) + 1;
            maps.put(c,value);
        }
        //{A=25, B=19, C=24, D=16}
        System.out.println(maps);
    }
}

5.HashMap特点

  • Map的一个实现类,特点是由键决定,无序,不重复,无索引;
  • HashMap和HashSet底层原理一样,都是哈希表结构,只是HashMap每个元素包含两个值而已
  • 实际:Set系列集合底层就是Map实现的,只是Set集合元素只要键数据,不要值数据;
  • 依赖hashCode方法和equals方法保证键的唯一;
  • 键要存储自定义对象,需要重写hashCode方法和equals方法
  • 基于哈希表,增删改查性能都较好

6.LinkedHashMap集合概述和特点

  • 由键决定:有序,不重复,无索引
  • 有序指保证存储和取出的元素顺序一致;
  • 原理:底层结构依然是哈希表,只是每个键值对对元素又额外多了一个双链表的机制记录存储顺序;

7.TreeMap集合概述特点

  • 由键决定:可排序,不重复,无索引;
  • 可排序:按照键数据的大小默认排序,只能对键排序;
  • 注意:TreeMap集合一定要排序,可以默认,也可以指定规则排序
  • TreeSet底层基于TreeMap

十五、集合嵌套

/**
 * 统计投票人数
 * 嵌套循环
 */
public class MapTest02 {
    public static void main(String[] args) {
        //1.记录每一个学生的选择情况,每人可以选多个
        HashMap<Integer, List<String>> data = new HashMap<>();

        String[] selects = {"A","B","C","D"};
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            //2.记录每个人选择
            ArrayList<String> select = new ArrayList<>();
            int x = random.nextInt(4)+1;
            for (int j = 0; j < x; j++) {
                select.add(selects[j]);
            }
            data.put(i,select);
        }
        //{0=[A, B, C], 1=[A, B, C], 2=[A], 3=[A]}
        System.out.println(data);

        //3.统计每个景点选择人数
        Map<String , Integer> maps = new HashMap<>();

        //4.提取所有人选择的值
        //[[A, B, C], [A, B, C],[A, B, C]]
        Collection<List<String>> values = data.values();
        System.out.println(values);

        for (List<String> value : values) {
            for (String s : value) {
                if (maps.containsKey(s)){
                    Integer v = maps.get(s)+1;
                    maps.put(s,v);
                }else {
                    maps.put(s,1);
                }
            }
        }

        //{A=10, B=7, C=5, D=2}
        System.out.println(maps);

        //提取统计好的值,比较出人数最多的值
        Collection<Integer> valuesTwo = maps.values();
        Integer[] nums = new Integer[valuesTwo.size()];
        int n=0;
        for (Integer integer : valuesTwo) {
            nums[n] = integer;
            n++;
        }
        Integer Max = nums[0];
        for (int i = 1; i < nums.length; i++) {
            if (Max<nums[i]){
                Max = nums[i];
            }
        }

        //通过遍历,寻找最大值的的键,输出人多的最想去的地方
        Set<Map.Entry<String, Integer>> entries = maps.entrySet();
        for (Map.Entry<String, Integer> entry : entries) {
            if (entry.getValue()==Max){
                System.out.println("想去人数最多的地点是:"+entry.getKey());
            }
        }
    }
}

十六、不可变集合

1.简述

  • 集合不可被修改;
  • 集合的数据项在创建时被提供,整个生命周期中不可被更改,否则报错。

2.为什么创建不可变集合

  • 如果某个数据不可被修改,把它防御性的拷贝到不可变集合中;
  • 当集合对象被不可信的库调用时,不可变形式是安全的;
    • 敏感信息给第三方看,但不允许修改

3.创建不可变集合

  • (JDK9新特性)在List、Set、Map接口中,都存在of方法,都可创建一个不可变的集合

RecordDate:2021/08/14