JAVA常用类库超重点——集合

对象数组有哪些问题?普通的对象数组最大的问题在于,数组中的元素个数是固定的,不能动态的扩充大小,所以最早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在Java中为了方便用户操作各个数据结构,所以引入了类集的概念,有时候就可以把类集称为Java对数据结构的实现。

类集这个概念是从JDK1.2(Java2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完整的提出类集的概念。

类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。

JAVA常用类库超重点——集合_第1张图片


Collection接口(重点

Collection接口是在整个Java类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。此接口定义在java.util包中

此接口定义如下:

public interface Collection extends Iterable

此接口使用了泛型技术,在JDK1.5滞后为了使类集操作的更加安全,所以引入了泛型

此接口的常用方法如下所示。

No. 方法名称 类型 描述
1 public boolean add(E e) 普通 向集合中插入一个元素

         2 

public boolean addAll(Collection c) 普通 向集合中插入一组元素
3 public void clear() 普通 清空集合中的元素
4 public boolean contains(Object o) 普通 查找一个元素是否存在
5 public boolean containsAll(Collection c) 普通 查找一组元素是否存在
6 public boolean isEmpty() 普通 判断集合是否为空
7 public Iterator iterator() 普通 为Iterator接口实例化
8 public boolean remove(Object o) 普通 从集合中删除一个对象
9 public boolean removeAll(Collection c) 普通 从集合中删除一组对象
10 public boolean retainAll(Collection c) 普通 判断是否没有指定的集合
11 public int size() 普通 返回集合中元素的个数
12 public Obejct[] toArray() 普通 以对象数组的形式返回集合中的全部内容
13 public T[] toArray(T[] a) 普通 指定操作的泛型类型,并把内容返回
14 public boolean equals(Object o) 普通 从Object类中重写而来
15 public int hasCode() 普通 从Object类中重写而来

本接口中一共定义了15个方法,那么此接口的全部子类或子接口就将全部继承以上接口中的方法。

但是,我们在实际开发中不会直接使用Collection接口。而使用其操作的子接口:ListSet

之所以有这样的明文规定,也是在JDK1.2之后才有的,一开始在EJB中的最早模型中全部都是使用Collection操作的,所以很早之前开发代码都是以Collection为准,但是后来为了更加清楚的区分,集合中是否允许有重复元素,所以SUN公司在其开源项目——PetShop(宠物商店)中就开始推广List和Set的使用。


List接口(重点

在整个集合中,List是Collection的子接口,里面所有的内容都是允许重复的

List子接口的定义:

public interface List extends Collection

此接口上依然使用了泛型技术,此接口对于Collection接口来讲有如下的扩充方法:

No. 方法名称 类型 描述
1 public void add(int index,E element) 普通 在指定位置处添加元素
2 public boolean addAll(int index,Collection c) 普通 在指定位置处增加一组元素
3 public E get(int index) 普通 根据索引位置取出元素
4 public int indexOf(Obejct o) 普通 根据对象查找指定的位置,找不到返回-1
5 public int lastIndexOf(Object o) 普通 从后面向前查找位置,找不到返回-1
6 public ListIterator listIterator() 普通 返回ListIterator接口的实例
7 public ListIterator listIterator(int index) 普通 返回从指定位置的 ListIterator 接口的实例
8 public E remove(int index) 普通 删除指定位置的内容
9 public E set(int index,E element) 普通 修改指定位置的内容
10 public List subList(int fromIndex,int toIndex) 普通 返回范围内的子集合

在List接口中有以上10个方法是对已有的Collection接口进行得扩充。

所以,证明,List接口拥有比Collecton接口更多的操作方法。

了解List接口之后,那么该如何使用该接口呢?需要找到此接口的实现类,常用的实现类有下面这几个:

ArrayList、Vector、LinkedList;其中我们使用频率最高的,就是ArrayList了,其次是Vector,最后是LinkedList啦。


ArrayList类

也就是“动态数组”,可以自动扩容。

ArrayList类是List的子类,底层实现是数组(查询速度快, 增删元素慢)。

此类的定义如下:

public class ArrayList extends AbstractList implements List,RandomAccess,Cloneable,Serializable

此类继承了 AbstractList 类。AbstractList 是 List 接口的子类,AbstractList 是个抽象类。且实现了 RandomAccess 接口,因此支持随机访问,这是理所当然的,因为 ArrayList 是基于数组实现的。

 例子:增加及取得元素

import java.util.ArrayList;

public class Demo1 {
    public static void main(String[] args) {
        ArrayList data = new ArrayList<>();//实例化ArrayList对象
        data.add("Hello");//添加内容,此方法是从Collection接口继承而来
        data.add(0,"Lamp");//添加内容,此方法是List接口单独定义的
        data.add("World");
        System.out.println(data);//打印data对象调用toString()方法
    }
}

输出
[Lamp, Hello, World]

以上的操作向集合中增加了三个元素,其中在指定位置增加的操作是List接口单独定义的,随后进行输出的时候,实际上调用的是toString()方法完成输出的。

我们可以发现,此时的对象数组并没有长度的限制,长度可以任意长,只要内存够大。

例子:删除、循环打印操作

  • 使用remove()方法删除若干个元素,并且使用循环的方式输出。
  • 根据指定位置取得内容的方法,只有List接口才有定义,其他任何接口都没有任何定义
import java.util.ArrayList;

public class Demo1 {
    public static void main(String[] args) {
        ArrayList data = new ArrayList<>();//实例化ArrayList对象
        data.add("Hello");//添加内容,此方法是从Collection接口继承而来
        data.add(0,"Lamp");//添加内容,此方法是List接口单独定义的
        data.add("World");
        data.remove(1);//根据索引删除元素,此方法是List接口单独定义的。
        data.remove("World");//删除指定对象。
        for(int i =0;i

 这里只需要对于删除的元素的操作有个基本了解,后续再深入理解。


Vector类

与ArrayList一样,Vector本身也属于List接口的子类,此类的定义如下:

public class Vector extends AbstractList implements List,RandomAccess,Cloneable,Serializable

此类与ArrayList一样,都是AbstractList的子类。所以,此时的操作只要是List接口的子类就都按照List进行操作

import java.util.List;
import java.util.Vector;

public class Demo1 {
    public static void main(String[] args) {
        List all = new Vector<>();//多态,父类List引用指向子类Vector对象
        all.add("hello");
        all.add(0,"LAMP");
        all.add("world");
        all.remove(1);
        all.remove("world");
        System.out.println("集合中的内容是:");
        for(int i = 0;i

以上的操作结果与使用ArrayList本身没有任何区别,因为操作的时候是以接口为操作的标准。

但是Vector属于java元老级的操作类,是最早的提供了动态对象数组的操作类,在JDK1.0的时候就已经推出了此类的使用,只是后来在JDK1.2之后引入了java类集合框架。但是为了照顾很多已经习惯于使用Vector的用户,所以在JDK1.2之后将Vector进行了升级,让其多实现了一个List接口,这样才将这个类继续保留了下来。


Vector 类和 ArrayList 类的区别(笔试重点

No. 区别点 ArrayList Vector
1 时间 是新的类,是在JDK1.2之后推出的 是旧的类,是在JDK1.0的时候就定义了的
2 性能 性能较高,是采用了异步处理 性能较低,是采用了同步处理
3 输出 支持 Iterator、ListIterator 输出 除了支持 Iterator、ListIterator 输出,还支持 Enumeration 输出

Iterator(绝对重点

俗称迭代器,基本操作原理:是不断判断是否有下一个元素,如果有,则直接输出。

此接口定义如下:

public interface Iterator

要想使用此接口,则必须使用Collection接口,在Collection接口中规定了一个iterator()方法,可以用于Iterator接口进行实例化操作。

此接口定义了如下三个方法:

No. 方法名称 类型 描述
1 boolean hasNext() 普通 判断是否有下个元素
2 E next() 普通 取出内容
3 void remove() 普通 删除当前内容

通过Collection接口为其进行实例化之后,一定要记住,Iterator中的操作指针是在第一个元素之上的,当调用next()

方法的时候,选取当前指针指向的值并向下移动,使用hasNext()可以检查序列中是否还有元素。

JAVA常用类库超重点——集合_第2张图片

例:观察Iterator输出

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        Collection l = new ArrayList<>();//创建一个动态数组对象
        l.add("A");
        l.add("B");
        l.add("C");
        l.add("D");
        l.add("E");
        Iterator iter = l.iterator();//创建一个迭代器对象
        while(iter.hasNext()){ //判断是否有下一个元素
            String str = iter.next(); //取出当前元素
            System.out.println(str); 
        }
    }
}
输出
A
B
C
D
E

以上的操作是Iterator接口使用最多的形式,也是一个标准的输出形式。

但是在使用Iterator输出的时候有一点必须注意,在进行迭代输出的时候如果想要删除当前元素,则只能使用Iterator接口中的remove()方法,而不能使用集合中的remove()方法。否则将出现未知的错误。

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        Collection l = new ArrayList<>();
        l.add("A");
        l.add("B");
        l.add("C");
        l.add("D");
        l.add("E");
        Iterator iter = l.iterator();
        while(iter.hasNext()){
            String str = iter.next();
            if(str == "C"){
                l.remove(str);//这是错误的,调用了集合中的删除方法
            }
            else{
                System.out.println(str + "、");
            }
        }
    }
}

程序出现了异常,因为原本的要输出的集合的内容被破坏了。

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        Collection l = new ArrayList<>();
        l.add("A");
        l.add("B");
        l.add("C");
        l.add("D");
        l.add("E");
        Iterator iter = l.iterator();
        while(iter.hasNext()){
            String str = iter.next();
            if(str == "C"){
                iter.remove();
            }
            else{
                System.out.println(str);
            }
        }
    }
}
输出
A
B
D
E

这才是正确的方式

但是,从实际开发的角度来看,元素的删除操作出现的几率是很小的,基本上可以忽略,即:集合中很少有删除元素的操作。

Iterator接口本身可以完成输出的功能,但是此接口只能进行由前向后的单向输出。如果要想进行双向输出,则必须使用其子接口——ListIterator。


ListIterator(理解)

ListIterator是可以进行双向输出的迭代接口,此接口定义如下:

public interface ListIterator extends Iterator

此接口是Iterator的子接口,此接口中定义了以下的操作方法:

No. 方法名称 类型 描述
1 void add(E e) 普通 增加元素
2 boolean hasPrevious() 普通 判断是否有前一个元素
3 E previous() 普通 取出前一个元素
4 void set(E e) 普通 修改元素的内容
5 int previousIndex() 普通 前一个索引的位置
6 int nextIndex() 普通 后一个索引的位置

但是如果想要使用ListIterator接口,则必须依靠List接口进行实例化。

List接口中定义了以下方法:ListIterator listIterator()

例:使用ListIterator输出:

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        List l = new ArrayList<>(); //创建动态数组对象,并指定存入整形数据
        l.add(1); //添加数据
        l.add(2);
        l.add(3);
        l.add(4);
        l.add(5);
        ListIterator iter =l.listIterator();创建迭代器
        System.out.println("从前向后输出");
        while(iter.hasNext()){ //判断下一位有无元素
            Integer a = iter.next();
            System.out.print(a + "、");
        }
        //根据迭代器模型图,此时指针以及移动到存放元素“5”之后的那个位置。
        System.out.println();
        System.out.println("从后向前输出");
        while(iter.hasPrevious()){ //判断上一位有无元素
            Integer b = iter.previous();
            System.out.print(b + "、");
        }
    }
}
输出
从前向后输出
1、2、3、4、5、
从后向前输出
5、4、3、2、1、

但是,此处有一点需要注意:如果想要进行由后向前的输出,则必须要先将指针移到最后一个数据之后的那个位置。

此接口一般使用较少。


增强版for循环——forEach

用于迭代数组或集合(Collection以下的)这种数据结构。

语法:

for(数据类型 变量名:数组或集合名称){}

示例

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        List l = new ArrayList<>();
        l.add("床前明月光");
        l.add("疑似地上霜");
        l.add("举头望明月");
        l.add("低头思故乡");
        for(String s:l) {
            System.out.println(s);
        }
    }
}
输出
床前明月光
疑似地上霜
举头望明月
低头思故乡

Set接口(重点

Set接口也是Collection的子接口,与List接口最大的不同在于,Set接口里面的内容是不允许重复的。

Set接口并没有对Collection接口进行扩充,基本上还是与Collection接口保持一致。因为此接口没有List接口中定义的get(int index)方法,所以无法使用循环进行输出。

那么在此接口中有两个常用的子类:HashSet、TreeSet


散列存放:HashSet(重点

既然Set接口并没有扩充任何的Collection接口中的内容,所以实用的方法全部都是Collection接口定义而来的。

HashSet属于散列的存放类集,里面的内容是无序存放的。

此类实现Set接口,由哈希表(实际上是HashMap实例)支持。

范例:观察HashSet

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        HashSet set = new HashSet<>();
        set.add("锄禾日当午");
        set.add("汗滴禾下土");
        set.add("谁知盘中餐");
        set.add("粒粒皆辛苦");
        set.add("粒粒皆辛苦");
        //转为数组进行输出
        String[] str = set.toArray(new String[] {});
        for(int i = 0;i< str.length;i++){
            System.out.println(str[i]+" ");
        }
        //直接输出
        System.out.println(set);
        //foreach输出
        for (String s:set) {
            System.out.println(s);
        }
    }
}
输出
汗滴禾下土 
谁知盘中餐 
锄禾日当午 
粒粒皆辛苦 
[汗滴禾下土, 谁知盘中餐, 锄禾日当午, 粒粒皆辛苦]
汗滴禾下土
谁知盘中餐
锄禾日当午
粒粒皆辛苦

实例化的HashSet对象,本身属于无序存放,而且是不能有重复内容的,所以在输出的时候,重复存入的内容,只输出一次,而且也并不是按我们存入的顺序输出的,具体是怎么输出的,以后会有讲解。


排序的子类:TreeSet(重点

内部原理是二叉树,与HashSet不同的是,TreeSet本身属于排序的子类,此类的定义如下

public class TreeSet extends AbstractSet implements NavigableSet,Cloneable,Serializable

下面通过代码来观察其是如何进行排序的

例:TreeSet排序观察

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        TreeSet set = new TreeSet<>();
        set.add("A");
        set.add("C");
        set.add("D");
        set.add("B");
        for (String s:set) {
            System.out.println(s);
        }
    }
}
输出
A
B
C
D

虽然在增加元素的时候属于无序操作,但是增加之后却可以为用户进行排序功能的实现,它不是根据你输入的顺序进行排序的,而是根据阿斯科码的大小进行排序的。


排序的说明(重点

既然Set接口的TreeSet类本身是允许排序,那么现在自定义一个类是否可以进行对象的排序呢?

例:定义Preson类

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        TreeSet set = new TreeSet<>();
        Preson p1 = new Preson("张三",11);
        Preson p2 = new Preson("李四",12);
        Preson p3 = new Preson("王五",10);
        set.add(p1);
        set.add(p2);
        set.add(p3);
        for (Preson p:set) {
            System.out.println(p);
        }
    }
    public static class Preson{
        private String name;
        private int age;

        public Preson(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public Preson() {
        }

        public String getName() {
            return name;
        }

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

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }


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

执行以上的操作代码之后,发现出现了如下的错误提示:

Exception in thread "main" java.lang.ClassCastException: class com.practice.demo3.Demo1$Preson cannot be cast to class java.lang.Comparable (com.practice.demo3.Demo1$Preson is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
	at java.base/java.util.TreeMap.compare(TreeMap.java:1291)
	at java.base/java.util.TreeMap.put(TreeMap.java:536)
	at java.base/java.util.TreeSet.add(TreeSet.java:255)
	at com.practice.demo3.Demo1.main(Demo1.java:10)

此时的提示是:Preson类不能向Comparable接口转型的问题

所以,证明:如果现在要是想进行排序的话,则必须在Person类中实现Comparable接口。

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        TreeSet set = new TreeSet<>();
        Preson p1 = new Preson("张三",11);
        Preson p2 = new Preson("李四",12);
        Preson p3 = new Preson("王五",10);
        Preson p4 = new Preson("钱六",10);
        set.add(p1);
        set.add(p2);
        set.add(p3);
        set.add(p4);
        for (Preson p:set) {
            System.out.println(p);
        }
    }
    public static class Preson implements Comparable{
        private String name;
        private int age;

        //实现Comparable接口的compareTo抽象方法
        //this大返回正整数,this小返回负整数,相等返回0
        @Override
        public int compareTo(Preson o) {
            if(this.age > o.age){
                return 1;
            }else if(this.age < o.age){
                return -1;
            }else{
                return 0;
            }
        }

        public Preson(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public Preson() {
        }

        public String getName() {
            return name;
        }

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

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }


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


    }
}
输出
Preson{name='王五', age=10}
Preson{name='张三', age=11}
Preson{name='李四', age=12}

此次代码运行成功,如上所示,compareTo方法中规定了按年龄大小从小到大排序(返回值小于0排在前,大于0排在后),又发现钱六没有了,因为钱六的年龄和王五的年龄是一样的,在调用comapreTo的时候,返回值是0,则会被程序认为是同一个对象。此时必须修改Preson类,如果年龄相等的话,按字符串进行排序。

public int compareTo(Preson o) {
            if(this.age > o.age){
                return 1;
            }else if(this.age < o.age){
                return -1;
            }else{
                return this.name.compareTo(o.name);//String类也重写了compareTo方法,调用String类的compareTo方法进行比较
            }
        }

此时再运行程序,发现钱六出现了,如果加入的是同一个人的信息的话,会被认为是重复元素,无法继续加入。

Preson{name='王五', age=10}
Preson{name='钱六', age=10}
Preson{name='张三', age=11}
Preson{name='李四', age=12}

关于重复元素的说明(重点

之前使用Comparable完成的对于重复元素的判断,那么Set接口定义的时候本身就是不允许重复元素的,那么证明如果现在真的是有重复元素的话,使用HashSet也同样可以进行区分。

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        HashSet set = new HashSet<>();
        Preson p1 = new Preson("张三",18);
        Preson p2 = new Preson("李四",12);
        Preson p3 = new Preson("王五",10);
        Preson p4 = new Preson("钱六",10);
        Preson p5 = new Preson("钱六",10);
        set.add(p1);
        set.add(p2);
        set.add(p3);
        set.add(p4);
        set.add(p5);
        for (Preson p:set) {
            System.out.println(p);
        }
    }
    public static class Preson{
        private String name;
        private int age;


        public Preson(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public Preson() {
        }

        public String getName() {
            return name;
        }

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

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }


        @Override
        public String toString() {
            return "Preson{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}
输出
Preson{name='张三', age=18}
Preson{name='钱六', age=10}
Preson{name='钱六', age=10}
Preson{name='王五', age=10}
Preson{name='李四', age=12}

此时发现,并没有去掉所谓的重复元素,也就是说之前的操作并不是真正意义上的重复元素判断,而是通过Comparable接口间接完成的。

如果想要判断两个对象是否相等,则必须使用Object类的equals()方法。

从最正规的来讲,如果要想判断两个对象是否相等,则有两种方法可以完成:

  • 第一种判断两个对象的编码是否一致,这个方法需要通过 hashCode()完成,即:每个对象有唯一的编码
  • 还需要进一步验证对象中的每个属性是否相等,需要通过 equals()完成。

此时需要重写Object中的hashCode方法,此方法表示一个唯一的编码,一般是通过公示计算出来的。

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        HashSet set = new HashSet<>();
        Preson p1 = new Preson("张三",18);
        Preson p2 = new Preson("李四",12);
        Preson p3 = new Preson("王五",10);
        Preson p4 = new Preson("钱六",10);
        Preson p5 = new Preson("钱六",10);
        set.add(p1);
        set.add(p2);
        set.add(p3);
        set.add(p4);
        set.add(p5);
        for (Preson p:set) {
            System.out.println(p);
        }
    }
    public static class Preson{
        private String name;
        private int age;


        public Preson(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public Preson() {
        }

        public String getName() {
            return name;
        }

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

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Preson preson = (Preson) o;
            return age == preson.age && Objects.equals(name, preson.name);
        }

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

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

此时运行程序发现,已经没有重复元素了,所以想要去掉重复元素的话,需要依靠 hashCode()和 equals()方法共同完成。


小结

关于TreeSet的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现Comparable接口,则不能实现TreeSet的排序,会报错。换句话说要添加到TreeSet集合中的对象的类型必须实现了Comparable接口。

不过TreeSet的集合因为借用了Comparable接口,同时可以去除重复值,而HashSet虽然是Set类的子接口,但是对于没有重写没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet 集合中也是不能去掉重复值的。


Map接口(重点

以上的Collection中,每次操作的都是一个对象,如果现在假设要操作一对对象,则就必须使用Map了,类似于以下一种情况

  • 张三    123456
  • 李四    234567

那么保存以上的信息的时候使用Collection就不那么方便,所以要使用Map接口。里面的所有内容都按照key->value的形式保存,也称为二元偶对象,Map集合的键Key不可重复。

此接口定义如下:

public interface Map

此接口与Collection接口没有任何关系,是第二大的集合操作接口,此接口常用方法如下:

No. 方法名称 类型 描述
1 void clear() 普通 清空Map集合中的内容
2 boolean containsKey(Obejct key) 普通 判断集合中是否存在指定的key
3 boolean containsValue(Objec tvalue) 普通 判断集合中是否存在指定的 value
4 Set> entrySet() 普通 将Map接口变为Set集合
5 V get(Object key) 普通 根据key找到其对应的value
6 boolean isEmpty() 普通 判断是否为空
7 Set keySet() 普通 将全部的key变为Set集合
8 Collection values() 普通 将全部的value变为Collection集合
9 V put(K key,V value) 普通 向集合中添加元素
10 void putAll(Map m) 普通 增加一组集合
11 V remove(Object key) 普通 根据key删除内容

Map本身是一个接口,所以一般会使用以下的几个子类:HashMap、TreeMap、Hashtable


HashMap(重点

HashMap是Map的子类,此类的定义如下:

public class HashMap extends AbstractMap implements Map,Cloneable,Serializable

此类继承了AbstractMap类,同时可以被克隆,可以被序列化下来。

例:向HashMap内添加,删除,输出内容

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        HashMap date = new HashMap<>();
        date.put("key1","锄禾日当午");
        date.put("key2","汗滴禾下土");
        date.put("key3","谁知盘中餐");
        Set set = date.keySet(); //将key值存入一个Set集合
        for (String s:set) {             //根据key获得value并循环输出value
            System.out.print(s+"->");
            System.out.println(date.get(s));
        }
        date.remove("key1");        //删除key1
        Collection values = date.values(); //将value存入一个Collection集合
        for (String s:values) {              //循环输出value
            System.out.println(s);
        }
    }
}

输出

key1->锄禾日当午
key2->汗滴禾下土
key3->谁知盘中餐
汗滴禾下土
谁知盘中餐

以上是Map接口在开发中最基本的操作过程,根据指定的key找到value,如果没有找到,返回null,找到了就返回具体的内人,然后通过foreach输出,foreach的原理就是利用迭代器Iterator。HashMap本身属于无序存放的。


Hashtable(重点

Hashtable是一个最早的key->value的操作类,本身是在JDK1.0的时候推出的,其基本操作与HashMap是类似的。

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        Hashtable date = new Hashtable<>();
        date.put("key1","锄禾日当午");
        date.put("key2","汗滴禾下土");
        date.put("key3","谁知盘中餐");
        Set set = date.keySet(); //将key值存入一个Set集合
        for (String s:set) {             //根据key获得value并循环输出value
            System.out.print(s+"->");
            System.out.println(date.get(s));
        }
        date.remove("key1");        //删除key1
        Collection values = date.values(); //将value存入一个Collection集合
        for (String s:values) {              //循环输出value
            System.out.println(s);
        }
    }
}

输出

key3->谁知盘中餐
key2->汗滴禾下土
key1->锄禾日当午
谁知盘中餐
汗滴禾下土

我们会发现它的输出顺序和HashMap是有所不同的,那是因为它们内部计算方式的不同,但是操作方式上几乎没有什么不同,因为本身就是以Map为操作标准的,但是Hashtable中是不能向集合中插入null值的。


HashMap 与 Hashtable 的区别(重点

在整个集合中除了 ArrayList 和 Vector 的区别之外,另外一个最重要的区别就是 HashMap 与 Hashtable 的区别。

No. 区别点 HashMap Hashtable
1 推出时间 JDK1.2之后推出的,新的操作类 JDK1.0时推出的,旧的操作类
2 性能 线程不安全的异步处理,性能较高 线程安全的同步处理,性能较低
3 null 允许设置为null 不允许设置,否则将出现空指针异常

 Map集合存储自定义对象问题

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        HashMap date = new HashMap<>();
        Book book1 = new Book("金苹果","介绍金苹果");
        Book book2 = new Book("银苹果","介绍银苹果");
        date.put(book1,"我们人生的第一本书");
        date.put(book2,"我们人生的第二本书");
        book1.setName("铜苹果");
    /*book1的name值发生改变,那么该对象的哈希值就发生了改变
    改变了之后,再根据这个改变了的key去找value的时候是找不到的,会返回null。
    那么假设这个哈希表已经存到加载因子决定的临界位置
    那么更改name值之后哈希表会发生散列,扩容并重新存储数据
    散列后哈希表会根据新的book1的新哈希值将key和value存放到新的位置
    注意这个时候旧的book1存储的value依然存在,位置也没变。只是没有key指向它了*/
        System.out.println(date.get(book1));
    /*这个时候调用get,是找不到数据的
    因为get是调用key去找对应的value
    此时key指定的位置发生了改变,已经不再指向之前的位置了
    而是新的位置,但是新的位置,并没有存入value,所以找到的是null*/
        Book book3 = new Book("金苹果","介绍金苹果");
    /*再看这一步,新建一个对象
    它的哈希值和之前的book1一样,我们会心想那这样,应该能找到最初的book1对应的value了吧*/
        System.out.println(date.get(book3));
    /*输出仍为null,首先,哈希值一样了没错,
    它也找到了"我们人生的第一本书"这个值,但是这个时候哈希表还会进行equals比对
    金苹果和铜苹果不同,所以equals比对失败,依然找不到数据*/
    }
    static class Book{
        private String name;
        private String info;

        public Book(String name, String info) {
            this.name = name;
            this.info = info;
        }

        public Book() {
        }

        public String getName() {
            return name;
        }

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

        public String getInfo() {
            return info;
        }

        public void setInfo(String info) {
            this.info = info;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Book book = (Book) o;
            return Objects.equals(name, book.name) && Objects.equals(info, book.info);
        }

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

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

输出

null
null

我们会发现,输出全是null,找不到数据了,我们不要随便更改key的对象的值,特别是key是自定义对象的时候,因为这样很容易造成哈希值错乱,而导致找不到数据,那么如果这个自定义对象它就是会变,那怎么办?解决办法很简单,不要将这个对象存储到key的位置就可以了。


此文仅作为本人的学习笔记,如有错误,还请个位指正,非常感谢!

你可能感兴趣的:(java)