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
所示。
