集合之Collection

Collection集合

集合概述

集合和数组都是容器

数组的特点

  • 数组在定义完成并启动后,类型确定,长度固定。
  • 在进行增删数据的时候,数组是不太合适的,增删数据都需要放弃原有数组或移位

数组适合的场景

  • 当业务数据的个数是固定的,且都是同一批数据类型的时候,可以采取定义数组存储

集合是Java中存储对象的一种容器

集合的特点

  • 集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合更像气球
  • 集合非常适合做元素的增删操作。
  • 注意:集合中只能存储引用类型的数据,如果要存储基本数据类型可以选用包装类。

集合适合的场景

  • 数据的个数不确定,且需要进行增删元素的时候。

Collection集合的体系特点

集合的体系结构

  • Collection单列集合,每个元素(数据) 只包含一个值
  • Map双列集合,每个元素包含两个值 (键值对)

Collection集合特点

  • List系列集合:添加的元素是有序的,可重复的、有索引
    • ArrayList、LinkedList:有序、可重复、有索引
  • Set系列集合:添加的元素是无序的、不重复、无索引
    • HashSet:无序、不重复、无索引;LinkedHashSet:有序、不重复、无索引
    • TreeSet:按照大小默认升序排序、不重复、无索引。
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

/**
    明确Collection集合体系的特点
 */
public class CollectionDemo1 {
    public static void main(String[] args) {
        //有序,可重复、有索引
        Collection list = new ArrayList();
        list.add("java");
        list.add("java");
        list.add("Mybatis");
        list.add(23);
        list.add(false);
        list.add(false);
        list.add("中");
        System.out.println(list);
        //无序,不可重复,无索引
        Collection list1 = new HashSet();
        list1.add("java");
        list1.add("java");
        list1.add("Mybatis");
        list1.add(23);
        list1.add(false);
        list1.add(false);
        list1.add("中");
        System.out.println(list1);
    }
}
output:
[java, java, Mybatis, 23, false, false, 中]
[java, false, 23, 中, Mybatis]

集合对于泛型的支持

  • 集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型。
Collection<String> lists = new ArrayList<String>();
Collection<String> lists = new ArrayList<>();//从jdk1.7开始后面的泛型类型申明可以省略不写

注意:集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象

Collection集合常用API

Collection集合

  • Collection是单例集合的祖宗接口,它是功能是全部单列集合都可以继承使用的。

Collection API如下:

方法名 说明
public boolean add(E e) 把给定的对象添加到当前集合中
public void clear() 清空集合中的所有元素
public boolean remove(E e) 把给定的对象在当前集合中删除
public boolean contains(Object o) 判断当前集合中是否含有给定的对象
public boolean isEmpty() 判断当前集合是否为空
public int size() 返回集合中元素的个数
public Object[] toArray() 把集合中的元素存储到数组中
public boolean addAll(Collection<? extends E> c) 将指定集合中的所有元素添加到此集合(可选操作)

Collection集合的遍历方式

方式一:迭代器

迭代器遍历概述

  • 遍历就是一个一个的把容器中的元素访问一遍。
  • 迭代器在Java中的代表是Iterator,迭代器是集合专用的遍历方式

Collection集合获取迭代器

方法名 说明
Iterator iterator() 返回此集合中元素的迭代器对象,该迭代器对象默认指向当前集合的0索引

Iterator中的常用方法

方法名称 说明
boolean hasNext() 询问当前位置是否有元素存在,存在则返回true,否则返回false
E next() 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class IteratorDemo1 {
    public static void main(String[] args) {
        Collection<String> lists = new ArrayList<>();
        lists.add("小昭");
        lists.add("赵敏");
        lists.add("素素");
        lists.add("灭绝");
        System.out.println(lists);
        //获取迭代器对象
        Iterator<String> it = lists.iterator();

        //循环遍历集合中的元素
        while (it.hasNext()){
            String ele = it.next();
            System.out.println(ele);
        }
    }
}
output:
[小昭, 赵敏, 素素, 灭绝]
小昭
赵敏
素素
灭绝

小结

  1. 迭代器的默认位置在哪里?
  • Iterator iterator():得到迭代器对象,默认指向当前集合的索引0位置。
  1. 迭代器如果元素越界会出现什么问题。
  • 会出现NoSuchElementException异常。

方式二:foreach/增强for循环

增强for循环

  • 增强for循环:既可以遍历集合,也可以遍历数组。
  • 他是JDK5之后出现的,其内部原理是一个Iterator迭代器,遍历集合相当于是迭代器的简化写法。
  • 实现Iterable接口的接口才可以使用迭代器和增强for,Collection接口已经实现了Iterable接口

格式

for(元素数据类型 变量名 : 数组或者Collection集合){
		//在此处使用变量即可,该变量就是元素
}

举例说明

Collection<String> list = new ArrayList<>();
...
for(String ele : list){
	System.out.println(ele);
}

方式三:lambda表达式

Lambda表达式遍历集合

  • 得益于JDK 8 开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。

Collection结合Lambda遍历的API

方法名 说明
default void forEach(Consumer<? super T> action): 结合lambda遍历集合
public class IteratorDemo1 {
    public static void main(String[] args) {
        Collection<String> lists = new ArrayList<>();
        lists.add("小昭");
        lists.add("赵敏");
        lists.add("素素");
        lists.add("灭绝");
        System.out.println(lists);

        //lambda表达式循环遍历集合中的元素
     /*  lists.forEach(new Consumer<String>() {
           @Override
           public void accept(String s) {
               System.out.println(s);
           }
       });*/

       /* //简化 Lambda
        lists.forEach((String s) ->{
                System.out.println(s); });*/

    /*   //在简化
        lists.forEach( s -> System.out.println(s));*/

        //新形式
        lists.forEach(System.out::println);
    }
}
output:
[小昭, 赵敏, 素素, 灭绝]
小昭
赵敏
素素
灭绝

记住这个

 lists.forEach((String s) ->{
                System.out.println(s); 
 });

Collection集合存储自定义 类型的对象

案例:影片信息在程序中的表示:

需求:

  • 某影院系统需要在后台存储三部电影,然后依次展示出来。

分析:

  1. 定义一个电影类,定义一个集合存储电影对象
  2. 创建3个电影对象,封装相关数据,把3个对象存入到集合中去
  3. 遍历集合中的3个对象,输出相关信息
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class TestDemo {
    public static void main(String[] args) {
        //定义一个电影类,定义一个集合存储电影对象
        Collection<Movie> movies = new ArrayList<>();
        movies.add(new Movie("《你好,李焕英》",9.5,"张小斐,贾玲,沈腾,陈赫"));
        movies.add(new Movie("《唐人街探案》",8.5,"王宝强,刘昊然,美女"));
        movies.add(new Movie("《刺杀小说家》",8.6,"刘德华,肖央"));

        //遍历集合中的元素
        movies.forEach(movie -> System.out.println("片名:"+movie.getName()
        +"评分:"+movie.getScore()+"演员:"+movie.getActor()));

        System.out.println("----------------------");
        for (Movie movie : movies) {
            System.out.println("片名:"+movie.getName()+
                    "评分:"+movie.getScore()+
                    "主演:"+movie.getActor());
        }
        System.out.println("----------------------");
        Iterator<Movie> it = movies.iterator();
        while (it.hasNext()){
            Movie ele = it.next();
            System.out.println("片名:"+ele.getName()+"评分:"+ele.getScore()+"主演:"+ele.getActor());
        }
    }
}
output:
片名:《你好,李焕英》评分:9.5演员:张小斐,贾玲,沈腾,陈赫
片名:《唐人街探案》评分:8.5演员:王宝强,刘昊然,美女
片名:《刺杀小说家》评分:8.6演员:刘德华,肖央
----------------------
片名:《你好,李焕英》评分:9.5主演:张小斐,贾玲,沈腾,陈赫
片名:《唐人街探案》评分:8.5主演:王宝强,刘昊然,美女
片名:《刺杀小说家》评分:8.6主演:刘德华,肖央
----------------------
片名:《你好,李焕英》评分:9.5主演:张小斐,贾玲,沈腾,陈赫
片名:《唐人街探案》评分:8.5主演:王宝强,刘昊然,美女
片名:《刺杀小说家》评分:8.6主演:刘德华,肖央

集合中存储的是元素对象的地址

常见数据结构

数据结构概述、栈、队列

  • 数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的。
  • 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。

栈数据结构的执行特点

  • 后进先出,先进后出
    • 数据进入栈模型的过程称为:压/进栈
    • 数据离开栈模型的过程称为:弹/出栈
      • 栈顶元素
      • 栈低元素

常见数据结构之队列

  • 先进先出,后进后出
    • 数据从后端进入队列模型的过程称为:入队列
    • 数据从前端进入队列模型的过程称为:出队列

数组

常见数据结构之数组——是一种查询快,增删慢的模型

  • 查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中是连续存储的).
  • 删除效率低:要将原始数据删除,同时后面每个数据前移
  • 添加效率极低:添加位置后的每个数据后移,再添加元素

链表的特点

  • 链表中的元素是在内存中不连续存储的,每个元素节点包含数据值和下一个元素的地址。

链表的特点

  • 链表中的元素是游离存储的,每个元素节点包含数据值和下一个元素的地址。
  • 链表查询慢。无论查询哪个数据都要从头开始找
  • 链表增删相对快

二叉树、二叉查找树

二叉树

  • 只能有一个根节点 ,每个节点最多支持2个直接子节点
  • 节点的度:节点拥有的子树的个数,二叉树的度不大于2叶子节点 度为0的节点,也称为终端节点
  • 高度:叶子节点的高度为1,叶子节点的父节点高度为2,以此类推,根节点的高度最高。
  • 层:根节点在第一层,以此类推
  • 兄弟节点:拥有共同父节点的节点互称为兄弟节点

二叉查找树:又称二叉排序树或者二叉搜索树

特点:

  • 每一个节点上最多有两个子节点
  • 左子树上所有节点的值都小于根节点的值
  • 右子树上所有节点的值都大于根节点的值

平衡二叉树

  • 平衡二叉树是在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能

平衡二叉树的要求

  • 任意节点的左右两个子树的高度查不超过1,任意节点的左右两个子树都是一颗平衡二叉树

红黑树

红黑数概述

  • 红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构
  • 1972年出现,当时被称之为平衡二叉B树,1978年被修改为如今的"红黑树"
  • 每一个节点可以使红或者黑,红黑树不是通过高度平衡的,它的平衡是通过”红黑规则“进行实现的

红黑节点

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

总结

  • 队列:先进先出,后进后出
  • 栈:后进先出,先进后出
  • 数组:内存连续区域,查询快,增删慢
  • 链表:元素是游离的,查询慢,首尾操作极快。
  • 二叉树:永远只有一个根节点,每个节点不超过2个子节点的树
  • 查找二叉树:小的左边,大的右边,但是可能树很高,查询性能变差。
  • 平衡二叉树:让数的高度差不大于1,增删改查都提高了。
  • 红黑叔:就是基于红黑规则实现了自平衡的排序二叉树。

List系列集合

List系列集合特点、特有API

  • ArrayList、LinkedList:有序,可重复,有索引。
  • 有序:存储和取出的元素顺序一致
  • 有索引:可以通过索引操作元素
  • 可重复:存储的元素可以重复

List集合特有方法

  • List集合因为支持索引,所以多了很多索引操作的独特API,其他Collection的功能List也都继承了
方法名称 说明
void add(int index,E element) 在此集合中指定的位置插入指定的元素
E remove(int index) 删除指定索引处的元素,返回被修改的元素
E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素

List的实现类的底层原理

  • ArrayList底层是基于数组实现的,根据查询元素快,增删相对慢。
  • LinkedList底层是基于双链表的,查询元素慢,增删首尾元素是非常快的。

List集合的遍历方式小结

  1. 迭代器
  2. 增强for循环
  3. Lambda表达式
  4. for循环(因为List集合存在索引)
public class ListDemo {
    public static void main(String[] args) {
        List<String> lists = new ArrayList<>();
        lists.add("java1");
        lists.add("java2");
        lists.add("java3");

        /**(1) for循环*/
        for (int i = 0; i < lists.size(); i++) {
            String ele = lists.get(i);
            System.out.println(ele);
        }

        /** (2)迭代器*/
        Iterator<String> it = lists.iterator();
        while (it.hasNext()){
            String rs = it.next();
            System.out.println(rs);
        }

        /**(3)增强for*/
        for (String list : lists) {
            System.out.println(list);
        }

        /**(4) Lambda表达式*/
        lists.forEach(ele -> System.out.println(ele));
    }
}

ArrayList集合的底层原理

  • ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。
  • 第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10 的数组。

LinkedList集合的底层原理

LinkedList的特点

  • 底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API

LinkedList集合的特有功能

方法名称 说明
public void addFirst(E e) 在该列表的开头插入指定的元素
public void addLast(E e) 将指定的元素追加到此列表的末尾
public E getFirst() 返回此列表中的第一个元素
public E getLast() 返回此列表中的最后一个元素
public E removeFirst() 从此列表中删除并返回第一个元素
public E removeLast() 从此列表中删除并返回最后一个元素
public class LinkedListDemo1 {
    public static void main(String[] args) {
        //栈
        LinkedList<String> stack = new LinkedList<>();
        //压栈,入栈
        stack.push("第1颗子弹!");//push就是包装了addFirst()
        stack.push("第2颗子弹!");
        stack.push("第3颗子弹!");
        stack.push("第4颗子弹!");

        //出栈,弹栈
        System.out.println(stack.pop());//pop就是包装了removeFirst()
        System.out.println(stack.pop());
        System.out.println(stack.pop());
        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);

        //出队
        System.out.println(queue.removeFirst());
        System.out.println(queue.removeFirst());
        System.out.println(queue.removeFirst());
        System.out.println(queue);
    }
}
output:
第4颗子弹!
第3颗子弹!
第2颗子弹!
[第1颗子弹!]
[1号, 2号, 3号, 4号]
1号
2号
3号
[4号]

补充知识:集合的并发修改异常问题

问题引出

  • 当我们从集合中找出某个元素并删除的时候可能会出现一种并发修改异常问题

哪些遍历存在问题?

  • 迭代器遍历集合且直接用集合删除元素的时候可能出现
  • 增强for循环遍历集合且直接用集合删除元素的时候可能出现

哪种遍历且删除元素不出问题

  • 迭代器遍历集合但是用迭代器自己的删除方法操作可以解决
  • 使用for循环遍历并删除元素不出出现这个问题(倒着遍历删,或者正着删i--);

补充知识:泛型深入

泛型的概述和优势

泛型概述

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

泛型的好处

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

泛型可以在很多地方进行定义

  • 类后面——》 泛型类
  • 方法申明上——》 泛型方法
  • 接口后面——》 泛型接口

自定义泛型类

泛型类的概述

  • 定义类的同时订定义了泛型的类就是泛型类

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

    • 范例:	public class MyArrayList<T>{}
      
  • 此处的泛型变量T 可以随便写为任意标识,常见如E、T、K、V等。

  • 作用:编译阶段可以指定数据类型,类似于集合的作用。

  • 案例

    • 模拟ArrayList集合自定义一个集合MyArrayList集合,完成添加和删除功能的泛型设计即可。
    public class MyArrayList<E> {
        private ArrayList list = new ArrayList();
    
        public  void add(E e){
            list.add(e);
        }
    
        public void remove(E e){
            list.remove(e);
        }
    
        @Override
        public String toString() {
            return list.toString();
        }
    }
    public class Test {
        public static void main(String[] args) {
            MyArrayList<String> list = new MyArrayList<>();
            list.add("java");
            list.add("323");
            list.add("中国");
    
            list.remove("java");
            System.out.println(list);
        }
    }
    output:
    [323, 中国]
    

泛型类的原理

  • 把出现泛型变量的地方全部替换成传输的真是数据类型。

自定义泛型方法

泛型方法的概述

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

案例

  • 给你任何一个类型的数组,都能返回它的内容。也就是实现Arrays.toString(数组)的功能!
public class GenericDemo {
    public static void main(String[] args) {
        String[] names = {"紫霞", "至尊宝", "白晶晶"};
        printArray(names);

        Integer[] ages = {22, 33, 44};
        printArray(ages);
    }

    public static <T> void printArray(T[] arr) {
        if (arr != null) {
            StringBuilder sb = new StringBuilder("[");
            for (int i = 0; i < arr.length; i++) {
                sb.append(arr[i]).append(i == arr.length - 1 ? "" : ",");
            }
            sb.append("]");
            System.out.println(sb);
        } else {
            System.out.println(arr);
        }
    }
}
output:
[紫霞,至尊宝,白晶晶]
[22,33,44]

泛型方法的原理

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

自定义泛型接口

泛型接口的概述

  • 使用了泛型定义的接口就是泛型接口
  • 泛型接口的格式:修饰符 interface 接口名称<泛型变量>{}

​ 范例: public interface Data{}

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

案例:

  • 教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作

泛型接口的原理

  • 实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。

泛型通配符、上下限

通配符:?

  • ?可以在“使用泛型” 的时候代表一切类型。
  • E T K V 实在定义泛型的时候使用的。

泛型通配符:案例导学

  • 开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。
public class GenericDemo {
    public static void main(String[] args) {
        ArrayList<BENZ> benzs = new ArrayList<>();
        benzs.add(new BENZ());
        benzs.add(new BENZ());
        benzs.add(new BENZ());
        go(benzs);

        ArrayList<BMW> bmws = new ArrayList<>();
        bmws.add(new BMW());
        bmws.add(new BMW());
        bmws.add(new BMW());
        go(bmws);
        ArrayList<Dog> dogs = new ArrayList<>();
        dogs.add(new Dog());
        //go(dogs);
    }

    public static void go(ArrayList<? extends Car> cars){

    }
}
class Dog{}

class BENZ extends Car{
}

class BMW extends Car{
}
//父类
class Car{
}

注意:

  • 虽然BMW和BENZ都继承了Car但是ArrayList和|ArrayList与ArrayList没有关系的!

泛型的上下限

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

Set系列集合

Set系列集合概述

Set系列集合特点

  • 无序:存取顺序不一致
  • 不重复:可以去除重复
  • 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。

Set实现类特点

  • HashSet:无序,不重复、无索引
  • LinkedHashSet:有序、不重复、无索引
  • TreeSet:排序、不重复、无索引

Set集合的功能基本上与Collection的API一致

HashSet元素无序的底层原理:哈希表

HashSet底层原理

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

哈希表的组成

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

哈希值

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

Object类的API

  • public int hashCode():返回对象的哈希值。

对象的哈希值特点

  • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
  • 默认情况下,不同对象的哈希值是不同的。
public class SetDemo02 {
    public static void main(String[] args) {
        String name = "csl";
        System.out.println(name.hashCode());
        System.out.println(name.hashCode());

        String name2 = "csl2";
        System.out.println(name2.hashCode());
        System.out.println(name2.hashCode());
    }
}
output:
98812
98812
3063222
3063222

HashSet 1.7 版本原理解析:数组+链表+(结合哈希算法)

  1. 创建一个默认长度为16的数组
  2. 根据数组元素的哈希值数组的长度求余计算出应存入的位置(哈希算法)
  3. 判断当前位置是否为null,如果是null直接存入
  4. 如果位置不为null,表示有元素,则调用equals方法比较
  5. 如果一样,则不存,如果不一样,则存入数组
    1. JDK7 新元素占老元素位置,指向老元素
    2. JDK 8 新元素挂在老元素下面

结论:哈希表是一种 对于增删改查数据性能都比较好的结构

JDK 1.8版本开始HashSet原理解析

  • 底层结构:哈希表(数组、链表、红黑树结合体)
  • 当挂在元素下面的数据过多时,查询性能低,从JDK8 开始后,当链表长度超过8的时候,自动转成红黑树。

结论:JDK8开始后,哈希表对于红黑树的引入进一步提高了操作数据的性能

哈希表的详细流程

  1. 创建一个默认长度为16,默认加载因子为0.75的数组,数组名table
  2. 根据元素的哈希值跟数组的长度取余计算出应存入的位置
  3. 判断当前位置是否为null,为null则存入,否则表示有元素,调用equals方法比较属性值,如果一样则不存,否,则存入数组。
  4. 当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍。

HashSet元素去重复的底层原理

  1. 创建一个默认长度为16的数组
  2. 根据数组元素的哈希值数组的长度求余计算出应存入的位置(哈希算法)
  3. 判断当前位置是否为null,如果是null直接存入
  4. 如果位置不为null,表示有元素,则调用equals方法比较
  5. 如果一样,则不存,如果不一样,则存入数组

结论:如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象equals和hashCode方法

案例

需求:创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合,要求:学生对象的成员变量值相同,我们就认为是一个对象

分析:

  1. 定义学生类,创建HashSet集合对象,创建学生对象
  2. 把学生添加到集合
  3. 在学生类中重写两个方法,hashCode()和equals(),自动生成即可
  4. 遍历集合增强for

实现类:LinkedHashSet

LinkedHashSet集合概述和特点

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

实现类:TreeSet

TreeSet集合概述和特点

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

TreeSet 集合默认的规则

  • 对于数值类型:Integer,Double,官方默认按照大小进行升序排序。
  • 对于字符串类型:默认按照首字母的编号进行升序。
  • 对于自定义类型如Student对象,TreeSet无法直接排序。需要制定排序规则。

自定义排序规则

  • TreeSet集合存储对象的时候有2种方式可以设计自定义比较规则

方式一

  • 让自定义的类(如学生类)实现Comparator接口重写里面的compareTo方法来定制比较规则

方式二

  • TreeSet 集合有参构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则

注意:如果TreeSet集合存储对象有实现比较规则,集合也有自带比较器,默认使用集合自带的比较器排序

public class Car implements Comparable<Car> {
    private String name;
    private double price;
    private int catAge;

    public Car() {
    }

    public Car(String name, double price, int catAge) {
        this.name = name;
        this.price = price;
        this.catAge = catAge;
    }

    public String getName() {
        return name;
    }

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

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public int getCatAge() {
        return catAge;
    }

    public void setCatAge(int catAge) {
        this.catAge = catAge;
    }

    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", price=" + price +
                ", catAge=" + catAge +
                '}';
    }

    @Override
    public int compareTo(Car o) {
        //return this.catAge - o.catAge;//升序,相同车龄就会
        return this.catAge - o.catAge >= 0 ? 1 : -1;
    }
}


public class SetDemo5 {
    public static void main(String[] args) {
        Set<Integer> sets = new TreeSet<>();//不重复、无索引、可排序
        sets.add(23);
        sets.add(24);
        sets.add(22);
        sets.add(12);
        System.out.println(sets);

        Set<String> sets1 = new TreeSet<>();
        sets1.add("Java");
        sets1.add("java");
        sets1.add("About");
        sets1.add("My");
        sets1.add("中");
        sets1.add("baba");
        System.out.println(sets1);

       /* Set<Car> cars = new TreeSet<>(new Comparator<Car>() {
            @Override
            public int compare(Car o1, Car o2) {
                return o2.getCatAge() -o1.getCatAge();//降序
            }
        });*/
//        Set<Car> cars = new TreeSet<>(( o1,  o2)->
//                 o2.getCatAge() -o1.getCatAge());//lambda简化后
        //注意:浮点型建议直接使用Double.compare进行比较
        Set<Car> cars = new TreeSet<>((o1, o2) -> Double.compare(o1.getPrice(),o2.getPrice()));


        Car car = new Car("宝马",22.2,2);
        Car car1 = new Car("奥迪",33.2,4);
        Car car2 = new Car("法拉利",44.2,1);
        Car car3 = new Car("奔驰",44.2,1);
        cars.add(car);
        cars.add(car1);
        cars.add(car2);
        cars.add(car3);
        System.out.println(cars);
    }
}
output:
[12, 22, 23, 24]
[About, Java, My, baba, java, 中]
[Car{name='宝马', price=22.2, catAge=2}, Car{name='奥迪', price=33.2, catAge=4}, Car{name='法拉利', price=44.2, catAge=1}]

总结

  1. 如果希望元素可以重复、又有索引,索引查询要快?
  • 用ArrayList集合,基于数组的。(用的最多)
  1. 如果希望元素可以重复,又有索引增删首尾操作快?
  • 用LinkedList集合,基于链表的
  1. 如果希望增删改查都快,但是元素不重复、无序、无索引?
  • 用HashSet集合,基于哈希表的。
  1. 如果希望增删改查都快,但是元素不重复、有序、无索引
  • 用LinkedHashSet集合,基于哈希表和双链表
  1. 如果要对对象进行排序
  • 用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。

可变参数

  • 可变参数用在形参中可以接受多个数据
  • 可变参数的格式:数据类型...参数名称

可变参数的作用

  • 传输参数非常灵活,方便。可以不传输参数,可以传输1个或者多个,也可以传输一个数组。
  • 可变参数在方法内部其实就是一个数组
public class MethodDemo {
    public static void main(String[] args) {
        sum();//1.不传输
        sum(1);//传输1个参数
        sum(10,22,33);//传输多个参数
        sum(new int[]{1,2,4,5,6});//传输一个数组
    }

    public static void sum(int... nums){
        //注意:可变参数在方法内部其实就是一个数组,nums
        System.out.println("元素个数:"+nums.length);
        System.out.println("元素内容:"+ Arrays.toString(nums));
    }
}
output:
元素个数:0
元素内容:[]
元素个数:1
元素内容:[1]
元素个数:3
元素内容:[10, 22, 33]
元素个数:5
元素内容:[1, 2, 4, 5, 6]

可变参数的注意事项

    1. 一个形参列表中可变参数只能有一个
    1. 可变参数必须放在形列表的最后面

集合工具类Collections

Collections集合工具类

  • java.utils.Collections:是集合工具类
  • 作用:Collections并不属于集合,是用来操作集合的工具类

Collections的常用API

方法名称 说明
public static boolean addAll(Collection<? super T> c, T... elements) 给集合对象批量添加元素
public static void shuffle(List<?> list) 打乱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,"令狐冲","鸠摩智","任盈盈","独孤剑");
        System.out.println(names);

        //2.public static void shuffle(List<?> list):打乱集合顺序
        Collections.shuffle(names);
        System.out.println(names);
    }
}
output:
[令狐冲, 鸠摩智, 任盈盈, 独孤剑]
[任盈盈, 鸠摩智, 独孤剑, 令狐冲]

Collections排序相关的API

  • 适用范围:只能对于List集合的排序。

排序方式1:

static <T extends Comparable<? super T>>void
sort(List<T> list)

根据其元素的自然顺序,将指定列表按升序 排序。

     //3.public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。(排值特性的元素)
        List<Integer> list = new ArrayList<>();
        Collections.addAll(list,22,11,25,1,4);
        System.out.println(list);
        Collections.sort(list);
        System.out.println(list);
}
output:
[22, 11, 25, 1, 4]
[1, 4, 11, 22, 25]

排序方式2:

static <T> void
sort(List<T> list, Comparator<? super T> c)

根据指定比较器产生的顺序对指定列表进行排序。

Collections体系的总和案例:

需求:

在启动房间是时候,应该提前准备好54张牌,完成洗牌、发牌、牌排序、逻辑。

分析:

  1. 当系统启动的同时需要准备好数据的时,就可以用静态代码块了。
  2. 洗牌就是打乱牌顺序
  3. 定义三个玩家、依次发出51张牌
  4. 给玩家牌进行排序
  5. 输出每个玩家的牌数据
public class Card {
    private String size;//牌的点数:"3","4","5","6","7","8","9","10","J","Q","K","A","2";
    private String color;//牌的花色:"♠","♥","♣","♦";
    private int index;//给定一个索引大小

    public Card() {
    }

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

    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 getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    @Override
    public String toString() {
        return size + color;
    }
}
public class GameDemo {

    /**
     * 1.定义一个静态的集合用来储存54张牌对象
     */
    public static List<Card> cards = new ArrayList<>();

    /**
     2.做牌:定义静态代码块初始化牌数据
     */
    static {
        //3.定义牌花色,个数确定、类型确定,使用数组
        String[] color = {"♠", "♥", "♣", "♦"};
        //4.定义点数
        String[] size = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
        //5.组合点数跟花色
        int index = 0;//记录牌的大小
        for (String s : size) {
            for (String s1 : color) {
                //6.创建对象来封装数据
                Card c = new Card(s, s1, index++);
                //7.存入到集合容器中去
                cards.add(c);
            }
        }
        //8.把大小王存入集合中
        Card c1 = new Card("", "小王", ++index);
        Card c2 = new Card("", "大王", ++index);

        Collections.addAll(cards, c1, c2);
        System.out.println(cards);
    }

    public static void main(String[] args) {
        //9.洗牌
        Collections.shuffle(cards);
        System.out.println("洗牌后:" + cards);

        //10.发牌,创建三个玩家,用集合存牌
        List<Card> zSan = new ArrayList<>();
        List<Card> liSi = new ArrayList<>();
        List<Card> zLiu = new ArrayList<>();
        //11.开始发牌(从集合中发出51张牌给三个玩家,剩余3张作为底牌)
        for (int i = 0; i < cards.size() - 3; i++) {
            //先拿到牌当前对象
            Card c = cards.get(i);
            if (i % 3 == 0) {
                zSan.add(c);
            } else if (i % 3 == 1) {
                Collections.addAll(liSi, c);
            } else if (i % 3 == 2) {
                Collections.addAll(zLiu, c);
            }
        }
        //12.拿到最后三张牌对象(把最后三张牌截取成一个子集合)
        List<Card> lastThreeCard = cards.subList(cards.size()-3,cards.size());

        //13.给玩家的牌排序(从小到大)
        sortCard(zSan);
        sortCard(liSi);
        sortCard(zLiu);
        //14.输出玩家的牌
        System.out.println(zSan);
        System.out.println(liSi);
        System.out.println(zLiu);
        System.out.println("底牌是:"+lastThreeCard);
    }

    public static void sortCard(List<Card> cards){
        Collections.sort(cards,(o1,  o2) ->o1.getIndex()-o2.getIndex());

    }
}
output:
[3♠, 3♥, 3♣, 3♦, 4♠, 4♥, 4♣, 4♦, 5♠, 5♥, 5♣, 5♦, 6♠, 6♥, 6♣, 6♦, 7♠, 7♥, 7♣, 7♦, 8♠, 8♥, 8♣, 8♦, 9♠, 9♥, 9♣, 9♦, 10♠, 10♥, 10♣, 10♦, J♠, J♥, J♣, J♦, Q♠, Q♥, Q♣, Q♦, K♠, K♥, K♣, K♦, A♠, A♥, A♣, A♦, 2♠, 2♥, 2♣, 2♦, 小王, 大王]
洗牌后:[K♦, 5♣, 4♣, K♠, 8♠, A♥, 5♥, Q♥, 10♦, A♦, K♣, 2♣, Q♦, 3♠, 6♣, 8♥, 4♥, 2♠, 2♦, 7♣, 9♠, 5♦, Q♠, 3♥, Q♣, J♦, 6♠, 9♦, 3♦, 6♦, 10♠, 7♥, 10♥, J♥, 7♠, 3♣, 8♦, A♠, 4♦, 2♥, J♣, 8♣, J♠, 5♠, 大王, K♥, 10♣, 7♦, 9♥, A♣, 小王, 4♠, 9♣, 6♥]
[5♥, 5♦, 8♥, 8♦, 9♥, 9♦, 10♠, J♠, J♥, Q♣, Q♦, K♠, K♥, K♦, A♦, 2♥, 2♦]
[3♠, 3♦, 4♥, 5♠, 5♣, 7♠, 7♥, 7♣, 8♠, 10♣, J♣, J♦, Q♠, Q♥, K♣, A♠, A♣]
[3♥, 3♣, 4♣, 4♦, 6♠, 6♣, 6♦, 7♦, 8♣, 9♠, 10♥, 10♦, A♥, 2♠, 2♣, 小王, 大王]
底牌是:[4♠, 9♣, 6♥]
posted @ 2022-01-14 21:15  tryAgainCs  阅读(135)  评论(0)    收藏  举报