zoukankan      html  css  js  c++  java
  • Java-集合

    课程目标

    1.集合的概念

    2.Collection接口

    3.List接口与实现类

    4.泛型和工具类

    5.Set接口与实现类

    6.Map接口与实现类

    1.集合的概念

    概念:对象的容器,定义了对多个对象进行操作的常用方法。可实现数组的功能。

    和数组的区别:

    1. 数组长度固定,集合长度不固定;
    2. 数组可以存储基本类型和引用类型,集合只能存储引用类型。

    位置:java.util.*;

    2.Collection接口

    2.1 Collection体系集合

    2.2 Collection父接口

    特点:Collection是集合的根接口。Collection表示一组对象,这些对象也称为collection的元素。一些collection允许有重复的元素,而另一些则不允许。一些collection是有序的,而另一些则是无序的。JDK不提供接口的任何直接实现:它提供更具体的子接口(如Set和List)实现。此接口通常用来传递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对象
    • is size() //返回此集合中的元素个数。
    • Object[] toArray() //将此集合和转换成数组。

    对于方法的演示,这里就不操作了,你可以用其具体实现类操作,比如ArrayList等。

    但是这里对遍历的重点知识说明以下:第一种就是用增强for循环遍历,第二种就是用Collection接口的Iterator接口遍历。

    对于Iterator接口,有三个重要的方法:hasNext();next()和remove()。其实就遍历而言用前两个方法配合就可以了,那为什么还需要第三个方法呢?这是因为在用Iterator遍历过程中是不能用Collection接口的remove方法删除元素的,如果这样做会报错。

    3.List接口与实现类

    3.1 List接口的使用

    特点:有序(是添加顺序和获取顺序一致,并非排序)、有下标(可以通过下标访问,但是我们用Collection接口时,就未必可以用下标访问,因为还有Set接口的元素集合,因此这里遍历就又多了个普通for循环)、元素可以重复。

    方法:(除去Collection的方法,新增了一些因角标而特有的方法)

    • void add(int index,Object o) //在index位置插入对象o。
    • boolean addAll(int index, Collection c) //将一个集合中的元素添加到此集合中的index位置。
    • Object get(int index) //返回集合中指定位置的元素。
    • List subList(int fromIndex, int toIndex) //返回fromIndex和toIndex之间的集合元素。

    遍历:上面对于Collection接口有两种遍历方式,这里对于List接口共有四种遍历方式(其实也算三种):普通for循环(借用角标);增强for循环;通过Iterator迭代器;通过ListIterator

    对于第四种方法,我这里特别说明以下,这个迭代器是在原有Iterator(三个方法)的基础上进行了增强

    ListIterator接口允许程序沿任一方向进行迭代遍历,在迭代期间修改列表,并获取列表中迭代器的当前位置。ListIterator不指向任何元素,其光标始终位于通过调用previous()返回的元素和通过next()返回的元素之间。长度为n的列表的迭代器具有n+1可能的光标位置。

    对于反向遍历,需要注意的是,刚开始遍历List时,光标时位于开始的,时不能反向遍历的,只有移动一些位置后才可以反向遍历。

    List方法:

    3.2 List接口的实现类

    ArrayList【重点】

    • 数组结构实现(内部维护的是数组)。查询快(为什么)、增删慢(为什么);
    • JDK1.2版本加入的,运行效率快、线程不安全。

    Vector:

    • 数组结构实现(和ArrayList一样维护的是数组结构),查询快、增删慢;
    • JDK1.0版本,运行效率慢(为什么)、线程安全。

    LinkedList【重点】

    • 链表结构实现(内部维护的是双向链表),增删快(为什么),查询慢(为什么)。

    3.2.1 ArrayList

    package com.yuncong.java_collection;
    import java.util.ArrayList;
    //ArrayList:查询遍历快,增加删除慢
    public class ArrayListDemo01 {
        public static void main(String[] args) {
            // 创建集合
            ArrayList arrayList = new ArrayList<>();
            // 1.添加元素
            Student s1 = new Student(22,"张三丰");
            Student s2 = new Student(18, "张无忌");
            Student s3 = new Student(17, "苦无");
            arrayList.add(s1);
            arrayList.add(s2);
            arrayList.add(s3);
            arrayList.add(s3);
            System.out.println("元素个数:"+arrayList.size());
            System.out.println(arrayList.toString());
            //2.删除元素
            arrayList.remove(s3);
            System.out.println("删除之后元素个数:" + arrayList.size());
            //注意:此时是无法删除s2的,因为这个删除比较的是equals方法,在没有重写前底层是equals(this==obj),即比较的地址。
            arrayList.remove(new Student(18, "张无忌")); 
            System.out.println("再次删除之后元素个数:" + arrayList.size());
        }
    }

    运行结果:

     下面这个代码是Student类重写equals方法(要记住这五步走战略):此外需要注意的是,重写这个equals方法后,不仅remove方法可以删除同名同龄的新对象,用contains方法判断是否存在也可以(没有重写前返回是false)

    还有其它方法,比如indexOf,也可以传入一个新new的同名同龄的对象。因此可以得出结论,但凡是该方法传入了对象,那么我重写equals方法后,传入新new的对象,就会使用该新的equals方法,当然前提是该方法用到了equals。

        //重写equals方法的五步走战略
        @Override
        public boolean equals(Object obj) {
            // 判断是不是同一个对象
            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;
        }

    此时再次运行则删除成功:

    package com.yuncong.java_collection;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.ListIterator;
    //ArrayList:查询遍历快,增加删除慢
    public class ArrayListDemo01 {
        public static void main(String[] args) {
            // 创建集合
            ArrayList arrayList = new ArrayList<>();
            // 1.添加元素
            Student s1 = new Student(22,"张三丰");
            Student s2 = new Student(18, "张无忌");
            Student s3 = new Student(17, "苦无");
            arrayList.add(s1);
            arrayList.add(s2);
            arrayList.add(s3);
            arrayList.add(s3);
            System.out.println("元素个数:"+arrayList.size());
            System.out.println(arrayList.toString());
            //2.删除元素
            arrayList.remove(s3);
            System.out.println("删除之后元素个数:" + arrayList.size());
            //注意:此时是无法删除s2的,因为这个删除比较的是equals方法,在没有重写前底层是equals(this==obj),即比较的地址。
            arrayList.remove(new Student(18, "张无忌"));
            System.out.println("重写Student类中的equals方法后再次删除之后元素个数:" + arrayList.size());
            //3.遍历元素【重点】
            //3.1使用普通for循环
            //3.2使用增强for循环
            //3.3使用Iterator迭代器
            System.out.println("==========使用Iterator迭代器遍历========");
            Iterator iterator = arrayList.iterator();
            while (iterator.hasNext()) {
                Student next = (Student) iterator.next();
                System.out.println(next);
            }
            //3.4使用ListIterator迭代器
            System.out.println("==========使用ListIterator迭代器遍历========");
            ListIterator listIterator = arrayList.listIterator();
            while (listIterator.hasNext()) {
                Student next = (Student) listIterator.next();
                System.out.println(next);
            }
            System.out.println("==========使用ListIterator迭代器逆序遍历========");
            while (listIterator.hasPrevious()) {
                Student previous = (Student) listIterator.previous();
                System.out.println(previous);
            }
            //4.判断
            System.out.println(arrayList.contains(s1));
            System.out.println(arrayList.contains(new Student(22,"张三丰")));
            System.out.println(arrayList.isEmpty());
            //5.查找
            System.out.println(arrayList.indexOf(s1));
            System.out.println(arrayList.indexOf(new Student(22,"张三丰")));
        }
    }

    运行结果:

    源码分析:

    DEFAULT_CAPACITY=10;默认容量

    注意:如果没有向集合中添加任何元素时,容量0,添加一个元素之后,容量10

    每次扩容大小是原来的1.5倍。

    elementData存放元素的数组

    size实际元素个数

    add{}添加元素 

    3.2.2 Vector

    该类的用法和ArrayList相似,这里就不再说了。面试可能用到,平时很少使用,至于为什么很少使用?百度一下

     3.2.3 LinkedList

    package com.yuncong.java_collection;
    import java.util.Iterator;
    import java.util.LinkedList;
    import java.util.ListIterator;
    public class LinkedListDemo01 {
        public static void main(String[] args) {
            //创建集合
            LinkedList<Object> linkedList = new LinkedList<>();
            //1.添加元素
            Student s1 = new Student(22,"张三丰");
            Student s2 = new Student(18, "张无忌");
            Student s3 = new Student(17, "苦无");
            linkedList.add(s1);
            linkedList.add(s2);
            linkedList.add(s3);
            linkedList.add(s3);
            System.out.println("元素个数:" + linkedList.size());
            System.out.println(linkedList.toString());
            //2.删除
            linkedList.remove(s3);
            System.out.println("用remove直接传引用删除后个数:" + linkedList.size());
            linkedList.remove(new Student(17, "苦无"));
            System.out.println("重写equals方法后,用new对象删除后个数:" + linkedList.size());
            //3.遍历
            //3.1普通for循环
            System.out.println("普通for循环遍历");
            for (int i = 0; i < linkedList.size(); i++) {
                System.out.println(linkedList.get(i));
            }
            //3.2增强for循环
            System.out.println("利用增强for循环遍历");
            for (Object o : linkedList) {
                System.out.println(o);
            }
            //3.3利用Iterator迭代器遍历
            System.out.println("利用迭代器遍历");
            Iterator<Object> iterator = linkedList.iterator();
            while (iterator.hasNext()) {
                Student next = (Student) iterator.next();
                System.out.println(next);
            }
            //3.4利用ListIterator
            System.out.println("利用列表迭代器");
            ListIterator<Object> objectListIterator = linkedList.listIterator();
            while (objectListIterator.hasNext()) {
                System.out.println(objectListIterator.next());
            }
            //4.判断
            System.out.println(linkedList.contains(s1));
            System.out.println(linkedList.isEmpty());
            //5.获取
            System.out.println(linkedList.indexOf(s1));
        }
    }

    运行结果:

     源码分析:

    4.泛型和工具类

    Java泛型是JDK1。5中引入的一个新特性,其本质是参数化类型,把类型作为参数传递。(其实也就是说用这个类、接口或方法可以传入一个类型参数,可以使用该泛型(表示类型)创建变量等操作,但是不能用来创建对象,如T t = new T();为什么不能??因为你不知道是什么类型,万一这个类型没有无参构造,或者该公祖奥方法是私有的怎么办??)

    常见形式有泛型类、泛型接口、泛型方法。

    语法:<T,...> T称为类型占位符,表示一种引用类型。(不能是基本类型)

    好处:

    • 提高代码的重用性;(演示后才会理解,如用泛型方法的时候,一个方法搞定所有类型数据,其实我这里就有疑问,那平时我们用的print、println方法为什么不用泛型呢,以后我可以研究一下)
    • 放置类型转换异常,提高代码的安全性。(演示后才会理解,在)

    泛型类

    package com.yuncong.java_collection;
    //泛型类
    //语法:类名<T>,如果添加多个泛型,用逗号隔开<T,K,E,V>
    public class GenericDemo01<T> {
        //使用泛型可以创建变量,但无法实例化
        T t;
        //泛型作为方法的参数
        public void show(T t) {
            System.out.println(t);
        }
        //3泛型作为方法的返回值
        public T getT() {
            return t;
        }
        public static void main(String[] args) {
            //使用泛型创建对象(此时就不能使用泛型了,要传递给他具体的类型,且必须是引用类型,如果要用基本类型,必须是包装类)
            GenericDemo01<String> genericDemo01 = new GenericDemo01<>();
            genericDemo01.t = "我和你";
            genericDemo01.show("大家好");
            System.out.println(genericDemo01.getT());
            //不同泛型类型对象之间不能相互赋值,这是错误的,为什么也特意提醒一下,因为其它类型之间都可以正常赋值
            //GenericDemo01<Integer> my = genericDemo01;
        }
    }

    运行结果:

     泛型接口:

     这是使用泛型接口的其中一种方法,也就是某类实现该接口时,就在接口右边明确这个泛型。

    还有一种方式是,某类实现该接口,但是接口右侧还是泛型,且类的右侧也是泛型(和接口一样的泛型),然后这个泛型什么时候明确呢?由创建该类的对象时创建。

     泛型方法:

    package com.yuncong.java_collection;
    //泛型方法
    //语法:<T> 返回值类型
    //这里需要解释一下:泛型方法和泛型类与泛型接口不同,泛型放在方法的返回值前面
    // //然后这个方法中可以在参数,返回值,方法内(如声明变量,但不能创建对象)使用。
    public class GenericDemo03 {
        //泛型方法
        public <T> void show(T t) {
            System.out.println("泛型方法执行,传入参数是:"+t+"类型为"+t.getClass());
            return;
        }
        public <T> T read(T t) {
            System.out.println("泛型方法执行,传入参数是:"+t+"类型为"+t.getClass());
            return t;
        }
        public static void main(String[] args) {
            //注意:泛型方法的时候不需要你特意声明泛型的类型,他会根据你传入的方法的类型决定
            GenericDemo03 genericDemo03 = new GenericDemo03();
            genericDemo03.show(3.14);//其实这里就体现了提高代码重用性的作用
            genericDemo03.show(1);
            genericDemo03.show(new Student(18,"高"));
            System.out.println(genericDemo03.read(true));
        }
    }

    运行结果:

    泛型集合

    概念:参数化类型、类型安全的集合(可以避免类型转换失败的情况,如果你不设置类型,默认object,你又放了各种类型,万一你失误转错了),强制集合元素的类型必须一致。(这句话有点迷茫哎)

    特点:

    • 编译时即可检查,而非运行时排除异常。(不太理解)
    • 访问时,不必类型转换(拆箱)。(其实我们平时用集合都是确定了泛型,如果没有确定,比如上面我们的代码,此时就默认是Object类型,这时候也有个好处,就是你放什么类型都可以,但好像这样没什么用)
    • 不同泛型之间引用不能相互赋值,泛型不存在多态。(这个上面已经提到过)

    5.Set接口与实现类

    Set子接口

    特点:无序、无下标、元素不可重复。

    方法:全部继承自Collection中的方法。(也就是说没有自定义方法,因此其遍历方式也只有两种)

    HashSet【重点】

    • 基于HashCode计算元素存放位置。(基于Hash算法实现位置存放)
    • 当存入元素的哈希码相同时,会调用equals进行确认,如果为true,则拒绝后者存入。(在没有重写的情况下,这里equals比较的是引用)
    • 存储结果是哈希表,什么是哈希表(数组+链表),其实在JDK1.8后变为(数组+链表+红黑树)
    package com.yuncong.java_collection;
    import java.util.HashSet;
    //HashSet集合的使用
    //存储结构:哈希表(数组+链表),JDK1.8后(数组+链表+红黑树)
    public class HashSetDemo01 {
        public static void main(String[] args) {
            //创建集合
            HashSet<Student> students = new HashSet<>();
            //1.添加数据
            Student s1 = new Student(22,"张三丰");
            Student s2 = new Student(18, "张无忌");
            Student s3 = new Student(17, "苦无");
            students.add(s1);
            students.add(s2);
            students.add(s3);
            students.add(s3);//重复,添加失败
            System.out.println("元素个数:"+students.size());
            //如果想同名同龄的新new的对象为同一对象,则student类中重写equals方法
            students.add(new Student(17, "苦无"));//不同对象,添加成功,但这里是失败的,因为重写了equals方法
            //但是当你看到结果的时候,打脸了,已经重写了equals方法,按道理已经是一样了,怎么还添加了???
            System.out.println("元素个数:"+students.size());
            System.out.println(students);
        }
    }

    运行结果:

     HashSet存储过程:

    下面通过存储过程来解决这个疑问

    (1)根据hashCode计算保存的位置,如果此位置为空,则直接保存,如果不为空执行第二步。

    (2)再执行equals方法,如果equals方法为true,则认为重复,否则,形成链表。

    因此为了解决上一个同名同龄也存入进去的问题,需要重写hashcode方法。(即既要重写hashcode方法,也要重写equals方法)

    package com.yuncong.java_collection;
    import java.util.HashSet;
    //HashSet集合的使用
    //存储结构:哈希表(数组+链表),JDK1.8后(数组+链表+红黑树)
    public class HashSetDemo01 {
        public static void main(String[] args) {
            //创建集合
            HashSet<Student> students = new HashSet<>();
            //1.添加数据
            Student s1 = new Student(22,"张三丰");
            Student s2 = new Student(18, "张无忌");
            Student s3 = new Student(17, "苦无");
            students.add(s1);
            students.add(s2);
            students.add(s3);
            students.add(s3);//重复,添加失败
            System.out.println("元素个数:"+students.size());
            //如果想同名同龄的新new的对象为同一对象,则student类中重写equals方法
            students.add(new Student(17, "苦无"));//不同对象,添加成功,但这里是失败的,因为重写了equals方法
            //但是当你看到结果的时候,打脸了,已经重写了equals方法,按道理已经是一样了,怎么还添加了???
            System.out.println("元素个数:"+students.size());
            System.out.println(students);
            //2.删除操作
            students.remove(s1);
            //这个也会调用hashcode和equals进行删除,最终成功
            students.remove(new Student(17, "苦无"));
            System.out.println(students);
            //3.遍历【重点】
            //3.1使用增强for
            //3.2使用迭代器
            //4.判断
            //这个一样会调用hashcode和equals方法
            System.out.println(students.contains(new Student(18, "张无忌")));
        }
    }

    Student重写方法即运行结果:

     如果每次这种情况都重写hashcode和equals方法是不是太麻烦??altr+insert重写(IDEA)

    当你用IDEA去重写这两个方法时,可以看到hashcode方法中出现了31,为什么用这个数??1.31是一个质数,减少散列冲突;(2)31提高执行效率 31*i=(i<<5)-i

    当然网上也有质疑这个31的,大家了解一下即可。

    TreeSet:【基于红黑树数据结构】

    • 基于排列顺序实现元素不重复。
    • 实现了SortedSet接口,对集合元素自动排序。
    • 元素对象的类型必须实现Comparable接口,指定排序规则。
    • 通过CompareTo方法确定是否为重复元素。
    package com.yuncong.java_collection;
    import java.util.TreeSet;
    public class TreeSetDemo01 {
        public static void main(String[] args) {
            //创建集合
            TreeSet<String> treeSet = new TreeSet<>();
            //1.添加元素
            treeSet.add("你好");
            treeSet.add("我好");
            treeSet.add("大家好");
            System.out.println(treeSet.toString());
            System.out.println("元素个数:"+treeSet.size());
        }
    }

    运行结果:

    这里插入String是成功了,现在我们插入其它引用类型试试。

    package com.yuncong.java_collection;
    import java.util.TreeSet;
    public class TreeSetDemo02 {
        public static void main(String[] args) {
            //创建集合
            TreeSet<Student> treeSet = new TreeSet<>();
            //1.添加元素
            Student s1 = new Student(22,"张三丰");
            Student s2 = new Student(18, "张无忌");
            Student s3 = new Student(17, "苦无");
            treeSet.add(s1);
            treeSet.add(s2);
            treeSet.add(s3);
            System.out.println(treeSet.toString());
            System.out.println("元素个数:"+treeSet.size());
        }
    }

    运行结果:

    结果显示:类型转换异常,说Student类不能转换成Comparable类型,这是什么鬼??这是说Student必须实现Comparable接口。这是为什么呢?因为TreeSet的数据结构是红黑树,什么是红黑树?

    红黑树=二叉排序树(二叉查找树、二叉搜索树)+有红黑两种颜色的标记

    二叉排序树:它或者是一棵空树;或者是具有下列性质的二叉树;

    1. 若左子树不空,则左子树上所有的节点的值均小于它的根节点的值;
    2. 若右子树不空,则右子树上所有节点的值均大于它的根节点的值;
    3. 左、右子树也分别为二叉排序树;

    当给你一组数据的时候【3,5,2,1,6】,如何实现二叉排序树??3为第一个元素;然后取出5,5大于3,则放在右子树;取出2,2小于3,则放在左子树;取出1,1小于3,放在左子树,又小于2,放在其左子树;取出6,6大于3,放在右子树,又大于5,放在5的右子树。

    而红黑树是二叉排序树+有颜色的标记(为了平衡,防止一边元素过重),因此要实现排序,对于String类型而言,里面已经实现了排序,即实现了Comparable接口(该接口是泛型的,里面仅有一个方法),但是Student类没有,请问用他来排序,是拿什么排序?拿内存地址?年龄?姓名?不确定,因此要让其实现Comparable接口后才能放入TreeSet集合中。

    package com.yuncong.java_collection;
    import java.util.TreeSet;
    //使用TreeSet保存数据
    //存储结果:红黑树
    //要求:元素必须要实现Comparable接口,compareTo()方法返回值为0,认为是重复元素,则不能添加
    public class TreeSetDemo02 {
        public static void main(String[] args) {
            //创建集合
            TreeSet<Student> treeSet = new TreeSet<>();
            //1.添加元素
            Student s1 = new Student(22,"张三丰");
            Student s2 = new Student(18, "张无忌");
            Student s3 = new Student(17, "苦无");
            treeSet.add(s1);
            treeSet.add(s2);
            treeSet.add(s3);
            treeSet.add(new Student(17, "苦无"));
            System.out.println(treeSet.toString());
            System.out.println("元素个数:"+treeSet.size());
            //2.删除
            treeSet.remove(s3);
            //这个删除就是一句compareTo方法去做的
            treeSet.remove(new Student(18, "张无忌"));
            System.out.println("删除后元素个数:"+treeSet.size());
            //3.遍历(对于Set接口,只有两种遍历方式)
            //3.1增强for循环
            //3.2使用迭代器
            //4.判断
            //也是根据comparableTo来比较
            System.out.println(treeSet.contains(new Student(22,"张三丰")));
        }
    }

    运行结果:

     补充Compatator接口的使用

    package com.yuncong.java_collection;
    import java.util.Comparator;
    import java.util.TreeSet;
    //使用TreeSet保存数据
    //Comparator:是按定制比较(比较器)
    //优势,或者说作用,其实与上一个Comparable相比,都是比较,那什么还要又这个?
    public class TreeSetDemo03 {
        public static void main(String[] args) {
            //创建集合
            TreeSet<Student> treeSet = new TreeSet<>(new Comparator<Student>() {
                @Override
                public int compare(Student o1, Student o2) {
                    int n1 = o1.getAge() - o2.getAge();
                    int n2 = o1.getName().compareTo(o2.getName());
                    return n1 == 0? n2:n1;
                }
            });
        }
    }

    上面的实现Comparable接口不就可以实现比较了吗?为什么这里还要有这个接口??不是多此一举吗??

    当然不是的,这个接口是用在原有类不能更改,或者说无法更改的情况下,自己在创建TreeSet对象时传入比较方法。你可能会问,类不都是我自己写的吗?还有不能改的,骚年,是你年龄太小,以后就知道了。

     案例:

    用过TreeSet集合实现字符串按照长度进行排序。

    package com.yuncong.java_collection;
    import java.util.Comparator;
    import java.util.TreeSet;
    //要求:使用TreeSet集合实现字符串按照长度进行排序
    public class TestForTreeSet {
        public static void main(String[] args) {
            String[] res = sortByLengthForStringArray(new String[]{"he","h","hello1","111","11"});
            for (String re : res) {
                System.out.println(re);
            }
        }
        public static String[] sortByLengthForStringArray(String[] arrs){
            TreeSet<String> set = 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;
                }
            });
            for (String arr : arrs) {
                set.add(arr);
            }
            int index = 0;
            for (String s : set) {
                arrs[index++] = s;
            }
            return arrs;
        }
    }

    运行结果:

     这道题同样也说明了Comparator接口的优先级更高,什么意思呢?如果一个类实现了Comparable接口,将其放入TreeSet集合时又传入了Comparator接口,则会按后者执行。

    6.Map接口与实现类

     Map父接口(遍历方法有两种,一种是keySet,一种是EntrySet)

    特点:村粗一堆数据。无序、无下标,键不可重复。

    方法:

    • V put(K key,V value); //将对象存入到集合中,关联键值。key重复则覆盖原值。
    • Object get(Object key); //根据键获取对应的值
    • keySet(K); //返回所有key。
    • Collection<V> values(); //返回包含所有值的Collection集合。
    • Set<Map,Entry<K,V>>; //键值匹配的Set集合。

     Map集合的实现类

    HashMap【重点】

    • JDK1.2版本,线程不安全,运行效率快;允许用null作为key或是value。

    Hashtable:

    • JDK1.0版本,线程安全,运行效率慢,不允许null作为key或是value。

    Properties:

    • Hashtable的子类,要求key和value都是String。通常用于配置文件的读取。

    TreeMap:

    • 实现了SortedMap接口(是Map的子接口),可以对key自动排序。

    HashMap

    package com.yuncong.java_collection;
    import java.util.HashMap;
    //HashMap集合的使用
    //存储结果:哈希表(数组+链表),在JDK1.8之后为(数组+链表+红黑树)
    //使用key的hashcode和equals方法作为重复的依据
    public class HashMapDemo01 {
        public static void main(String[] args) {
            //创建集合
            HashMap<Person,String> hashMap = new HashMap<>();
            //1.添加元素
            Person p1 = new Person("唐僧", 13);
            Person p2 = new Person("猪八戒", 14);
            Person p3 = new Person("悟空", 18);
            hashMap.put(p1,"北京");
            hashMap.put(p2,"上海");
            hashMap.put(p3,"杭州");
            hashMap.put(p3,"驻马店");
            hashMap.put(new Person("悟空", 18),"扬州");
            System.out.println("元素个数:"+hashMap.size());
        }
    }

    运行结果:元素个数:4

    这说明添加new的对象成功,这是为什么呢?其是使用key值的hashcode和equals作为重复依据,如果想让new出来的新同名同龄对象一样,则要重写这两个方法(可以用IDEA的快捷键重写)

    重写后:

    package com.yuncong.java_collection;
    import java.util.HashMap;
    import java.util.Map;
    //HashMap集合的使用
    //存储结果:哈希表(数组+链表),在JDK1.8之后为(数组+链表+红黑树)
    //使用key的hashcode和equals方法作为重复的依据
    public class HashMapDemo01 {
        public static void main(String[] args) {
            //创建集合
            HashMap<Person,String> hashMap = new HashMap<>();
            //1.添加元素
            Person p1 = new Person("唐僧", 13);
            Person p2 = new Person("猪八戒", 14);
            Person p3 = new Person("悟空", 18);
            hashMap.put(p1,"北京");
            hashMap.put(p2,"上海");
            hashMap.put(p3,"杭州");
            hashMap.put(p3,"驻马店");
            hashMap.put(new Person("悟空", 18),"扬州");
            System.out.println("元素个数:"+hashMap.size());
            //2.删除
            hashMap.remove(p3);
            System.out.println("删除之后元素个数:"+hashMap.size());
            //3.遍历
            //3.1使用keySet
            for (Person key : hashMap.keySet()) {
                System.out.println(key.toString()+"========="+hashMap.get(key));
            }
            //3.2使用entrySet
            for (Map.Entry<Person, String> entry : hashMap.entrySet()) {
                System.out.println(entry.getKey()+"=========="+entry.getValue());
            }
            //4.判断
            System.out.println(hashMap.containsKey(new Person("唐僧", 13)));
            System.out.println(hashMap.containsValue("上海"));
        }
    }

    运行结果:

    源码分析 

    之前没有讲HashSet的源码,其实其底层用的就是HashMap的key。

    Hashtable

    其底层还是哈希表,即数组+链表,没有像HashMap一样在JDK1.8后修改为数组+链表+红黑树的形式。因为这个类不怎么用了。

    这个类是在JDK1.0版本就有的,线程安全,效率慢,我们已经不用了,但是这个类有一个子类Properties(这个类和流有着紧密关系),我们还是会用的。

    Properties:

    • Hashtable的子类,要求key和value都是String。通常用于配置文件的读取。这个在流之后更加详细的讲解。

     TreeMap(底层是红黑树结构)

    package com.yuncong.java_collection;
    import java.util.TreeMap;
    //底层是红黑树,因此要排序
    public class TreeMapDemo01 {
        public static void main(String[] args) {
            //创建集合
            TreeMap<Person, String> treeMap = new TreeMap<>();
            //1.添加元素
            Person p1 = new Person("唐僧", 13);
            Person p2 = new Person("猪八戒", 14);
            Person p3 = new Person("悟空", 18);
            treeMap.put(p1,"北京");
            treeMap.put(p2,"上海");
            treeMap.put(p3,"杭州");
            treeMap.put(p3,"驻马店");
            System.out.println("元素个数:"+treeMap.size());
            System.out.println(treeMap.toString());
        }
    }

    运行结果:

     因此运用TreeMap和TreeSet时,如果存放自定义类型,要实现Comparable接口,或者传入比较对象。

    package com.yuncong.java_collection;
    import java.util.Comparator;
    import java.util.Map;
    import java.util.TreeMap;
    //底层是红黑树,因此要排序
    public class TreeMapDemo01 {
        public static void main(String[] args) {
            //创建集合
            TreeMap<Person, String> treeMap = new TreeMap<>(new Comparator<Person>() {
                @Override
                public int compare(Person o1, Person o2) {
                    int n1 = o1.getName().compareTo(o2.getName());
                    int n2 = o1.getAge() - o2.getAge();
                    return n1==0?n2:n1;
                }
            });
            //1.添加元素
            Person p1 = new Person("唐僧", 13);
            Person p2 = new Person("猪八戒", 14);
            Person p3 = new Person("悟空", 18);
            treeMap.put(p1,"北京");
            treeMap.put(p2,"上海");
            treeMap.put(p3,"杭州");
            treeMap.put(p3,"驻马店");
            System.out.println("元素个数:"+treeMap.size());
            System.out.println(treeMap.toString());
            //2.删除
            treeMap.remove(new Person("悟空", 18));
            treeMap.remove(p2);
            System.out.println("删除后元素个数:"+treeMap.size());
            //3.遍历
            //3.1使用keyset
            for (Person person : treeMap.keySet()) {
                System.out.println(person+"========="+treeMap.get(person));
            }
            //3.2使用entrySet
            for (Map.Entry<Person, String> entry : treeMap.entrySet()) {
                System.out.println(entry.getKey()+"========"+entry.getValue());
            }
            //4.判断
            System.out.println(treeMap.containsKey(p1));
            System.out.println(treeMap.containsKey(new Person("唐僧", 13)));
        }
    }

    运行结果:

     TreeMap与TreeSet的关系,通过源码发现,其实TreeSet底层用的就是TreeMap的key。换句话说TreeSet这个类就是在维护一个TreeMap类,用的是该类的key集合。

    Collections工具类

    概念:集合工具类,定义了除了存取以外的集合常用方法。(之前我们学过Arrays,其是对数组进行操作的,现在这个是对集合进行操作的)

    方法:

    • public static void reverse(List<?> list); //反转集合中元素的顺序
    • public static void shuffle(List<?> list); //随机重置集合元素的顺序
    • public static void sort(List<T> list); //升序排序(元素类型必须实现Comparable接口)
    package com.yuncong.java_collection;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    //Collections工具类的使用
    public class CollectionsDemo01 {
        public static void main(String[] args) {
            //Integer内部实现了Comparable接口
            List<Integer> list =  new ArrayList<>();
            list.add(20);
            list.add(5);
            list.add(12);
            list.add(30);
            list.add(6);
            //sort排序
            System.out.println("排序前:"+list.toString());
            Collections.sort(list);//你也可以自定义排序规格
            System.out.println("排序后:"+list.toString());
            //binarySearch,二分查找,这个要求该集合必须已经排好序,查到返回下标,找不到返回-4
            System.out.println(Collections.binarySearch(list,12));
            System.out.println(Collections.binarySearch(list,14));
            //copy复制
            List<Integer> dest = new ArrayList<>();
            Collections.copy(dest,list);
        }
    }

    运行结果:

     这个方法设计不是很好,它要求你复制的对象和被复制的对象大小必须一样,这里list长度是5,而dest是新建的,长度是0,因此失败。这设计的岂不是很差,毕竟是不定长,怎么这么设计。

    修改后:

    package com.yuncong.java_collection;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;
    //Collections工具类的使用
    public class CollectionsDemo01 {
        public static void main(String[] args) {
            //Integer内部实现了Comparable接口
            List<Integer> list =  new ArrayList<>();
            list.add(20);
            list.add(5);
            list.add(12);
            list.add(30);
            list.add(6);
            //sort排序
            System.out.println("排序前:"+list.toString());
            Collections.sort(list);//你也可以自定义排序规格
            System.out.println("排序后:"+list.toString());
            //binarySearch,二分查找,这个要求该集合必须已经排好序,查到返回下标,找不到返回-4
            System.out.println(Collections.binarySearch(list,12));
            System.out.println(Collections.binarySearch(list,14));
            //copy复制
            List<Integer> dest = new ArrayList<>();
            for (int i = 0; i < list.size(); i++) {
                dest.add(0);
            }
            Collections.copy(dest,list);
            System.out.println(dest.toString());
            //reverse反转
            Collections.reverse(list);
            System.out.println("反转之后:"+list);
            //shuffle 打乱
            Collections.shuffle(list);
            System.out.println("打乱之后:"+list);
            //补充!!!!!
            //补充:list转成数组
            System.out.println("-------list转成数组--------");
            //这里传入的参数,0是数组长度,其实你传几都可以,当传入小于长度是,自动变为集合长度,当大于时,后面就是null了
            //因此一般传入0,这样能保证数组长度和集合长度一定相同。
            Integer[] array = list.toArray(new Integer[0]);
            System.out.println(array.length);
            System.out.println(array.toString());
            System.out.println(Arrays.toString(array));
            //数组转成集合
            System.out.println("-------数组转成集合-------");
            String[] names = {"张三","李四","王五","赵六"};
            //但是请注意:数组转成的集合是一个受限的集合,不能添加和删除,会报错
            List<String> asList = Arrays.asList(names);
            System.out.println(asList);
            //这里还有一个小问题,基本类型数据数组转成集合的时候,这个数组可用int定义,也可用Integer,但是写法有点不一样
            int[] nums = {1,2,3,4,5};
            List<int[]> ints = Arrays.asList(nums);//多了[]
            System.out.println(ints);//且list里面的每一个元素都是一个int数组
            Integer[] nums2 = {1,2,3,4,5};//建议
            List<Integer> integers = Arrays.asList(nums2);
            System.out.println(integers);
        }
    }

    运行结果:

     集合总结:

     LinkedHashSet具有可预测的迭代顺序,也就是我们插入的顺序,其是线程不安全的。

  • 相关阅读:
    Java进阶 -- 文章汇总
    Java并发编程 -- 文章汇总
    大话设计模式读书笔记--文章汇总
    Java并发编程--6.Exchanger线程间交换数据
    Java并发编程--7.Java内存操作总结
    Java并发编程--5.信号量和障碍器
    Java并发编程--4.Executor框架
    Java并发编程--3.Lock
    Java并发编程--2.synchronized
    Java并发编程--1.Thread和Runnable
  • 原文地址:https://www.cnblogs.com/G-JT/p/13888377.html
Copyright © 2011-2022 走看看