Java的集合类被定义在Java.util包中,主要有 4种集合,分别为List、Queue、Set和Map,每种
集合的具体分类如图

List:可重复
List是非常常用的数据类型,是有序的Collection,一共有三个实现类,分别是ArrayList、
Vector和LinkedList。
1.ArrayList:基于数组实现,增删慢,查询快,线程不安全
ArrayList是使用最广泛的List实现类,其内部数据结构基于数组实现,提供了对List的增加
(add)、删除(remove)和访问(get)功能。
ArrayList的缺点是对元素必须连续存储,当需要在ArrayList的中间位置插入或者删除元素时,
需要将待插入或者删除的节点后的所有元素进行移动,其修改代价较高,因此,ArrayList不适合随
机插入和删除的操作,更适合随机查找和遍历的操作。
ArrayList不需要在定义时指定数组的长度,在数组长度不能满足存储要求时,ArrayList会创建
一个新的更大的数组并将数组中已有的数据复制到新的数组中。
2.Vector:基于数组实现,增删慢,查询快,线程安全
Vector的数据结构和ArrayList一样,都是基于数组实现的,不同的是Vector支持线程同步,即
同一时刻只允许一个线程对Vector进行写操作(新增、删除、修改),以保证多线程环境下数据的一致性,但需要频繁地对Vector实例进行加锁和释放锁操作,因此,Vector的读写效率在整体上比
ArrayList低。
3.LinkedList:基于双向链表实现,增删快,查询慢,线程不安全
LinkedList采用双向链表结构存储元素,在对LinkedList进行插入和删除操作时,只需在对应的
节点上插入或删除元素,并将上一个节点元素的下一个节点的指针指向该节点即可,数据改动较
小,因此随机插入和删除效率很高。但在对LinkedList进行随机访问时,需要从链表头部一直遍历
到该节点为止,因此随机访问速度很慢。除此之外,LinkedList还提供了在List接口中未定义的方
法,用于操作链表头部和尾部的元素,因此有时可以被当作堆栈、队列或双向队列使用。
2.1.2 Queue
Queue是队列结构,Java中的常用队列如下。
◎ ArrayBlockingQueue:基于数组数据结构实现的有界阻塞队列。
◎ LinkedBlockingQueue:基于链表数据结构实现的有界阻塞队列。
◎ PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
◎ DelayQueue:支持延迟操作的无界阻塞队列。
◎ SynchronousQueue:用于线程同步的阻塞队列。
◎ LinkedTransferQueue:基于链表数据结构实现的无界阻塞队列。
◎ LinkedBlockingDeque:基于链表数据结构实现的双向阻塞队列。
Set:不可重复
Set核心是独一无二的性质,适用于存储无序且值不相等的元素。对象的相等性在本质上是对
象的HashCode值相同,Java依据对象的内存地址计算出对象的HashCode值。如果想要比较两个对
象是否相等,则必须同时覆盖对象的hashCode方法和equals方法,并且hashCode方法和equals方法的
返回值必须相同。
1.HashSet:HashTable实现,无序
HashSet存放的是散列值,它是按照元素的散列值来存取元素的。元素的散列值是通过元素的
hashCode方法计算得到的,HashSet首先判断两个元素的散列值是否相等,如果散列值相等,则接
着通过equals方法比较,如果equls方法返回的结果也为true,HashSet就将其视为同一个元素;如果
equals方法返回的结果为false,HashSet就不将其视为同一个元素。
2.TreeSet:二叉树实现
TreeSet基于二叉树的原理对新添加的对象按照指定的顺序排序(升序、降序),每添加一个
对象都会进行排序,并将对象插入二叉树指定的位置。
Integer和String等基础对象类型可以直接根据TreeSet的默认排序进行存储,而自定义的数据类
型必须实现Comparable接口,并且覆写其中的compareTo函数才可以按照预定义的顺序存储。若覆
写compare函数,则在升序时在this.对象小于指定对象的条件下返回-1,在降序时在this.对象大于指
定对象的条件下返回1。
3.LinkHashSet:HashTable实现数据存储,双向链表记录顺序
LinkedHashSet在底层使用LinkedHashMap存储元素,它继承了HashSet,所有的方法和操作都
与HashSet相同,因此LinkedHashSet的实现比较简单,只提供了 4个构造方法,并通过传递一个标
识参数调用父类的构造器,在底层构造一个LinkedHashMap来记录数据访问,其他相关操作与父类
HashSet相同,直接调用父类HashSet的方法即可。2.1.4 Map
1.HashMap:数组+链表存储数据,线程不安全
HashMap基于键的HashCode值唯一标识一条数据,同时基于键的HashCode值进行数据的存
取,因此可以快速地更新和查询数据,但其每次遍历的顺序无法保证相同。HashMap的key和value
允许为null。
HashMap是非线程安全的,即在同一时刻有多个线程同时写HashMap时将可能导致数据的不一
致。如果需要满足线程安全的条件,则可以用Collections的synchronizedMap方法使HashMap具有线
程安全的能力,或者使用ConcurrentHashMap。
HashMap的数据结构如图 2-2所示,其内部是一个数组,数组中的每个元素都是一个单向链
表,链表中的每个元素都是嵌套类Entry的实例,Entry实例包含4个属性:key、value、hash值和用
于指向单向链表下一个元素的next。
HashMap常用的参数如下。
◎ capacity:当前数组的容量,默认为 16,可以扩容,扩容后数组的大小为当前的两倍,因此
该值始终为2 n 。
◎ loadFactor:负载因子,默认为0.75。
◎ threshold:扩容的阈值,其值等于capacity×loadFactor。
HashMap在查找数据时,根据HashMap的Hash值可以快速定位到数组的具体下标,但是在找到
数组下标后需要对链表进行顺序遍历直到找到需要的数据,时间复杂度为O(n )。
为了减少链表遍历的开销,Java 8对HashMap进行了优化,将数据结构修改为数组+链表或红黑
树。在链表中的元素超过 8个以后,HashMap会将链表结构转换为红黑树结构以提高查询效率,因
此其时间复杂度为O(log N )。Java 8 HashMap的数据结构如图

2.ConcurrentHashMap:分段锁实现,线程安全
与HashMap不同,ConcurrentHashMap采用分段锁的思想实现并发操作,因此是线程安全的。
ConcurrentHashMap由多个Segment组成(Segment的数量也是锁的并发度),每个Segment均继承自
ReentrantLock并单独加锁,所以每次进行加锁操作时锁住的都是一个Segment,这样只要保证每个Segment都是线程安全的,也就实现了全局的线程安全。
在ConcurrentHashMap中有个concurrencyLevel参数表示并行级别,默认是 16,也就是说
ConcurrentHashMap默认由 16个Segments组成,在这种情况下最多同时支持 16个线程并发执行写操作,只要它们的操作分布在不同的Segment上即可。并行级别concurrencyLevel可以在初始化时设置,一旦初始化就不可更改。ConcurrentHashMap的每个Segment内部的数据结构都和HashMap相同。

3.HashTable:线程安全
HashTable是遗留类,很多映射的常用功能都与HashMap类似,不同的是它继承自Dictionary
类,并且是线程安全的,同一时刻只有一个线程能写HashTable,并发性不如ConcurrentHashMap。
4.TreeMap:基于二叉树数据结构
TreeMap基于二叉树数据结构存储数据,同时实现了SortedMap接口以保障元素的顺序存取,
默认按键值的升序排序,也可以自定义排序比较器。
TreeMap常用于实现排序的映射列表。在使用TreeMap时其key必须实现Comparable接口或采用
自定义的比较器,否则会抛出java.lang.ClassCastException异常。
5.LinkedHashMap:基于HashTable数据结构,使用链表保存插入顺序
LinkedHashMap为HashMap的子类,其内部使用链表保存元素的插入顺序,在通过Iterator遍历
LinkedHashMap时,会按照元素的插入顺序访问元素。
异常的概念
异常指在方法不能按照正常方式完成时,可以通过抛出异常的方式退出该方法,在异常中封装
了方法执行过程中的错误信息及原因,调用方在获取该异常后可根据业务的情况选择处理该异常或
者继续抛出该异常。
在方法在执行过程中出现异常时,Java异常处理机制会将代码的执行权交给异常处理器,异常
处理器根据在系统中定义的异常处理规则执行不同的异常处理逻辑(抛出异常或捕捉并处理异
常)。
2.2.2 异常分类
在Java中,Throwable是所有错误或异常的父类,Throwable又可分为Error和Exception,常见的
Error有AWTError、ThreadDeath,Exception又可分为RuntimeException和CheckedException

Error指Java程序运行错误,如果程序在启动时出现Error,则启动失败;如果程序在运行过程中
出现Error,则系统将退出进程。出现Error通常是因为系统的内部错误或资源耗尽,Error不能被在
运行过程中被动态处理。如果程序出现Error,则系统能做的工作也只能有记录错误的成因和安全
终止。
Exception指Java程序运行异常,即运行中的程序发生了人们不期望发生的事件,可以被Java异
常处理机制处理。Exception也是程序开发中异常处理的核心,可分为RuntimeException(运行时异
常)和CheckedException(检查异常),如图2-7所示。
◎ RuntimeException:指在Java虚拟机正常运行期间抛出的异常,RuntimeException可以被捕获
并处理,如果出现RuntimeException,那么一定是程序发生错误导致的。我们通常需要抛出该异常
或者捕获并处理该异常。常见的RuntimeException有NullPointerException、ClassCastException、
ArrayIndexOutOf BundsException等。
◎ CheckedException:指在编译阶段Java编译器会检查CheckedException异常并强制程序捕获和
处理此类异常,即要求程序在可能出现异常的地方通过try catch语句块捕获并处理异常。常见的
CheckedException有由于I/O 错误导致的IOException、SQLException、ClassNotFoundException等。 该类异常一般由于打开错误的文件、SQL语法错误、类不存在等引起。

反射机制
2.3.1 动态语言的概念
动态语言指程序在运行时可以改变其结构的语言,比如新的属性或方法的添加、删除等结构上
的变化。JavaScript、Ruby、Python等都属于动态语言;C、C++不属于动态语言。从反射的角度来
说,Java属于半动态语言。
2.3.2 反射机制的概念
反射机制指在程序运行过程中,对任意一个类都能获取其所有属性和方法,并且对任意一个对
象都能调用其任意一个方法。这种动态获取类和对象的信息,以及动态调用对象的方法的功能被称
为Java语言的反射机制。
2.3.3 反射的应用
Java中的对象有两种类型:编译时类型和运行时类型。编译时类型指在声明对象时所采用的类
型,运行时类型指为对象赋值时所采用的类型。
在如下代码中,persion对象的编译时类型为Person,运行时类型为Student,因此无法在编译时
获取在Student类中定义的方法:
因此,程序在编译期间无法预知该对象和类的真实信息,只能通过运行时信息来发现该对象和
类的真实信息,而其真实信息(对象的属性和方法)通常通过反射机制来获取,这便是Java语言中
反射机制的核心功能。
2.3.4 Java的反射API
Java的反射API主要用于在运行过程中动态生成类、接口或对象等信息,其常用API如下。
◎ Class类:用于获取类的属性、方法等信息。
◎ Field类:表示类的成员变量,用于获取和设置类中的属性值。
◎ Method类:表示类的方法,用于获取方法的描述信息或者执行某个方法。
◎ Constructor类:表示类的构造方法。
2.3.5 反射的步骤
反射的步骤如下。
(1)获取想要操作的类的Class对象,该Class对象是反射的核心,通过它可以调用类的任意方
法。
(2)调用Class对象所对应的类中定义的方法,这是反射的使用阶段。
(3)使用反射API来获取并调用类的属性和方法等信息。
获取Class对象的3种方法如下。
(1)调用某个对象的getClass方法以获取该类对应的Class对象:

(2)调用某个类的class属性以获取该类对应的Class对象:

(3)调用Class类中的forName静态方法以获取该类对应的Class对象,这是最安全、性能也最
好的方法:

我们在获得想要操作的类的Class对象后,可以通过Class类中的方法获取并查看该类中的方法
和属性,具体代码如下:

创建对象的两种方式
创建对象的两种方式如下。
◎ 使用Class对象的newInstance方法创建该Class对象对应类的实例,这种方法要求该Class对象
对应的类有默认的空构造器。
◎ 先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance方法创建
Class对象对应类的实例,通过这种方法可以选定构造方法创建实例。创建对象的具体代码如下

Method的invoke方法
Method提供了关于类或接口上某个方法及如何访问该方法的信息,那么在运行的代码中如何
动态调用该方法呢?答案就通过调用Method的invoke方法。我们通过invoke方法可以实现动态调
用,比如可以动态传入参数及将方法参数化。具体过程为:获取对象的Method,并调用Method的
invoke方法,如下所述。
( 1 ) 获 取 Method 对 象 : 通 过 调 用 Class 对 象 的 getMethod(String name, Class<?>...
parameterTypes)返回一个Method对象,它描述了此Class对象所表示的类或接口指定的公共成员方法。name参数是String类型,用于指定所需方法的名称。parameterTypes参数是按声明顺序标识该方法的形参类型的Class对象的一个数组,如果parameterTypes为null,则按空数组处理。
(2)调用invoke方法:指通过调用Method对象的invoke方法来动态执行函数。invoke方法的具
体使用代码如下:

的getMethod("setName",String.class) 获取一个method 对象;接着使用Class 对象获取指定的
Constructor对象并调用Constructor对象的newInstance方法创建Class对象对应类的实例;最后通过调
用method.invoke方法实现动态调用,这样就通过反射动态生成类的对象并调用其方法。
2.4 注解
2.4.1 注解的概念
注解(Annotation)是Java提供的设置程序中元素的关联信息和元数据(MetaData)的方法,
它是一个接口,程序可以通过反射获取指定程序中元素的注解对象,然后通过该注解对象获取注解中的元数据信息。
2.4.2 标准元注解:@Target、@Retention、@Documented、@Inherited
元注解(Meta-Annotation)负责注解其他注解。在Java中定义了 4个标准的元注解类型
@Target、@Retention、@Documented、@Inherited,用于定义不同类型的注解。
(1)@Target:@Target说明了注解所修饰的对象范围。注解可被用于packages、types(类、
接口、枚举、注解类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变
量(循环变量、catch参数等)。在注解类型的声明中使用了target,可更加明确其修饰的目标,
target的具体取值类型如表2-1所示。

Java集合与异常详解
2万+

被折叠的 条评论
为什么被折叠?



