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具有可预测的迭代顺序,也就是我们插入的顺序,其是线程不安全的。

 

posted @ 2020-10-29 13:56  峡谷挨打记  阅读(90)  评论(0编辑  收藏  举报