List 集合

List 集合中元素有序、可重复,集合中每个元素都有其对应的索引顺序。

List 判断两个对象相等,只要通过 equals 方法比较返回 true 即可。

看个例子:

public class A {
    public boolean equals(Object obj) {
        return true;
    }
}

import java.util.ArrayList;
import java.util.List;

public class ListTest2 {
    public static void main(String[] args) {
        List books = new ArrayList();
        books.add(new String("a"));
        books.add(new String("b"));
        books.add(new String("c"));
        System.out.println(books);
        books.remove(new A());
        System.out.println(books);
        books.remove(new A());
        System.out.println(books);
    }
}

当试图删除一个 A 对象时,List 会调用 A 对象的 equals 方法依次与集合元素进行比较。如果 equals 方法以某个集合元素作为参数时返回 true,List 将会删除该元素。这里 A 重写了 equals 方法,总是返回 true,所以每次都会从 List 集合中删除一个元素。

ArrayList 类

ArrayList 类是基于数组实现的 List 类,完全支持前面介绍的 List 接口的全部功能。

ArrayList 封装了一个动态的、允许再分配的 Object[] 数组。

Set 集合

HashSet 类

  • 元素没有顺序,集合元素的值可以是 null

  • HashSet 不是同步的,假设有多个线程同时修改了 HashSet 集合时,必须通过代码来保证其同步

HashSet 判断元素相等的标准是两个对象通过 equals() 比较相等,同时两个对象的 hashCode()返回值也相等。

hashCode 和 equals 符合这样一个约定:equals 返回 true, hashCode 必须相等。很多 Java 类库中的代码都是按照这种约定使用这两个方法的,比 如 HashSet。这也是为什么我们要求如果一个类覆盖了 hashCode 方法,就 一定要覆盖 equals 方法,并保证方法的实现符合上述约定

当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来获得该对象的 hashCode 值,然后根据该 hashCode 值决定该对象在 HashSet 中的存储位置。

HashSet 中每个能存储元素的槽位称为桶(bucket)。如果多个元素的 hashCode 值相同,但它们通过 equals 方法比较返回 false,就需要在一个桶里放多个元素,这会导致性能下降。所以,建议在需要把某个类的对象保存到 HashSet 集合时,重写该类的 equals 和 hashCode 方法,尽量保证两个对象通过 equals 方法比较返回 true 时,他们的 hashCode 方法返回值也相等。

当把可变对象添加到 HashSet 中后,需要特别小心,尽量不要去修改可变对象中参与计算 hashCode() 、equals() 方法的实例变量,否则会导致 HashSet 无法正确访问这些集合元素。

看个例子:

public class R {
    int count;
    public R(int count) {
        this.count = count;
    }
    public String toString() {
        return "R[count:" + count + "]";
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj != null && obj.getClass() == R.class) {
            R r = (R)obj;
            return this.count == r.count;
        }
        return false;
    }
    public int hashCode() {
        return this.count;
    }
}

import java.util.HashSet;
import java.util.Iterator;

public class HashSetTest2 {
    public static void main(String[] args) {
        HashSet hs = new HashSet();
        hs.add(new R(5));
        hs.add(new R(-3));
        hs.add(new R(9));
        hs.add(new R(-2));
        System.out.println(hs);
        Iterator it = hs.iterator();
        R first = (R)it.next();
        first.count = -3;
        System.out.println(hs);
        hs.remove(new R(-3));
        System.out.println(hs);
        System.out.println("hs 是否包含 count 为 -3 的 R 对象" + hs.contains(new R(-3)));
        System.out.println("hs 是否包含 count 为 -2 的 R 对象" + hs.contains(new R(-2)));
    }
}

/*
[R[count:-2], R[count:-3], R[count:5], R[count:9]]
[R[count:-3], R[count:-3], R[count:5], R[count:9]]
[R[count:-3], R[count:5], R[count:9]]
hs 是否包含 count 为 -3 的 R 对象false
hs 是否包含 count 为 -2 的 R 对象false
*/

LinkedHashSet 类

LinkedHashSet 是 HashSet 的子类,同样根据 hashCode 值来决定元素的存储位置。但是使用链表维护元素的次序,使得当遍历 LinkedHashSet 集合里的元素时,LinkedHashSet 会按元素的添加顺序访问集合里的元素。

LinkedHashSet 需要维护元素的插入顺序,因此性能略低于 HashSet,但在迭代访问 Set 里的全部元素时会有很好的性能,因为它以链表维护内部的顺序。

TreeSet 类

TreeSet 是 SortedSet 接口的实现类,顾名思义这是一种排序的 Set 集合。

TreeSet 底层使用 TreeMap 实现,采用红黑树的数据结构来存储集合元素。TreeSet 支持两种排序方法:自然排序和定制排序。默认情况下,使用自然排序。

自然排序

Java 提供了 Comparable 接口,接口定义了一个 compareTo(Object obj) 方法。实现该接口的类必须实现该抽象方法。

compareTo(Object obj) 比较规则如下:

  • obj1.compareTo(obj2) 返回值为 0,表明相等
  • obj1.compareTo(obj2) 返回值大于 0,表明 obj1 > obj2
  • obj1.compareTo(obj2) 返回值小于 0,表明 obj1 < obj2

TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素大小关系,再将集合元素按升序排序,这就是自然排序。所以自然排序中的元素对象都必须实现了 Comparable 接口。

如果两个对象通过 compareTo(Object obj) 比较相等, 即返回值为0,TreeSet 认为它们相等,那么新对象将无法添加到 TreeSet 集合中。

如果希望 TreeSet 能正常工作,TreeSet 只能添加同一种类型的对象。

定制排序

如果需要实现定制排序,需要在创建 TreeSet 集合对象时,提供一个 Comparator 对象与该 TreeSet 集合关联。Comparator 是一个函数式接口,可以使用 Lambda 表达式代替。

通过定制排序方式时,依然不可以向 TreeSet 中添加不同类型的对象,否则引发 ClassCastException 异常。此时集合判断两个元素相等的标准是:通过 Comparator 比较两个元素返回了 0, 这样 TreeSet 也不会把第二个元素添加到集合中。

import java.util.TreeSet;

public class TreeSettest4 {
    public static void main(String[] args) {
        TreeSet ts = new TreeSet((o1, o2) -> {
            M m1 = (M) o1;
            M m2 = (M) o2;
            return m1.age > m2.age ? -1 : m1.age < m2.age ? 1: 0;
        });
        ts.add(new M(5));
        ts.add(new M(-3));
        ts.add(new M(9));
        System.out.println(ts);
    }
}

上面使用目标类型为 Comparator 的 Lambda 表达式,它负责 ts 集合的排序。所有 M 类无需实现 Comparable 接口,而是由 TreeSet 关联的 Lambda 表达式负责元素的排序。

在实现 compareTo 方法时,强烈推荐与 equals 结果一致,否则可能会出现一些奇怪的错误。因为有些类是根据 equals 来判断重复性,有些是利用自然排序 x.compareTo(y) == 0 来判断。compareTo 是判断元素在排序中的位置是否相等,equals 是判断元素是否相等,既然一个决定排序位置,一个决定相等,所以我们非常有必要确保当排序位置相同时,其equals也应该相等。

EnumSet 类

EnumSet 是专为枚举类设计的集合类,EnumSet 中的所有元素都必须是指定枚举类型的枚举类,该枚举类型在创建 EnumSet 时显式或隐式的的指定。

EnumSet 的集合元素是有序的,以枚举值在 Enum 类内的定义顺序来决定集合元素的顺序。EnumSet 集合不允许插入 null 元素。

EnumSet 内部以位向量的形式存储,这种存储形式紧凑高效,占用内存很小,运行效率很高。尤其是在进行批量操作时,比如调用 containsAll 和 retainAll 方法时。

Map 集合

定义:Map 用于保存具有映射关系的数据,key 和 value 之间存在单向的一对一关系,key 不允许重复。

Set 与 Map 之间关系非常密切,如果把 key-value 对中的 value 当成 key 的附庸,key 在哪里,value 就在哪里。这样就可以像对待 Set 一样对待 Map 了。

实际上,Map 提供了一个 Entry 内部类来封装 key-value 对,而计算 Entry 存储时则只考虑 Entry 封装的 key。从源码来看,Java 是先实现了 Map,然后通过包装一个所有 value 都为 null 的 Map 就实现了 Set 集合。

HashMap 实现类

HashMap 中用作 key 的对象必须实现 hashCode() 方法和 equals() 方法。

HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法比较返回 true,两个 key 的 hashCode 值也相等。

HashMap 判断两个 value 相等的标准是:两个对象通过 equals() 方法返回 true 即可。

与 HashSet 类似,当使用自定义类作为 HashMap 的 key 时,如果重写该类的 equals() 方法 和 hashCode() 方法,则应该保证两个方法的判断标准一致,即当两个 key 通过 equals() 方法比较返回 true 时,两个 key 的 hashCode() 方法返回值也应该相同。

与 HashSet 类似,尽量不要使用可变对象作为 HashMap 的 key,如果使用了,则尽量不要在程序中修改作为 key 的可变对象。

LinkedHashMap 实现类

LinkedHashMap 也使用双向链表来维护 key-value 对的次序(其实只需要考虑 key 的次序),该链表负责维护 Map 的迭代顺序,迭代顺序与 key-value 对的插入顺序保持一致。

import java.util.LinkedHashMap;

public class LinkedHashMapTest {
    public static void main(String[] args) {
        LinkedHashMap scores = new LinkedHashMap();
        scores.put("Chinses", 80);
        scores.put("English", 82);
        scores.put("Math", 76);
        scores.forEach((key ,value) -> System.out.println(key + "--->" + value));
    }
}

TreeMap 实现类

TreeMap 是一个红黑树数据结构,每个 key-value 对即作为红黑树的一个节点。TreeMap 存储 key-value 对节点时,需要根据 key 对节点进行排序。TreeMap 可以保证所有的 key-value 对处于有序状态。

两种排序方式:

  • 自然排序:TreeMap 的所有 key 必须实现 Comparable 接口,而且所有的 key 应该是同一个类的对象,否则会抛出 ClassCastException 异常
  • 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。采用定制排序时不要求 Map 的 key 实现 Comparable 接口

TreeMap 判断两个 key 相等的标准是:两个 key 通过 compareTo 方法返回 0。

类似于 TreeSet,如果使用自定义类作为 TreeMap 的 key,为了让 TreeMap 良好的工作,则重写该类的 equals() 方法和 compareTo() 方法时应该保持一致的结果:两个 key 通过 equals 方法比较返回 true 时,它们通过 compareTo 方法比较应该返回 0。

在实现 compareTo 方法时,强烈推荐与 equals 结果一致,否则可能会出现一些奇怪的错误。因为有些类是根据 equals 来判断重复性,有些是利用自然排序 x.compareTo(y) == 0 来判断。compareTo 是判断元素在排序中的位置是否相等,equals 是判断元素是否相等,既然一个决定排序位置,一个决定相等,所以我们非常有必要确保当排序位置相同时,其equals也应该相等。

官方文档的说明:

Virtually all Java core classes that implement Comparable have natural orderings that are consistent with equals.

EnumMap 实现类

EnumMap 的 key 必须是单个枚举类的枚举值。

EnumMap 具有以下特征:

  • EnumMap 在内部以数组形式保存

  • EnumMap 根据 key 的自然顺序(即枚举值在枚举类中的定义顺序)来维护 key-value 对的顺序

  • EnumMap 不能使用 null 作为 key 值

创建 EnumMap 时必须指定一个枚举类,从而将该 EnumMap 和指定枚举类相关联。

欢迎关注我的公众号

Java 基础篇之集合的更多相关文章

  1. 图学java基础篇之集合工具

    两个工具类 java.utils下又两个集合相关_(准确来说其中一个是数组的)_的工具类:Arrays和Collections,其中提供了很多针对集合的操作,其中涵盖了一下几个方面: 拷贝.填充.反转 ...

  2. 图学java基础篇之集合

    (本文部分图片引用自其他博客,最后有链接,侵删.由于笔记使用markdown记录,格式可能不是太好看,见谅) 集合结构 红字为java.util包下的,绿字为concurrent包下扩展的与并发相关的 ...

  3. java基础篇 之 集合概述(List)

    list,有序集合,元素可重复 LinkedList:底层用链表实现,查找慢,增删快.为什么?? ArrayList:底层用数组实现,查找看,增删慢.为什么?? Vector:跟ArrayList一样 ...

  4. 金三银四跳槽季,BAT美团滴滴java面试大纲(带答案版)之一:Java基础篇

    Java基础篇: 题记:本系列文章,会尽量模拟面试现场对话情景, 用口语而非书面语 ,采用问答形式来展现.另外每一个问题都附上“延伸”,这部分内容是帮助小伙伴们更深的理解一些底层细节的补充,在面试中可 ...

  5. 小白—职场之Java基础篇

    java基础篇 java基础 目录 1.java是一种什么语言,jdk,jre,jvm三者的区别 2.java 1.5之后的三大版本 3.java跨平台及其原理 4.java 语言的特点 5.什么是字 ...

  6. Java基础篇(JVM)——类加载机制

    这是Java基础篇(JVM)的第二篇文章,紧接着上一篇字节码详解,这篇我们来详解Java的类加载机制,也就是如何把字节码代表的类信息加载进入内存中. 我们知道,不管是根据类新建对象,还是直接使用类变量 ...

  7. java基础篇---I/O技术

    java基础篇---I/O技术   对于任何程序设计语言而言,输入输出(I/O)系统都是比较复杂的而且还是比较核心的.在java.io.包中提供了相关的API. java中流的概念划分 流的方向: 输 ...

  8. java基础篇---HTTP协议

    java基础篇---HTTP协议   HTTP协议一直是自己的薄弱点,也没抽太多时间去看这方面的内容,今天兴致来了就在网上搜了下关于http协议,发现有园友写了一篇非常好的博文,博文地址:(http: ...

  9. java基础篇---I/O技术(三)

    接上一篇java基础篇---I/O技术(二) Java对象的序列化和反序列化 什么叫对象的序列化和反序列化 要想完成对象的输入或输出,还必须依靠对象输出流(ObjectOutputStream)和对象 ...

随机推荐

  1. html基础——下拉式菜单

    一个网站能否让用户容易使用该网站往往是由菜单栏体现出来,因为它为网页的大多数页面提供功能入口.一个轻轻的点击以后,即可显示出菜单项,将网站的大部分页面和功能显示出来让用户清楚了解从而用户节约一定的时间 ...

  2. CodeForces 677D. Vanya and Treasure 枚举行列

    677D. Vanya and Treasure 题意: 给定一张n*m的图,图上每个点标有1~p的值,你初始在(1,1)点,你必须按照V:1,2,3...p的顺序走图上的点,问你如何走时间最少. 思 ...

  3. 牛客OI测试赛 C 序列 思维

    链接:https://www.nowcoder.com/acm/contest/181/C来源:牛客网 题目描述 小a有n个数,他想把他们划分为连续的权值相等的k段,但他不知道这是否可行. 每个数都必 ...

  4. jquery中的 $(function(){ .. }) 函数

    2017-04-29 在讲解jquery中的 $(function(){ .. }) 函数之前,我们先简单了解下匿名函数.匿名函数的形式为:(function(){ ... }),又如 functio ...

  5. Docker详解(一)

    目录 Docker简介 Docker组成 永远的HelloWorld 序言:众所周知,近几年的互联网各项技术发展的如火如荼,敏捷开发模式越来越普及,"快"似乎成为了行业的标准,于是 ...

  6. Dinic算法学习

    转自 此文虽为转载,但博主的网络流就是从这开始的,认为写的不错 网络流基本概念 什么是网络流 在一个有向图上选择一个源点,一个汇点,每一条边上都有一个流量上限(以下称为容量),即经过这条边的流量不能超 ...

  7. HDU 1223 还是畅通工程(最小生成树prim模板)

    一个很简单的prim模板,但虽然是模板,但也是最基础的,也要脱离模板熟练打出来 后期会更新kruskal写法 #include<iostream> #include<cstdio&g ...

  8. 锁和synchronized

    锁的常见概念 互斥: 同一时刻只有一个线程执行 临界区:一段需要互斥执行的代码 细粒度锁: 用不同的锁对受保护资源进行精细化管理. 细粒度锁可以提高并行度,是性能优化的一个重要手段 死锁 :一组互相竞 ...

  9. NameNode数据存储

    HDFS架构图 HDFS原理 1)  三大组件 NameNode. DataNode .SecondaryNameNode 2)NameNode 存储元数据(文件名.创建时间.大小.权限.文件与blo ...

  10. 洛谷 P1525 关押罪犯 NOIp2010提高组 (贪心+并查集)

    题目链接:https://www.luogu.org/problemnew/show/P1525 题目分析 通过分析,我们可以知道,这道题的抽象意义就是把一个带边权的无向图,分成两个点集,使得两个集合 ...