Java基础 - 9 - 集合进阶(一)

集合是一种容器,用来装数据的,类似于数组,但集合的大小可变,开发中非常常用

为了满足不同的业务场景需求,Java除了ArrayList还提供了很多不同特点的集合给我们选择

一. 集合的体系结构

集合可以分为两类:Collection(单列集合)和Map(双列集合)

        · Collection代表单列集合,每个元素(数据)只包含一个值

        · Map代表双列集合,每个元素包含两个值(键值对)

二. Collection(单列集合)

2.1 Collection集合体系

2.2 Collection集合特点

· LIst系列集合:添加的元素是有序、可重复、有索引

        · ArrayList、LinkList:有序、可重复、有索引

· Set系列集合:添加的元素是无序、不重复、无索引

        · HashSet:无序、不重复、无索引

        · LinkedHashSet:有序、不重复、无索引

        · TreeSet:按照大小默认升序排序、不重复、无索引

//demo
public class demo {
    public static void main(String[] args) {
        //ArrayList:有序、可重复、有索引
        ArrayList<String> list = new ArrayList<>();
        list.add("java1");
        list.add("java2");
        list.add("java1");  //可重复
        list.add("java2");  //可重复
        System.out.println(list);  //[java1, java2, java1, java2]
        System.out.println(list.get(0));  //用索引值取内容  java1

        //HashSet:无序、不重复、无索引
        HashSet<String> set = new HashSet<>();
        set.add("java1");
        set.add("java2");
        set.add("java1");
        set.add("java2");
        System.out.println(set);  //[java2, java1]  不支持索引
    }
}

2.3 Collection的常用方法

        Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的

Collection的常用方法如下:

方法名说明
public boolean add(E e)把给定的对象添加到当前集合中
public void clear()清空集合中所有的元素
public boolean remove(E e)把给定的对象在当前集合中删除
public boolean contains(Object obj)判断当前集合中是否包含给定的对象
public boolean isEmpty()判断当前集合是否为空
public int size()返回集合中元素的个数
public Object[] toArray()把集合中的元素,存储到数组中
//demo
public class demo {
    public static void main(String[] args) {
        //Collection是接口,不能直接new(创建对象)
        Collection<String> c = new ArrayList<>();   //多态写法
        //public boolean add(E e):添加元素,添加成功返回true
        c.add("java1");
        c.add("java2");
        c.add("java1");
        c.add("java2");
        c.add("java3");
        System.out.println(c); //[java1, java2, java1, java2, java3]

        System.out.println(c.isEmpty()); //false
        System.out.println(c.size()); //5
        System.out.println(c.contains("java1")); //true(包含java1)
        System.out.println(c.remove("java1")); //true(删除成功)
        System.out.println(c); //[java2, java1, java2, java3]
        Object[] arr1 = c.toArray();
        System.out.println(Arrays.toString(arr1)); //[java2, java1, java2, java3]

        //public void clear():清空集合的元素
        c.clear();
        System.out.println(c); //[]

        //public boolean isEmpty():判断集合是否为空 为空返回true,不为空返回false
        System.out.println(c.isEmpty()); //true

        //public int size():获取集合的大小
        System.out.println(c.size()); //0

        //public boolean contains(Object obj):判断集合中是否包含某个元素
        System.out.println(c.contains("java1")); //false

        //public boolean remove(E e):删除某个元素,如果有多个重复元素,默认删除第一个重复元素
        System.out.println(c.remove("java1")); //false(没有java1元素)

        System.out.println("=====================");

        //public Object[] toArray():把集合转换成数组(因为不确定集合中的数据类型,所以转换成Object数组比较合理)
        Object[] arr2 = c.toArray();
        System.out.println(Arrays.toString(arr2));  //[]

        //如果非要把集合转换成字符串数组(要保证集合中的数据都是字符串,否则不能转)
        String[] arr3 = c.toArray(new String[c.size()]);
        System.out.println(Arrays.toString(arr3));

        System.out.println("=====================");

        //把一个集合的全部数据倒入另一个集合中去
        Collection<String> c1 = new ArrayList<>();
        c1.add("java1");
        c1.add("java2");
        Collection<String> c2 = new ArrayList<>();
        c2.add("java3");
        c2.add("java4");
        c1.addAll(c2);  //就是把c2集合的全部数据倒入到c1集合中去(相当于copy了一份c2的数据给c1)
        System.out.println(c1); //[java1, java2, java3, java4]
        System.out.println(c2); //[java3, java4]
    /*
        Collection<Integer> c3 = new ArrayList<>();
        c3.add(1);
        c1.addAll(c3);  //如果两个集合中存储的数据类型不一样,则不能倒入
    */
    }
}

2.4 Collection的遍历方式

· 为什么Collection的遍历不支持用普通的for循环?

        因为Collection中有些集合是无序的(如Set等),没有索引

2.4.1 迭代器

        迭代器是用来遍历集合的专用方式(数组没有迭代器),仔Java中迭代器的代表是Iterator

Collection集合获取迭代器的方法

方法名称说明
Iterator<E> iterator()返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素

 Iterator迭代器中的常用方法

方法名称说明
boolean hasNext()询问当前位置是否有元素存在,存在返回true,不存在返回false
E next()

获取当前位置的元素,并同时将迭代器对象指向下一个元素处

//demo
public class demo {
    public static void main(String[] args) {
        //Collection是接口,不能直接new(创建对象)
        Collection<String> c = new ArrayList<>();   //多态写法
        c.add("张三");
        c.add("李四");
        c.add("王五");
        c.add("赵二");
        c.add("郝六");
        System.out.println(c); //[张三, 李四, 王五, 赵二, 郝六]

        //使用迭代器遍历集合
        //1.从集合对象中获取迭代器对象
        Iterator<String> it = c.iterator();  //Iterator<E> iterator()返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素
        //E next()	获取当前位置的元素,并同时将迭代器对象指向下一个元素处
//        System.out.println(it.next());  //张三
//        System.out.println(it.next());  //李四
//        System.out.println(it.next());  //王五
//        System.out.println(it.next());  //赵二
//        System.out.println(it.next());  //郝六
        //System.out.println(it.next());  //超出集合的长度,会保错(出现NoSuchElementException异常)

        //应该使用循环结合迭代器遍历集合
        while(it.hasNext()){  //boolean hasNext():询问当前位置是否有元素存在,存在返回true,不存在返回false
            //System.out.println(it.next());  //不建议这样写(如果循环中写两行,集合元素是单数,则会存在报错现象)
            String ele = it.next();
            System.out.println(ele);
        }
    }
}

2.4.2 增强for循环

        增强for循环可以用来遍历集合或数组

        增强for循环遍历集合,本质就是迭代器遍历集合的简化写法

//demo
public class demo {
    public static void main(String[] args) {
        //Collection是接口,不能直接new(创建对象)
        Collection<String> c = new ArrayList<>();   //多态写法
        c.add("张三");
        c.add("李四");
        c.add("王五");
        c.add("赵二");
        c.add("郝六");
        System.out.println(c); //[张三, 李四, 王五, 赵二, 郝六]

        //使用增强for循环遍历集合
        for(String ele : c){
            System.out.println(ele);
        }
        //快捷写法(集合对象.for) c.for

        //使用增强for循环遍历数组
        String[] name = {"123","java","哈哈哈哈"};
        for(String ele : name){
            System.out.println(ele);
        }
        //快捷写法 name.for
    }
}

2.4.3 Lambda表达式

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

需要使用Collection的如下方法来完成

方法名称说明
default void forEach(Consumer<? super T> action)

集合Lambda遍历集合

//demo
public class demo {
    public static void main(String[] args) {
        //Collection是接口,不能直接new(创建对象)
        Collection<String> c = new ArrayList<>();   //多态写法
        c.add("张三");
        c.add("李四");
        c.add("王五");
        c.add("赵二");
        c.add("郝六");
        System.out.println(c); //[张三, 李四, 王五, 赵二, 郝六]

        //结合Lambda表达式遍历集合
        //default void forEach(Consumer<? super T> action)  //Consumer是接口,不能直接创建对象,这里使用匿名内部类
        c.forEach(new Consumer<String>() {  //forEach方法的底层源码思想是 增强for循环
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });

        //层层简化
        c.forEach((String s) -> {
                System.out.println(s);
        });

        //层层简化
        c.forEach( s -> {
            System.out.println(s);
        });

        //层层简化
        c.forEach( s -> System.out.println(s));

        //层层简化
        c.forEach(System.out::println);
    }
}

2.4.4 案例:遍历集合中的自定义对象

//demo
public class demo {
    public static void main(String[] args) {
        Collection<Movie> movies = new ArrayList<>();  //每部电影都是一个对象,多部电影用集合封装
        Movie m1 = new Movie("肖申克的救赎","The Shawshank Redemption","弗兰克","蒂姆","1994");
        Movie m2 = new Movie("霸王别姬","Farewell My Concubine","陈凯歌","张国荣","1993");
        Movie m3 = new Movie("阿甘正传","Forrest Gump","罗伯特","汤姆","1994");
        movies.add(m1);
        movies.add(m2);
        movies.add(m3);

        //遍历集合中的三个对象,输出每部电影的详情信息
        //方法1:迭代器
        Iterator<Movie> it = movies.iterator(); //创建迭代器的对象(从集合对象中获取迭代器对象)
        while(it.hasNext()){
            Movie m = it.next();
            System.out.println(m.toString());
        }

        System.out.println("=============================");

        //方法2:增强for循环  快捷键 movies.for
        //for(元素数据类型 变量名 : 集合或数组)
        for (Movie movie : movies) {
            System.out.println(movie.toString());
        }

        System.out.println("=============================");

        //方法3:结合Lambda表达式遍历集合
        //集合.forEach(Consumer<? super T> action)
        movies.forEach(new Consumer<Movie>() {
            @Override
            public void accept(Movie movie) {
                System.out.println(movie.toString());
            }
        });

        //简化代码
        movies.forEach((Movie movie) -> {
            System.out.println(movie.toString());
        });
    }
}

//Movie
public class Movie {
    private String name; //电影名称
    private String engName; //电影英文名
    private String director; //电影导演
    private String actor; //电影主演
    private String time; //电影上映时间

    public Movie(){

    }

    public Movie(String name, String engName, String director, String actor, String time) {
        this.name = name;
        this.engName = engName;
        this.director = director;
        this.actor = actor;
        this.time = time;
    }

    @Override
    public String toString() {
        return "Movie{" +
                "name='" + name + '\'' +
                ", engName='" + engName + '\'' +
                ", director='" + director + '\'' +
                ", actor='" + actor + '\'' +
                ", time='" + time + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public String getEngName() {
        return engName;
    }

    public void setEngName(String engName) {
        this.engName = engName;
    }

    public String getDirector() {
        return director;
    }

    public void setDirector(String director) {
        this.director = director;
    }

    public String getActor() {
        return actor;
    }

    public void setActor(String actor) {
        this.actor = actor;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }
}

 注意:集合中存储的是元素的地址信息!!!

三. List集合

3.1 List集合特点、特有方法

· List系列集合特点:有序、可重复、有索引

        · ArrayList:有序、可重复、有索引

        · LinkedList:有序、可重复、有索引

· List集合的特有方法

        List集合因为支持索引,所以多了很多和索引相关的方法,当然,Collection的功能List也都继承了

方法名称说明
void add(int index,E element)在此集合中的指定位置插入指定的元素
E remove(int index)删除指定索引处的元素,返回被删除的元素
E set(int index,E element)修改指定索引处的元素,返回被修改的元素
E get(int index)返回指定索引处的元素
//demo
public class demo {
    public static void main(String[] args) {
        //创建一个ArrayList集合对象(List是一个接口不能直接创建对象,要用实现类创建)
        List<String> list = new ArrayList<>(); //多态
        list.add("java1");
        list.add("java2");
        list.add("java2");
        list.add("java3");
        System.out.println(list); //[java1, java2, java2, java3] 有序,可重复,有索引

        //void add(int index,E element)	在此集合中的指定位置插入指定的元素
        list.add(2,"java4");
        System.out.println(list); //[java1, java2, java4, java2, java3]

        //E remove(int index) 删除指定索引处的元素,返回被删除的元素
        System.out.println(list.remove(2)); //java4
        System.out.println(list); //[java1, java2, java2, java3]

        //E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
        System.out.println(list.set(2,"list5")); //java2
        System.out.println(list); //[java1, java2, list5, java3]

        //E get(int index)	返回指定索引处的元素
        System.out.println(list.get(0)); //java1
    }
}

3.2 List集合遍历方式

· List集合支持的遍历方式

        · for循环(因为List集合有索引)

        · 迭代器

        · 增强for循环

        · Lambda表达式

//demo
public class demo {
    public static void main(String[] args) {
        //创建一个ArrayList集合对象(List是一个接口不能直接创建对象,要用实现类创建)
        List<String> list = new ArrayList<>(); //多态
        list.add("java1");
        list.add("java2");
        list.add("java3");
        System.out.println(list); //[java1, java2, java3]

        //1.for循环 快捷方式输入 list.fori
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        System.out.println("===================");

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

        System.out.println("===================");

        //3.增强for循环(forEach遍历)
        for (String s : list) {
            System.out.println(s);
        }

        System.out.println("===================");

        //4.结合Lambda表达式
        list.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });
        //简化
        list.forEach( s -> System.out.println(s));
    }
}

3.3 ArrayList集合的底层原理

        ArrayList和LinkedList两者底层采用的数据结构(存储、组织数据的方式)不同,应用场景也不同

        · ArrayList集合底层采用的是数组,LinkedList集合底层采用的是双链表

3.3.1 数组的特点(查询快、增删慢)

· 查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同

· 删除效率低:可能需要把后面很多的数据进行前移

· 添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容

3.3.2 ArrayList集合的底层原理

         ArrayList集合是基于数组实现的,因此ArrayList集合的特点也是查询快,增删慢

1. 利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组

2. 添加第一个元素时,底层会创建一个新的长度为10的数组

3. 存满时,会扩容1.5倍

4. 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准(如扩容1.5倍是多了5个位置,但是有11个位置的数据要放,则会直接添加11个位置)    

3.3.3 ArrayList集合适合的应用场景

· ArrayList集合适合根据索引查询数据,比如根据随机索引取数据(高效)或者数据量不是很大时

· ArrayList集合不适合数据量大的同时又要频繁的进行增删操作

3.4 LinkedList集合的底层原理

        LinkedList集合是基于双链表实现的

3.4.1 链表

链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址

链表的特点

        · 查询慢,无论查询哪个数据都要从头开始找

        · 链表增删相对快

双向链表的特点

        查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的

3.4.2 ArrayList集合的底层原理

        LinkedList集合是基于双链表实现的,因此LinkedList的特点也是查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的

LinkedList新增了很多首尾操作的特有方法

方法名称说明
public void addFirst(E e)在该列表开头插入指定的元素
public void addLast(E e)将指定的元素追加到此列表的末尾
public E getFirst()返回此列表中的第一个元素
public E getLast()返回此列表中的最后一个元素
public E removeFirst()从此列表中删除并返回第一个元素
public E removeLast()从此列表中删除并返回最后一个元素

3.4.3 LinkedList集合适合的应用场景

LinkedList的应用场景1:可以用来设计队列(队列的特点:先进先出,后进后出)

队列的两端都开口                             

数据进去是入队,数据出来是出队

//demo
public class demo {
    public static void main(String[] args) {
        //创建一个队列
        LinkedList<String> queue = new LinkedList<>();  //这里不要用多态,因为List没有首尾操作的方法
        //入队操作
        queue.addLast("1号");
        queue.addLast("2号");
        queue.addLast("3号");
        queue.addLast("4号");
        System.out.println(queue); //[1号, 2号, 3号, 4号]

        //2.出队操作
        System.out.println(queue.removeFirst()); //移除第一个位置(1号)
        System.out.println(queue); //[2号, 3号, 4号]
    }
}

LinkedList的应用场景2:可以用来设计栈(栈的特点:后进先出,先进后出) 

栈的一端开口(栈顶),一端封闭(栈底)        

数据进入栈模型的过程称为:压/进栈(push),数据离开栈模型的过程称为:弹/出栈(pop)

//demo
public class demo {
    public static void main(String[] args) {
        //创建一个栈对象
        LinkedList<String> stack = new LinkedList<>();  //这里不要用多态,因为List没有首尾操作的方法
        //压栈(进栈)操作
//        stack.addFirst("子弹1");
//        stack.addFirst("子弹2");
//        stack.addFirst("子弹3");
//        stack.addFirst("子弹4");
        stack.push("子弹1");
        stack.push("子弹2");
        stack.push("子弹3");
        stack.push("子弹4");
        System.out.println(stack); //[子弹4, 子弹3, 子弹2, 子弹1]
        //弹栈(出栈)操作
//        System.out.println(stack.removeFirst()); //子弹4
        System.out.println(stack.pop()); //子弹4
        System.out.println(stack); //[子弹3, 子弹2, 子弹1]
    }
}

四. Set集合

4.1 Set系列集合特点

Set系列集合:无序(添加数据的顺序和获取出的数据顺序不一致)、不重复、无索引

        · HashSet:无序、不重复、无索引

        · LinkedHashSet:有序、不重复、无索引

        · TreeSet:按照大小默认升序排序、不重复、无索引

注意:Set要用到的常用方法基本上就是Collection提供的!自己几乎没有额外新增一些常用功能

//demo
public class demo {
    public static void main(String[] args) {
        //创建一个Set集合的对象
        //Set是接口,不能直接创建对象,要通过实现类的方式创建
        //Set<Integer> set = new HashSet<>(); //多态,创建了一个HashSet的集合对象
        //Set<Integer> set = new LinkedHashSet<>();
        Set<Integer> set = new TreeSet<>();
        set.add(7);
        set.add(-4);
        set.add(2);
        set.add(2);
        set.add(5);
        set.add(4);
        set.add(3);
        set.add(1);
        System.out.println(set);
        //HashSet:[1, 2, -4, 3, 4, 5, 7]  //无序,无索引,不重复
        //LinkedHashSet:[7, -4, 2, 5, 4, 3, 1]  //有序(和我的添加顺序一致),无索引,不重复
        //TreeSet:[-4, 1, 2, 3, 4, 5, 7]  //按照大小默认升序排序、不重复、无索引
    }
}

4.2 HashSet集合的底层原理

4.2.1 哈希值

        · 哈希值就是一个int类型的数值,Java中每个对象都有一个哈希值

        · Java中的所有对象,都可以调用Object类提供的hashCode方法,返回该对象自己的哈希值

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

对象哈希值的特点:

        · 同一个对象多次调用hashCode()方法返回的哈希值是相同的

        · 不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)(创建对象的个数超过int所能容纳的范围,就会导致哈希碰撞)

//demo
public class demo{
    public static void main(String[] args){
        Car c1 = new Car("奔驰",23.9);
        Car c2 = new Car("宝马",23.8);
        System.out.println(c1.hashCode()); //2133927002
        System.out.println(c1.hashCode()); //2133927002
        System.out.println(c2.hashCode()); //1836019240

        String str1 = new String("abc");
        String str2 = new String("acD");
        System.out.println(str1.hashCode()); //96354(哈希碰撞)
        System.out.println(str2.hashCode()); //96354(哈希碰撞)
    }
}

//Car
public class Car {
    private String name;
    private double price;

    public Car() {
    }

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

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

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

 4.2.2 数据结构——树

4.2.3 HashSet集合的底层原理

        基于哈希表实现

        哈希表是一种增删改查数据,性能都较好的数据结构

哈希表

        JDK8之前,哈希表=数组+链表

        JDK8开始,哈希表=数组+链表+红黑树

JDK8之前HashSet集合的底层原理,基于哈希表:数组+链表

如果数据占满了数组,继续增加数据,越来越多的数据会导致链表过长,致使查询性能降低

因此会采用扩容的方法

        默认加载因子0.75 × 16 = 12,当数组中占满了12个位置的时候,就开始扩容(新数组是原来数组的两倍),再把原来的数组重新转移到新的数组中

JDK8开始HashSet集合的底层原理,基于哈希表:数组+链表+红黑树

        JDK8开始,当链表长度超过8,切数据长度≥64时,自动将链表转成红黑树

4.2.4 深入理解HashSet集合去重复的机制

        HashSet集合默认不能对内容一样的两个不同对象去重复

如何让HashSet集合能够实现对内容一样的两个不同对象去重复?

//demo
public class demo{
    public static void main(String[] args){
        Set<Car> set = new HashSet<>();
        Car c1 = new Car("奔驰",23.9);
        Car c2 = new Car("奔驰",23.9);
        Car c3 = new Car("宝马",23.8);
        set.add(c1);
        set.add(c2);
        set.add(c3);
        System.out.println(set);
        //没有重写equals和hashCode之前:
            //内容一样的两个不同对象(c1,c2)添加到HashSet中,认为它们是不重复的
            //结果是:[Car{name='奔驰', price=23.9}, Car{name='奔驰', price=23.9}, Car{name='宝马', price=23.8}]
        //重写之后
            //内容一样的两个不同对象(c1,c2)添加到HashSet中,认为它们是重复的
            //结果是:[Car{name='奔驰', price=23.9}, Car{name='宝马', price=23.8}]
    }
}

//Car
public class Car {
    private String name;
    private double price;

    //右键->生成->equals()和hashCode()
    //只要两个对象内容一样就返回true
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o;
        return Double.compare(car.price, price) == 0 &&
                Objects.equals(name, car.name);
    }

    //只要两个对象内容一样,返回的哈希值就是一样的
    @Override
    public int hashCode() {
        return Objects.hash(name, price);
    }

    public Car() {
    }

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

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

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

4.3 LinkedHashSet集合的底层原理

         LinkedHashSet集合也是基于哈希表(数组、链表、红黑树)实现的

        但是它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置

4.4 TreeSet集合

特点:不重复,五索引,可排序(默认升序排序,按照元素的大小,由小到大排序)

TreeSet集合底层是基于红黑树实现的排序

注意:

        · 对于数值类型:Integer,Double,默认按照数值本身的大小进行升序排序

        · 对于字符串类型:默认按照首字符的编号升序排序

        · 对于自定义类型如Student对象,TreeSet默认是无法直接排序的

自定义排序规则

· TreeSet集合存储自定义类型的对象时,必须指定排序规则,支持如下两个方式来指定比较规则

方式一

· 让自定义的类(如Student类)实现Comparable接口,重写里面的compareTo方法来指定比较规则

方式二

· 通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象),用于指定比较规则

        public TreeSet(Comparator<? super E> comparator)

//demo
public class demo{
    public static void main(String[] args){
        Set<Integer> set = new TreeSet<>();
        set.add(6);
        set.add(5);
        set.add(5);
        set.add(7);
        System.out.println(set);  //[5, 6, 7]

        //如果同时存在两种比较规则,会就近选择自己自带的比较器对象进行排序
//        Set<Car> set1 = new TreeSet<>(new Comparator<Car>() {
//            @Override
//            public int compare(Car o1, Car o2) {
//                //需求:按照价格降序排序
//                return Double.compare(o2.getPrice(),o1.getPrice());
//            }
//        });
        //简化
        Set<Car> set1 = new TreeSet<>((o1, o2)-> Double.compare(o2.getPrice(),o1.getPrice()));

        Car c1 = new Car("奔驰",12.8);
        Car c2 = new Car("宝马",14.1);
        Car c3 = new Car("大众",9.9);
        Car c4 = new Car("未来",14.1);
        set1.add(c1);
        set1.add(c2);
        set1.add(c3);
        set1.add(c4);
        System.out.println(set1);
        //没有自定义排序规则前报ClassCastException异常
        //自定义排序规则后,[Car{name='大众', price=9.9}, Car{name='奔驰', price=12.8}, Car{name='宝马', price=14.1}]
        //会发现c4没有存进去,是因为需求是按照price升序排列,如果两个对象price一样,则后面一个就被判定为重复
    }
}

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

    //this代表主调   o是被调
    @Override
    public int compareTo(Car o) {
        //如果认为左边对象大于右边对象返回正整数
        //如果认为左边对象小于右边对象返回负整数
        //如果认为左边对象等于右边对象返回0
        //需求:按照价格升序排序
        if(this.price>o.price){
            return 1;
        }else if(this.price<o.price){
            return -1;
        }else{
            return 0;
        }
        //return (int)(this.price-o.price); //不能这样写,返回值是int类型,两个double类型相减之后强转可能会出bug
    }

    //右键->生成->equals()和hashCode()
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o;
        return Double.compare(car.price, price) == 0 &&
                Objects.equals(name, car.name);
    }

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

    public Car() {
    }

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

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

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

五. 总结

六. 注意事项:集合的并发修改异常问题

集合的并发修改异常

· 使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误

· 由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误

怎么保证遍历集合的同时删除数据时不出bug?

· 使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可

· 如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,单输出元素后做i--操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/455073.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

案例--某站视频爬取

众所周知&#xff0c;某站的视频是&#xff1a; 由视频和音频分开的。 所以我们进行获取&#xff0c;需要分别获得它的音频和视频数据&#xff0c;然后进行音视频合并。 这么多年了&#xff0c;某站还是老样子&#xff0c;只要加个防盗链就能绕过。&#xff08;防止403&#xf…

Git之版本回退

文章转载于&#xff1a;https://www.jianshu.com/p/3020740561a8 以前&#xff0c;如果是要去除某一块功能&#xff0c;我都是选择性删除&#xff0c;选择性注释&#xff0c;然后前后逻辑各种查看&#xff0c;各种比较。每一次&#xff0c;改完这些我总感觉心好累啊&#xff01…

Prompt进阶3:LangGPT(构建高性能质量Prompt策略和技巧2)--稳定高质量文案生成器

Prompt进阶3:LangGPT(构建高性能质量Prompt策略和技巧2)–稳定高质量文案生成器 1.LangGPT介绍 现有 Prompt 创建方法有如下缺点&#xff1a; 缺乏系统性&#xff1a;大多是细碎的规则&#xff0c;技巧&#xff0c;严重依赖个人经验缺乏灵活性&#xff1a;对他人分享的优质 …

数据结构 day2

1:思维导图 2&#xff1a;计算结构体大小 3&#xff1a;大小端存储 &#xff08;1&#xff09;&#xff1a;数据溢出 1 #include <stdio.h>2 #include <string.h>3 #include <stdlib.h>4 int main(int argc, const char *argv[])5 {6 short a 0x1234;…

STM32输入捕获频率和占空比proteus仿真失败

这次用了两天的时间来验证这个功能&#xff0c;虽然实验没有成功&#xff0c;但是也要记录一下&#xff0c;后面能解决了&#xff0c;回来再写上解决的办法&#xff1a; 这个程序最后的实验结果是读取到的CCR1和CCR2的值都是0&#xff0c;所以没有办法算出来频率和占空比。 还…

FPGA - 单总线协议(one-wire)

1&#xff0c;简介 单总线&#xff08;one-wire&#xff09;是美国 DALLAS 公司推出的外围串行扩展总线技术&#xff0c;与 SPI、I2C 等串行数据通信方式不同&#xff0c;它采用单根信号线&#xff0c;既传输时钟又传输数据&#xff0c;而且数据传输是双向的。它具有节省 I/O口…

数据结构中的平衡搜索树 --- 红黑树 (如何旋转与变色)

目录 红黑树的概念 红黑树的性质 红黑树节点的定义 红黑树的插入 1. 按照二叉搜索的树规则插入新节点 2. 检测新节点插入后&#xff0c;红黑树的性质是否造到破坏 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的农作物害虫检测系统(深度学习模型+UI界面+训练数据集)

摘要&#xff1a;开发农作物害虫检测系统对于提高农业生产效率和作物产量具有关键作用。本篇博客详细介绍了如何运用深度学习构建一个农作物害虫检测系统&#xff0c;并提供了完整的实现代码。该系统基于强大的YOLOv8算法&#xff0c;并对比了YOLOv7、YOLOv6、YOLOv5&#xff0…

Linux——多线程

目录 线程概念 线程控制 线程创建 进程 vs 线程 线程异常 线程等待 线程终止 pthread_cancel 进程替换 线程分离 线程互斥 mutex mutex接口 mutex的理解 互斥锁的实现 可重入和线程安全 死锁 什么是死锁 死锁产生的必要条件 避免死锁 线程同步 概念 条件…

论坛管理系统|基于Spring Boot+ Mysql+Java+B/S架构的论坛管理系统设计与实现(可运行源码+数据库+设计文档+部署说明+视频演示)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 目录 前台功能效果图 管理员功能登录前台功能效果图 用户功能模块 系统功能设计 数据库E-R图设计 l…

webpack面试题

1、webpack是干什么的 Webpack是一个现代的JavaScript应用程序的静态模块打包工具。当webpack处理应用程序时&#xff0c;它会在内部构建一个依赖图&#xff0c;此依赖图对应映射到项目所需的每个模块&#xff0c;然后将所有这些模块打包成一个或多个bundle。Webpack的主要功能…

Java初阶数据结构队列的实现

1.队列的概念 1.队列就是相当于排队打饭 2.在排队的时候就有一个队头一个队尾。 3.从队尾进对头出 4.所以他的特点就是先进先出 所以我们可以用链表来实现 单链表实现要队尾进队头出{要有last 尾插头删} 双向链表实现效率高&#xff1a;不管从哪个地方当作队列都是可以的&…

HttpContext请求接收上下文模块设计与实现(http模块四)

目录 类功能 类定义 类实现 编译测试 类功能 类定义 // HttpContext接收请求上下文模块功能设计 typedef enum {RECV_HTTP_ERROR,RECV_HTTP_LINE,RECV_HTTP_HEAD,RECV_HTTP_BODY,RECV_HTTP_OVER } HttpRecvStatu;class HttpContext { private:int _resp_statu; …

Games101笔记-变换

Scale Reflection Shear Rotate 没有额外提示默认绕原点旋转 线性变换 Transiation 不属于线性变换&#xff0c;仿射变换 齐次坐标 二维的点和向量增加一个维度 点加点等于两个点的中点 所有的仿射变换都可以写成齐次坐标的形式 在表示二维情况下的仿射变换时&#…

Linux驱动分离与分层的简介

一. 简介 我们在前面几章编写的设备驱动都非常的简单&#xff0c;都是对 IO 进行最简单的读写操作。 像 I2C 、SPI 、 LCD 等这些复杂外设的驱动就不能这么去写了&#xff0c; Linux 系统要考虑到驱动的可重用性&#xff0c;因 此&#xff0c;提出了驱动的分离与分层这样的软…

Maven: There are test failures.(已解决)

问题解决办法 进行package打包时报错如下&#xff1a; 然后这些并不能看出是测试的哪里的问题&#xff0c;可以点击上一级进行查看更详细的错误&#xff0c;越向上日志越详细&#xff0c;可以看到是52行出了错误&#xff0c; 52对应代码如下&#xff1a; 原因是存在注册的测…

基于FPGA的图像锐化算法(USM)设计

免费获取源码请关注微信号《FPGA学习笔记册》&#xff01; 1.图像锐化算法说明 图像锐化算法在实际的图像处理应用很广泛&#xff0c;例如&#xff1a;医学成像、工业检测和军事领域等&#xff1b;它的作用就是将模糊的图像变的更加清晰。常用的图像锐化算法有拉普拉斯算子、s…

记录一下在Pycharm中虚拟环境的创建

如果在Pycharm中要新建一个虚拟环境&#xff0c;那你可以在Terminal中选择Command Prompt&#xff0c;在这里面执行相关命令 一、安装了Anaconda&#xff0c;创建虚拟环境 当你使用解释器是Anaconda提供的时&#xff0c;你可以使用conda命令执行&#xff0c;见以下操作&#x…

Acwing.4261 孤独的照片(贡献法)

题目 Farmer John 最近购入了 N 头新的奶牛&#xff0c;每头奶牛的品种是更赛牛&#xff08;Guernsey&#xff09;或荷斯坦牛&#xff08;Holstein&#xff09;之一。 奶牛目前排成一排&#xff0c;Farmer John 想要为每个连续不少于三头奶牛的序列拍摄一张照片。 然而&…

华为组网:核心交换机旁挂防火墙,基于ACL重定向配置实验

如图所示&#xff0c;由于业务需要&#xff0c;用户有访问Internet的需求。 用户通过接入层交换机SwitchB和核心层交换机SwitchA以及接入网关Router与Internet进行通信。为了保证数据和网络的安全性&#xff0c;用户希望保证Internet到服务器全部流量的安全性&#xff0c;配置重…