17-Hutool工具(上)
参考资料: https://www.hutool.cn/
参考资料-api文档: https://apidoc.gitee.com/loolly/hutool/overview-summary.html
本文主要用作汇总和自查用。
依赖:
1 |
|
支持泛型的克隆接口和克隆类
痛点
我们知道,JDK中的Cloneable接口只是一个空接口,并没有定义成员,它存在的意义仅仅是指明一个类的实例化对象支持位复制(就是对象克隆),如果不实现这个类,调用对象的clone()方法就会抛出CloneNotSupportedException异常。而且,因为clone()方法在Object对象中,返回值也是Object对象,因此克隆后我们需要自己强转下类型。
泛型克隆接口
因此,cn.hutool.core.clone.Cloneable接口应运而生。此接口定义了一个返回泛型的成员方法,这样,实现此接口后会提示必须实现一个public的clone方法,调用父类clone方法即可:
1 |
|
泛型克隆类
但是实现此接口依旧有不方便之处,就是必须自己实现一个public类型的clone()方法,还要调用父类(Object)的clone方法并处理异常。于是cn.hutool.clone.CloneSupport类产生,这个类帮我们实现了上面的clone方法,因此只要继承此类,不用写任何代码即可使用clone()方法:
1 |
|
当然,使用CloneSupport的前提是你没有继承任何的类,谁让Java不支持多重继承呢(你依旧可以让父类继承这个类,如果可以的话)。如果没办法继承类,那实现*cn.hutool.clone.Cloneable**也是不错的主意,因此**hutool*提供了这两种方式,任选其一,在便捷和灵活上都提供了支持。
深克隆
我们知道实现Cloneable接口后克隆的对象是浅克隆,要想实现深克隆,请使用:
1 |
|
前提是对象必须实现Serializable接口。
ObjectUtil同样提供一些静态方法:**clone(obj)**、**cloneIfPossible(obj)**用于简化克隆调用,详细的说明请查看核心类的相关文档。
类型转换工具类-Convert
痛点
在Java开发中我们要面对各种各样的类型转换问题,尤其是从命令行获取的用户参数、从HttpRequest获取的Parameter等等,这些参数类型多种多样,我们怎么去转换他们呢?常用的办法是先整成String,然后调用XXX.parseXXX方法,还要承受转换失败的风险,不得不加一层try catch,这个小小的过程混迹在业务代码中会显得非常难看和臃肿。
Convert类
Convert类可以说是一个工具方法类,里面封装了针对Java常见类型的转换,用于简化类型转换。Convert类中大部分方法为toXXX,参数为Object,可以实现将任意可能的类型转换为指定类型。同时支持第二个参数defaultValue用于在转换失败时返回一个默认值。
Java常见类型转换
- 转换为字符串:
1 |
|
- 转换为指定类型数组:
1 |
|
- 转换为日期对象:
1 |
|
转换为集合
1
2
3
4Object[] a = {"a", "你", "好", "", 1};
List<?> list = Convert.convert(List.class, a);
//从4.1.11开始可以这么用
List<?> list = Convert.toList(a);
其它类型转换
- 标准类型
通过Convert.convert(Class<T>, Object)
方法可以将任意类型转换为指定类型,Hutool中预定义了许多类型转换,例如转换为URI、URL、Calendar等等,这些类型的转换都依托于ConverterRegistry
类。通过这个类和Converter
接口,我们可以自定义一些类型转换。详细的使用请参阅“自定义类型转换”一节。
- 泛型类型
通过convert(TypeReference<T> reference, Object value)
方法,自行new一个TypeReference
对象可以对嵌套泛型进行类型转换。例如,我们想转换一个对象为List<String>
类型,此时传入的标准Class就无法满足要求,此时我们可以这样:
1 |
|
通过TypeReference实例化后制定泛型类型,即可转换对象为我们想要的目标类型。
半角和全角转换
在很多文本的统一化中这两个方法非常有用,主要对标点符号的全角半角转换。
半角转全角:
1 |
|
全角转半角:
1 |
|
16进制(Hex)
在很多加密解密,以及中文字符串传输(比如表单提交)的时候,会用到16进制转换,就是Hex转换,为此Hutool中专门封装了HexUtil工具类,考虑到16进制转换也是转换的一部分,因此将其方法也放在Convert类中,便于理解和查找,使用同样非常简单:
转为16进制(Hex)字符串
1 |
|
将16进制(Hex)字符串转为普通字符串:
1 |
|
因为字符串牵涉到编码问题,因此必须传入编码对象,此处使用UTF-8编码。 toHex方法同样支持传入byte[],同样也可以使用hexToBytes方法将16进制转为byte[]
Unicode和字符串转换
与16进制类似,Convert类同样可以在字符串和Unicode之间轻松转换:
1 |
|
很熟悉吧?如果你在properties文件中写过中文,你会明白这个方法的重要性。
编码转换
在接收表单的时候,我们常常被中文乱码所困扰,其实大多数原因是使用了不正确的编码方式解码了数据。于是Convert.convertCharset
方法便派上用场了,它可以把乱码转为正确的编码方式:
1 |
|
注意 经过测试,UTF-8编码后用GBK解码再用GBK编码后用UTF-8解码会存在某些中文转换失败的问题。
时间单位转换
Convert.convertTime
方法主要用于转换时长单位,比如一个很大的毫秒,我想获得这个毫秒数对应多少分:
1 |
|
金额大小写转换
面对财务类需求,Convert.digitToChinese
将金钱数转换为大写形式:
1 |
|
注意 转换为大写只能精确到分(小数点儿后两位),之后的数字会被忽略。
数字转换
- 数字转为英文表达
1 |
|
- 数字简化
1 |
|
- 数字转中文
数字转中文方法中,只保留两位小数
1 |
|
- 数字中文表示转换为数字
1 |
|
原始类和包装类转换
有的时候,我们需要将包装类和原始类相互转换(比如Integer.class 和 int.class),这时候我们可以:
1 |
|
自定义类型转换-ConverterRegistry
由来
Hutool中类型转换最早只是一个工具类,叫做“Convert”,对于每一种类型转换都是用一个静态方法表示,但是这种方式有一个潜在问题,那就是扩展性不足,这导致Hutool只能满足部分类型转换的需求。
解决
为了解决这些问题,我对Hutool中这个类做了扩展。思想如下:
Converter
类型转换接口,通过实现这个接口,重写convert方法,以实现不同类型的对象转换ConverterRegistry
类型转换登记中心。将各种类型Convert对象放入登记中心,通过convert
方法查找目标类型对应的转换器,将被转换对象转换之。在此类中,存放着默认转换器和自定义转换器,默认转换器是Hutool中预定义的一些转换器,自定义转换器存放用户自定的转换器。
通过这种方式,实现类灵活的类型转换。使用方式如下:
1 |
|
自定义转换
Hutool的默认转换有时候并不能满足我们自定义对象的一些需求,这时我们可以使用ConverterRegistry.getInstance().putCustom()
方法自定义类型转换。
- 自定义转换器
1 |
|
- 注册转换器
1 |
|
- 执行转换
1 |
|
注意: convert(Class type, Object value, T defaultValue, boolean isCustomFirst)方法的最后一个参数可以选择转换时优先使用自定义转换器还是默认转换器。convert(Class type, Object value, T defaultValue)和convert(Class type, Object value)两个重载方法都是使用自定义转换器优先的模式。
ConverterRegistry
单例和对象模式
ConverterRegistry提供一个静态方法getInstance()返回全局单例对象,这也是推荐的使用方式,当然如果想在某个限定范围内自定义转换,可以实例化ConverterRegistry对象。
日期时间工具-DateUtil
由来
考虑到Java本身对日期时间的支持有限,并且Date和Calendar对象的并存导致各种方法使用混乱和复杂,故使用此工具类做了封装。这其中的封装主要是日期和字符串之间的转换,以及提供对日期的定位(一个月前等等)。
对于Date对象,为了便捷,使用了一个DateTime类来代替之,继承自Date对象,主要的便利在于,覆盖了toString()方法,返回yyyy-MM-dd HH:mm:ss形式的字符串,方便在输出时的调用(例如日志记录等),提供了众多便捷的方法对日期对象操作,关于DateTime会在相关章节介绍。
方法
转换
Date、long、Calendar之间的相互转换
1 |
|
字符串转日期
DateUtil.parse
方法会自动识别一些常用格式,包括:
yyyy-MM-dd HH:mm:ss
- yyyy/MM/dd HH:mm:ss
- yyyy.MM.dd HH:mm:ss
- yyyy年MM月dd日 HH时mm分ss秒
- yyyy-MM-dd
- yyyy/MM/dd
- yyyy.MM.dd
- HH:mm:ss
- HH时mm分ss秒
- yyyy-MM-dd HH:mm
- yyyy-MM-dd HH:mm:ss.SSS
- yyyyMMddHHmmss
- yyyyMMddHHmmssSSS
- yyyyMMdd
- EEE, dd MMM yyyy HH:mm:ss z
- EEE MMM dd HH:mm:ss zzz yyyy
- yyyy-MM-dd’T’HH:mm:ss’Z’
- yyyy-MM-dd’T’HH:mm:ss.SSS’Z’
- yyyy-MM-dd’T’HH:mm:ssZ
- yyyy-MM-dd’T’HH:mm:ss.SSSZ
1 |
|
我们也可以使用自定义日期格式转化:
1 |
|
格式化日期输出
1 |
|
获取Date对象的某个部分
1 |
|
开始和结束时间
有的时候我们需要获得每天的开始时间、结束时间,每月的开始和结束时间等等,DateUtil也提供了相关方法:
1 |
|
日期时间偏移
日期或时间的偏移指针对某个日期增加或减少分、小时、天等等,达到日期变更的目的。Hutool也针对其做了大量封装
1 |
|
针对当前时间,提供了简化的偏移方法(例如昨天、上周、上个月等):
1 |
|
日期时间差
有时候我们需要计算两个日期之间的时间差(相差天数、相差小时数等等),Hutool将此类方法封装为between方法:
1 |
|
格式化时间差
有时候我们希望看到易读的时间差,比如XX天XX小时XX分XX秒,此时使用DateUtil.formatBetween
方法:
1 |
|
星座和属相
1 |
|
其它
1 |
|
日期时间对象-DateTime
由来
考虑工具类的局限性,在某些情况下使用并不简便,于是DateTime
类诞生。DateTime
对象充分吸取Joda-Time库的优点,并提供更多的便捷方法,这样我们在开发时不必再单独导入Joda-Time库便可以享受简单快速的日期时间处理过程。
说明
DateTime类继承于java.util.Date类,为Date类扩展了众多简便方法,这些方法多是DateUtil
静态方法的对象表现形式,使用DateTime对象可以完全替代开发中Date对象的使用。
使用
新建对象
DateTime
对象包含众多的构造方法,构造方法支持的参数有:
- Date
- Calendar
- String(日期字符串,第二个参数是日期格式)
- long 毫秒数
构建对象有两种方式:DateTime.of()
和new DateTime()
:
1 |
|
使用对象
DateTime
的成员方法与DateUtil
中的静态方法所对应,因为是成员方法,因此可以使用更少的参数操作日期时间。
示例:获取日期成员(年、月、日等)
1 |
|
更多成员方法请参阅API文档。
对象的可变性
DateTime对象默认是可变对象(调用offset、setField、setTime方法默认变更自身),但是这种可变性有时候会引起很多问题(例如多个地方共用DateTime对象)。我们可以调用setMutable(false)
方法使其变为不可变对象。在不可变模式下,offset
、setField
方法返回一个新对象,setTime
方法抛出异常。
1 |
|
格式化为字符串
调用toString()
方法即可返回格式为yyyy-MM-dd HH:mm:ss
的字符串,调用toString(String format)
可以返回指定格式的字符串。
1 |
|
农历日期-ChineseDate
介绍
农历日期,提供了生肖、天干地支、传统节日等方法。
使用
- 构建
ChineseDate
对象
ChineseDate
表示了农历的对象,构建此对象既可以使用公历的日期,也可以使用农历的日期。
1 |
|
- 基本使用
1 |
|
- 获取天干地支
从5.4.1
开始,Hutool支持天干地支的获取:
1 |
|
LocalDateTime工具-LocalDateTimeUtil
介绍
从Hutool的5.4.x开始,Hutool加入了针对JDK8+日期API的封装,此工具类的功能包括LocalDateTime
和LocalDate
的解析、格式化、转换等操作。
使用
- 日期转换
1 |
|
- 日期字符串解析
1 |
|
解析同样支持LocalDate
:
1 |
|
- 日期格式化
1 |
|
- 日期偏移
1 |
|
如果是减少时间,offset第二个参数传负数即可:
1 |
|
- 计算时间间隔
1 |
|
- 一天的开始和结束
1 |
|
计时器工具-TimeInterval
介绍
Hutool通过封装TimeInterval
实现计时器功能,即可以计算方法或过程执行的时间。
TimeInterval
支持分组计时,方便对比时间。
使用
1 |
|
也可以实现分组计时:
1 |
|
IO工具类-IoUtil
由来
IO工具类的存在主要针对InputStream、OutputStream、Reader、Writer封装简化,并对NIO相关操作做封装简化。总体来说,Hutool对IO的封装,主要是工具层面,我们努力做到在便捷、性能和灵活之间找到最好的平衡点。
方法
拷贝
流的读写可以总结为从输入流读取,从输出流写出,这个过程我们定义为拷贝。这个是一个基本过程,也是文件、流操作的基础。
以文件流拷贝为例:
1 |
|
copy方法同样针对Reader、Writer、Channel等对象有一些重载方法,并提供可选的缓存大小。默认的,缓存大小为1024
个字节,如果拷贝大文件或流数据较大,可以适当调整这个参数。
针对NIO,提供了copyByNIO
方法,以便和BIO有所区别。我查阅过一些资料,使用NIO对文件流的操作有一定的提升,我并没有做具体实验。相关测试请参阅博客:http://www.cnblogs.com/gaopeng527/p/4896783.html
Stream转Reader、Writer
IoUtil.getReader
:将InputStream
转为BufferedReader
用于读取字符流,它是部分readXXX方法的基础。IoUtil.getWriter
:将OutputStream
转为OutputStreamWriter
用于写入字符流,它是部分writeXXX的基础。
本质上这两个方法只是简单new一个新的Reader或者Writer对象,但是封装为工具方法配合IDE的自动提示可以大大减少查阅次数(例如你对BufferedReader、OutputStreamWriter不熟悉,是不需要搜索一下相关类?)
读取流中的内容
读取流中的内容总结下来,可以分为read方法和readXXX方法。
read
方法有诸多的重载方法,根据参数不同,可以读取不同对象中的内容,这包括:
InputStream
Reader
FileChannel
这三个重载大部分返回String字符串,为字符流读取提供极大便利。
readXXX
方法主要针对返回值做一些处理,例如:
readBytes
返回byte数组(读取图片等)readHex
读取16进制字符串readObj
读取序列化对象(反序列化)readLines
按行读取
toStream
方法则是将某些对象转换为流对象,便于在某些情况下操作:
String
转换为ByteArrayInputStream
File
转换为FileInputStream
写入到流
IoUtil.write
方法有两个重载方法,一个直接调用OutputStream.write
方法,另一个用于将对象转换为字符串(调用toString方法),然后写入到流中。IoUtil.writeObjects
用于将可序列化对象序列化后写入到流中。
write
方法并没有提供writeXXX,需要自己转换为String或byte[]。
关闭
对于IO操作来说,使用频率最高(也是最容易被遗忘)的就是close
操作,好在Java规范使用了优雅的Closeable
接口,这样我们只需简单封装调用此接口的方法即可。
关闭操作会面临两个问题:
- 被关闭对象为空
- 对象关闭失败(或对象已关闭)
IoUtil.close
方法很好的解决了这两个问题。
在JDK1.7中,提供了AutoCloseable
接口,在IoUtil
中同样提供相应的重载方法,在使用中并不能感觉到有哪些不同。
文件工具类-FileUtil
简介
在IO操作中,文件的操作相对来说是比较复杂的,但也是使用频率最高的部分,我们几乎所有的项目中几乎都躺着一个叫做FileUtil或者FileUtils的工具类,我想Hutool应该将这个工具类纳入其中,解决用来解决大部分的文件操作问题。
总体来说,FileUtil类包含以下几类操作工具:
- 文件操作:包括文件目录的新建、删除、复制、移动、改名等
- 文件判断:判断文件或目录是否非空,是否为目录,是否为文件等等。
- 绝对路径:针对ClassPath中的文件转换为绝对路径文件。
- 文件名:主文件名,扩展名的获取
- 读操作:包括类似IoUtil中的getReader、readXXX操作
- 写操作:包括getWriter和writeXXX操作
在FileUtil中,我努力将方法名与Linux相一致,例如创建文件的方法并不是createFile,而是touch
,这种统一对于熟悉Linux的人来说,大大提高了上手速度。当然,如果你不熟悉Linux,那FileUtil工具类的使用则是在帮助你学习Linux命令。这些类Linux命令的方法包括:
ls
列出目录和文件touch
创建文件,如果父目录不存在也自动创建mkdir
创建目录,会递归创建每层目录del
删除文件或目录(递归删除,不判断是否为空),这个方法相当于Linux的delete命令copy
拷贝文件或目录
这些方法提供了人性化的操作,例如touch
方法,在创建文件的情况下会自动创建上层目录(我想对于使用者来说这也是大部分情况下的需求),同样mkdir
也会创建父目录。
需要注意的是,
del
方法会删除目录而不判断其是否为空,这一方面方便了使用,另一方面也可能造成一些预想不到的后果(比如拼写错路径而删除不应该删除的目录),所以请谨慎使用此方法。
关于FileUtil中更多工具方法,请参阅API文档。
文件类型判断-FileTypeUtil
由来
在文件上传时,有时候我们需要判断文件类型。但是又不能简单的通过扩展名来判断(防止恶意脚本等通过上传到服务器上),于是我们需要在服务端通过读取文件的首部几个二进制位来判断常用的文件类型。
使用
这个工具类使用非常简单,通过调用FileTypeUtil.getType
即可判断,这个方法同时提供众多的重载方法,用于读取不同的文件和流。
1 |
|
原理和局限性
这个类是通过读取文件流中前N个byte值来判断文件类型,在类中我们通过Map形式将常用的文件类型做了映射,这些映射都是网络上搜集而来。也就是说,我们只能识别有限的几种文件类型。但是这些类型已经涵盖了常用的图片、音频、视频、Office文档类型,可以应对大部分的使用场景。
对于某些文本格式的文件我们并不能通过首部byte判断其类型,比如
JSON
,这类文件本质上是文本文件,我们应该读取其文本内容,通过其语法判断类型。
自定义类型
为了提高FileTypeUtil
的扩展性,我们通过putFileType
方法可以自定义文件类型。
1 |
|
第一个参数是文件流的前N个byte的16进制表示,我们可以读取自定义文件查看,选取一定长度即可(长度越长越精确),第二个参数就是文件类型,然后使用FileTypeUtil.getType
即可。
注意 xlsx、docx本质上是各种XML打包为zip的结果,因此会被识别为zip格式。
文件监听-WatchMonitor
由来
很多时候我们需要监听一个文件的变化或者目录的变动,包括文件的创建、修改、删除,以及目录下文件的创建、修改和删除,在JDK7前我们只能靠轮询方式遍历目录或者定时检查文件的修改事件,这样效率非常低,性能也很差。因此在JDK7中引入了WatchService
。不过考虑到其API并不友好,于是Hutool便针对其做了简化封装,使监听更简单,也提供了更好的功能,这包括:
- 支持多级目录的监听(WatchService只支持一级目录),可自定义监听目录深度
- 延迟合并触发支持(文件变动时可能触发多次modify,支持在某个时间范围内的多次修改事件合并为一个修改事件)
- 简洁易懂的API方法,一个方法即可搞定监听,无需理解复杂的监听注册机制。
- 多观察者实现,可以根据业务实现多个
Watcher
来响应同一个事件(通过WatcherChain)
WatchMonitor
在Hutool中,WatchMonitor
主要针对JDK7中WatchService
做了封装,针对文件和目录的变动(创建、更新、删除)做一个钩子,在Watcher
中定义相应的逻辑来应对这些文件的变化。
内部应用
在hutool-setting模块,使用WatchMonitor监测配置文件变化,然后自动load到内存中。WatchMonitor的使用可以避免轮询,以事件响应的方式应对文件变化。
使用
WatchMonitor
提供的事件有:
ENTRY_MODIFY
文件修改的事件ENTRY_CREATE
文件或目录创建的事件ENTRY_DELETE
文件或目录删除的事件OVERFLOW
丢失的事件
这些事件对应StandardWatchEventKinds
中的事件。
下面我们介绍WatchMonitor的使用:
监听指定事件
1 |
|
监听全部事件
其实我们不必实现Watcher
的所有接口方法,Hutool同时提供了SimpleWatcher
类,只需重写对应方法即可。
同样,如果我们想监听所有事件,可以:
1 |
|
createAll
方法会创建一个监听所有事件的WatchMonitor,同时在第二个参数中定义Watcher来负责处理这些变动。
延迟处理监听事件
在监听目录或文件时,如果这个文件有修改操作,JDK会多次触发modify方法,为了解决这个问题,我们定义了DelayWatcher
,此类通过维护一个Set将短时间内相同文件多次modify的事件合并处理触发,从而避免以上问题。
1 |
|
文件读取-FileReader
由来
在FileUtil
中本来已经针对文件的读操作做了大量的静态封装,但是根据职责分离原则,我觉得有必要针对文件读取单独封装一个类,这样项目更加清晰。当然,使用FileUtil操作文件是最方便的。
使用
在JDK中,同样有一个FileReader类,但是并不如想象中的那样好用,于是Hutool便提供了更加便捷FileReader类。
1 |
|
FileReader提供了以下方法来快速读取文件内容:
readBytes
readString
readLines
同时,此类还提供了以下方法用于转换为流或者BufferedReader:
getReader
getInputStream
文件写入-FileWriter
相应的,文件读取有了,自然有文件写入类,使用方式与FileReader
也类似:
1 |
|
写入文件分为追加模式和覆盖模式两类,追加模式可以用append
方法,覆盖模式可以用write
方法,同时也提供了一个write方法,第二个参数是可选覆盖模式。
同样,此类提供了:
getOutputStream
getWriter
getPrintWriter
这些方法用于转换为相应的类提供更加灵活的写入操作。
文件追加-FileAppender
由来
顾名思义,FileAppender
类表示文件追加器。此对象持有一个一个文件,在内存中积累一定量的数据后统一追加到文件,此类只有在写入文件时打开文件,并在写入结束后关闭之。因此此类不需要关闭。
在调用append方法后会缓存于内存,只有超过容量后才会一次性写入文件,因此内存中随时有剩余未写入文件的内容,在最后必须调用flush方法将剩余内容刷入文件。
也就是说,这是一个支持缓存的文件内容追加器。此类主要用于类似于日志写出这类需求所用。
使用
1 |
|
文件跟随-Tailer
由来
有时候我们要启动一个线程实时“监控”文件的变化,比如有新内容写出到文件时,我们可以及时打印出来,这个功能非常类似于Linux下的tail -f
命令。
使用
1 |
|
其中Tailer.CONSOLE_HANDLER
表示文件新增内容默认输出到控制台。
1 |
|
我们也可以实现自己的LineHandler来处理每一行数据。
注意 此方法会阻塞当前线程。
文件名工具-FileNameUtil
由来
文件名操作工具类,主要针对文件名获取主文件名、扩展名等操作,同时针对Windows平台,清理无效字符。
此工具类在5.4.1
之前是FileUtil
的一部分,后单独剥离为FileNameUtil
工具。
使用
- 获取文件名
1 |
|
- 获取主文件名和扩展名
1 |
|
注意,此处获取的扩展名不带
.
。FileNameUtil.mainName
和FileNameUtil.getPrefix
等价,同理FileNameUtil.extName
和FileNameUtil.getSuffix
等价,保留两个方法用于适应不同用户的习惯。
资源工具-ResourceUtil
介绍
ResourceUtil
提供了资源快捷读取封装。
使用
ResourceUtil
中最核心的方法是getResourceObj
,此方法可以根据传入路径是否为绝对路径而返回不同的实现。比如路径是:file:/opt/test
,或者/opt/test
都会被当作绝对路径,此时调用FileResource
来读取数据。如果不满足以上条件,默认调用ClassPathResource
读取classpath中的资源或者文件。
同样,此工具类还封装了readBytes
和readStr
用于快捷读取bytes和字符串。
举个例子,假设我们在classpath下放了一个test.xml
,读取就变得非常简单:
1 |
|
假设我们的文件存放在src/resources/config
目录下,则读取改为:
1 |
|
注意 在IDEA中,新加入文件到
src/resources
目录下,需要重新import项目,以便在编译时顺利把资源文件拷贝到target目录下。如果提示找不到文件,请去target目录下确认文件是否存在。
ClassPath资源访问-ClassPathResource
什么是ClassPath
简单说来ClassPath就是查找class文件的路径,在Tomcat等容器下,ClassPath一般是WEB-INF/classes
,在普通java程序中,我们可以通过定义-cp
或者-classpath
参数来定义查找class文件的路径,这些路径就是ClassPath。
为了项目方便,我们定义的配置文件肯定不能使用绝对路径,所以需要使用相对路径,这时候最好的办法就是把配置文件和class文件放在一起,便于查找。
由来
在Java编码过程中,我们常常希望读取项目内的配置文件,按照Maven的习惯,这些文件一般放在项目的src/main/resources
下,读取的时候使用:
1 |
|
使用当前类来获得资源其实就是使用当前类的类加载器获取资源,最后openStream()方法获取输入流来读取文件流。
封装
面对这种复杂的读取操作,我们封装了ClassPathResource
类来简化这种资源的读取:
1 |
|
这样就大大简化了ClassPath中资源的读取。
Hutool提供针对properties的封装类
Props
,同时提供更加强大的配置文件Setting类,这两个类已经针对ClassPath做过相应封装,可以以更加便捷的方式读取配置文件。相关文档请参阅Hutool-setting章节
字符串工具-StrUtil
由来
这个工具的用处类似于Apache Commons Lang中的StringUtil
,之所以使用StrUtil
而不是使用StringUtil
是因为前者更短,而且Str
这个简写我想已经深入人心了,大家都知道是字符串的意思。常用的方法例如isBlank
、isNotBlank
、isEmpty
、isNotEmpty
这些我就不做介绍了,判断字符串是否为空,下面我说几个比较好用的功能。
方法
1. hasBlank
、hasEmpty
方法
就是给定一些字符串,如果一旦有空的就返回true,常用于判断好多字段是否有空的(例如web表单数据)。
这两个方法的区别是hasEmpty
只判断是否为null或者空字符串(””),hasBlank
则会把不可见字符也算做空,isEmpty
和isBlank
同理。
2. removePrefix
、removeSuffix
方法
这两个是去掉字符串的前缀后缀的,例如去个文件名的扩展名啥。
1 |
|
还有忽略大小写的removePrefixIgnoreCase
和removeSuffixIgnoreCase
都比较实用。
3. sub
方法
不得不提一下这个方法,有人说String有了subString你还写它干啥,我想说subString方法越界啥的都会报异常,你还得自己判断,难受死了,我把各种情况判断都加进来了,而且index的位置还支持负数哦,-1表示最后一个字符(这个思想来自于Python,如果学过Python的应该会很喜欢的),还有就是如果不小心把第一个位置和第二个位置搞反了,也会自动修正(例如想截取第4个和第2个字符之间的部分也是可以的哦~) 举个栗子
1 |
|
4. str
、bytes
方法
好吧,我承认把String.getByte(String charsetName)
方法封装在这里了,原生的String.getByte()
这个方法太坑了,使用系统编码,经常会有人跳进来导致乱码问题,所以我就加了这两个方法强制指定字符集了,包了个try抛出一个运行时异常,省的我得在我业务代码里处理那个恶心的UnsupportedEncodingException
。
5. format方法
我会告诉你这是我最引以为豪的方法吗?灵感来自slf4j,可以使用字符串模板代替字符串拼接,我也自己实现了一个,而且变量的标识符都一样,神马叫无缝兼容~~来,上栗子(吃多了上火吧……)
1 |
|
参数我定义成了Object类型,如果传别的类型的也可以,会自动调用toString()方法的。
6. 定义的一些常量
为了方便,我定义了一些比较常见的字符串常量在里面,像点、空串、换行符等等,还有HTML中的一些转义字符。
更多方法请参阅API文档。
16进制工具-HexUtil
介绍
十六进制(简写为hex或下标16)在数学中是一种逢16进1的进位制,一般用数字0到9和字母A到F表示(其中:AF即1015)。例如十进制数57,在二进制写作111001,在16进制写作39。
像java,c这样的语言为了区分十六进制和十进制数值,会在十六进制数的前面加上 0x,比如0x20是十进制的32,而不是十进制的20。HexUtil
就是将字符串或byte数组与16进制表示转换的工具类。
用于
16进制一般针对无法显示的一些二进制进行显示,常用于: 1、图片的字符串表现形式 2、加密解密 3、编码转换
使用
HexUtil
主要以encodeHex
和decodeHex
两个方法为核心,提供一些针对字符串的重载方法。
1 |
|
Escape工具-EscapeUtil
介绍
转义和反转义工具类Escape / Unescape。escape采用ISO Latin字符集对指定的字符串进行编码。所有的空格符、标点符号、特殊字符以及其他非ASCII字符都将被转化成%xx格式的字符编码(xx等于该字符在字符集表里面的编码的16进制数字)。
此类中的方法对应Javascript中的escape()
函数和unescape()
函数。
方法
EscapeUtil.escape
Escape编码(Unicode),该方法不会对 ASCII 字母和数字进行编码,也不会对下面这些 ASCII 标点符号进行编码: * @ - _ + . / 。其他所有的字符都会被转义序列替换。EscapeUtil.unescape
Escape解码。EscapeUtil.safeUnescape
安全的unescape文本,当文本不是被escape的时候,返回原文。
Hash算法-HashUtil
介绍
HashUtil
其实是一个hash算法的集合,此工具类中融合了各种hash算法。
方法
这些算法包括:
additiveHash
加法hashrotatingHash
旋转hashoneByOneHash
一次一个hashbernstein
Bernstein’s hashuniversal
Universal Hashingzobrist
Zobrist HashingfnvHash
改进的32位FNV算法1intHash
Thomas Wang的算法,整数hashrsHash
RS算法hashjsHash
JS算法pjwHash
PJW算法elfHash
ELF算法bkdrHash
BKDR算法sdbmHash
SDBM算法djbHash
DJB算法dekHash
DEK算法apHash
AP算法tianlHash
TianL Hash算法javaDefaultHash
JAVA自己带的算法mixHash
混合hash算法,输出64位的值
URL工具-URLUtil
介绍
URL(Uniform Resource Locator)中文名为统一资源定位符,有时也被俗称为网页地址。表示为互联网上的资源,如网页或者FTP地址。在Java中,也可以使用URL表示Classpath中的资源(Resource)地址。
方法
获取URL对象
URLUtil.url
通过一个字符串形式的URL地址创建对象URLUtil.getURL
主要获得ClassPath下资源的URL,方便读取Classpath下的配置文件等信息。
其它
URLUtil.normalize
标准化化URL链接。对于不带http://头的地址做简单补全。
1 |
|
URLUtil.encode
封装URLEncoder.encode
,将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。
1 |
|
URLUtil.decode
封装URLDecoder.decode
,将%开头的16进制表示的内容解码。URLUtil.getPath
获得path部分 URI -> http://www.aaa.bbb/search?scope=ccc&q=ddd PATH -> /searchURLUtil.toURI
转URL或URL字符串为URI。
XML工具-XmlUtil
由来
在日常编码中,我们接触最多的除了JSON外,就是XML格式了,一般而言,我们首先想到的是引入Dom4j包,却不知JDK已经封装有XML解析和构建工具:w3c dom。但是由于这个API操作比较繁琐,因此Hutool中提供了XmlUtil简化XML的创建、读和写的过程。
使用
读取XML
读取XML分为两个方法:
XmlUtil.readXML
读取XML文件XmlUtil.parseXml
解析XML字符串为Document对象
写XML
XmlUtil.toStr
将XML文档转换为StringXmlUtil.toFile
将XML文档写入到文件
创建XML
XmlUtil.createXml
创建XML文档, 创建的XML默认是utf8编码,修改编码的过程是在toStr和toFile方法里,既XML在转为文本的时候才定义编码。
XML操作
通过以下工具方法,可以完成基本的节点读取操作。
XmlUtil.cleanInvalid
除XML文本中的无效字符XmlUtil.getElements
根据节点名获得子节点列表XmlUtil.getElement
根据节点名获得第一个子节点XmlUtil.elementText
根据节点名获得第一个子节点XmlUtil.transElements
将NodeList转换为Element列表
XML与对象转换
writeObjectAsXml
将可序列化的对象转换为XML写入文件,已经存在的文件将被覆盖。readObjectFromXml
从XML中读取对象。
注意 这两个方法严重依赖JDK的
XMLEncoder
和XMLDecoder
,生成和解析必须成对存在(遵循固定格式),普通的XML转Bean会报错。
Xpath操作
Xpath的更多介绍请看文章:https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
createXPath
创建XPathgetByXPath
通过XPath方式读取XML节点等信息
栗子:
1 |
|
总结
XmlUtil只是w3c dom的简单工具化封装,减少操作dom的难度,如果项目对XML依赖较大,依旧推荐Dom4j框架。
对象工具-ObjectUtil
由来
在我们的日常使用中,有些方法是针对Object通用的,这些方法不区分何种对象,针对这些方法,Hutool封装为ObjectUtil
。
方法
默认值
借助于lambada表达式,ObjectUtil可以完成判断给定的值是否为null,不为null执行特定逻辑的功能。
1 |
|
ObjectUtil.equal
比较两个对象是否相等,相等需满足以下条件之一:
- obj1 == null && obj2 == null
- obj1.equals(obj2)
1 |
|
ObjectUtil.length
计算对象长度,如果是字符串调用其length方法,集合类调用其size方法,数组调用其length属性,其他可遍历对象遍历计算长度。
支持的类型包括:
- CharSequence
- Collection
- Map
- Iterator
- Enumeration
- Array
1 |
|
ObjectUtil.contains
对象中是否包含元素。
支持的对象类型包括:
- String
- Collection
- Map
- Iterator
- Enumeration
- Array
1 |
|
判断是否为null
ObjectUtil.isNull
ObjectUtil.isNotNull
注意:此方法不能判断对象中字段为空的情况,如果需要检查Bean对象中字段是否全空,请使用
BeanUtil.isEmpty
。
克隆
ObjectUtil.clone
克隆对象,如果对象实现Cloneable接口,调用其clone方法,如果实现Serializable接口,执行深度克隆,否则返回null
。
1 |
|
ObjectUtil.cloneIfPossible
返回克隆后的对象,如果克隆失败,返回原对象ObjectUtil.cloneByStream
序列化后拷贝流的方式克隆,对象必须实现Serializable接口
序列化和反序列化
serialize
序列化,调用JDK序列化deserialize
反序列化,调用JDK
判断基本类型
ObjectUtil.isBasicType
判断是否为基本类型,包括包装类型和原始类型。
包装类型:
- Boolean
- Byte
- Character
- Double
- Float
- Integer
- Long
- Short
原始类型:
- boolean
- byte
- char
- double
- float
- int
- long
- short
1 |
|
反射工具-ReflectUtil
介绍
Java的反射机制,可以让语言变得更加灵活,对对象的操作也更加“动态”,因此在某些情况下,反射可以做到事半功倍的效果。Hutool针对Java的反射机制做了工具化封装,封装包括:
- 获取构造方法
- 获取字段
- 获取字段值
- 获取方法
- 执行方法(对象方法和静态方法)
使用
获取某个类的所有方法
1 |
|
获取某个类的指定方法
1 |
|
构造对象
1 |
|
执行方法
1 |
|
泛型类型工具-TypeUtil
介绍
针对 java.lang.reflect.Type
的工具类封装,最主要功能包括:
- 获取方法的参数和返回值类型(包括Type和Class)
- 获取泛型参数类型(包括对象的泛型参数或集合元素的泛型类型)
方法
首先我们定义一个类:
1 |
|
getClass
获得Type对应的原始类
getParamType
1 |
|
获取方法参数的泛型类型
getReturnType
获取方法的返回值类型
1 |
|
getTypeArgument
获取泛型类子类中泛型的填充类型。
1 |
|
分页工具-PageUtil
由来
分页工具类并不是数据库分页的封装,而是分页方式的转换。在我们手动分页的时候,常常使用页码+每页个数的方式,但是有些数据库需要使用开始位置和结束位置来表示。很多时候这种转换容易出错(边界问题),于是封装了PageUtil工具类。
使用
transToStartEnd
将页数和每页条目数转换为开始位置和结束位置。 此方法用于不包括结束位置的分页方法。
例如:
- 页码:0,每页10 -> [0, 10]
- 页码:1,每页10 -> [10, 20]
1 |
|
方法中,页码从0开始,位置从0开始
totalPage
根据总数计算总页数
1 |
|
分页彩虹算法
此方法来自:https://github.com/iceroot/iceroot/blob/master/src/main/java/com/icexxx/util/IceUtil.java
在页面上显示下一页时,常常需要显示前N页和后N页,PageUtil.rainbow
作用于此。
例如我们当前页为第5页,共有20页,只显示6个页码,显示的分页列表应为:
1 |
|
剪贴板工具-ClipboardUtil
介绍
在Hutool群友的强烈要求下,在3.2.0+ 中新增了ClipboardUtil
这个类用于简化操作剪贴板(当然使用场景被局限)。
使用
ClipboardUtil
封装了几个常用的静态方法:
通用方法
getClipboard
获取系统剪贴板set
设置内容到剪贴板get
获取剪贴板内容
针对文本
setStr
设置文本到剪贴板getStr
从剪贴板获取文本
针对Image对象(图片)
setImage
设置图片到剪贴板getImage
从剪贴板获取图片
类工具-ClassUtil
类处理工具 ClassUtil
这个工具主要是封装了一些反射的方法,使调用更加方便。而这个类中最有用的方法是scanPackage
方法,这个方法会扫描classpath下所有类,这个在Spring中是特性之一,主要为Hulu框架中类扫描的一个基础。下面介绍下这个类中的方法。
getShortClassName
获取完整类名的短格式如:cn.hutool.core.util.StrUtil
-> c.h.c.u.StrUtil
isAllAssignableFrom
比较判断types1和types2两组类,如果types1中所有的类都与types2对应位置的类相同,或者是其父类或接口,则返回true
isPrimitiveWrapper
是否为包装类型
isBasicType
是否为基本类型(包括包装类和原始类)
getPackage
获得给定类所在包的名称,例如: cn.hutool.util.ClassUtil
-> cn.hutool.util
scanPackage
方法
此方法唯一的参数是包的名称,返回结果为此包以及子包下所有的类。方法使用很简单,但是过程复杂一些,包扫面首先会调用 getClassPaths
方法获得ClassPath,然后扫描ClassPath,如果是目录,扫描目录下的类文件,或者jar文件。如果是jar包,则直接从jar包中获取类名。这个方法的作用显而易见,就是要找出所有的类,在Spring中用于依赖注入,我在Hulu中则用于找到Action类。当然,你也可以传一个ClassFilter
对象,用于过滤不需要的类。
getClassPaths
方法
此方法是获得当前线程的ClassPath,核心是Thread.currentThread().getContextClassLoader().getResources
的调用。
getJavaClassPaths
方法
此方法用于获得java的系统变量定义的ClassPath。
getClassLoader
和getContextClassLoader
方法
后者只是获得当前线程的ClassLoader,前者在获取失败的时候获取ClassUtil
这个类的ClassLoader。
getDefaultValue
获取指定类型分的默认值,默认值规则为:
- 如果为原始类型,返回0
- 非原始类型返回null
其它
更多详细的方法描述见:
https://apidoc.gitee.com/loolly/hutool/cn/hutool/core/util/ClassUtil.html
类加载工具-ClassLoaderUtil
介绍
提供ClassLoader相关的工具类,例如类加载(Class.forName包装)等
方法
获取ClassLoader
getContextClassLoader
获取当前线程的ClassLoader,本质上调用Thread.currentThread().getContextClassLoader()
getClassLoader
按照以下顺序规则查找获取ClassLoader:
- 获取当前线程的ContextClassLoader
- 获取ClassLoaderUtil类对应的ClassLoader
- 获取系统ClassLoader(ClassLoader.getSystemClassLoader())
加载Class
loadClass
加载类,通过传入类的字符串,返回其对应的类名,使用默认ClassLoader并初始化类(调用static模块内容和可选的初始化static属性)
扩展Class.forName
方法,支持以下几类类名的加载:
- 原始类型,例如:int
- 数组类型,例如:int[]、Long[]、String[]
- 内部类,例如:java.lang.Thread.State会被转为java.lang.Thread$State加载
同时提供loadPrimitiveClass
方法用于快速加载原始类型的类。包括原始类型、原始类型数组和void
isPresent
指定类是否被提供,通过调用loadClass
方法尝试加载指定类名的类,如果加载失败返回false。
加载失败的原因可能是此类不存在或其关联引用类不存在。
枚举工具-EnumUtil
介绍
枚举(enum)算一种“语法糖”,是指一个经过排序的、被打包成一个单一实体的项列表。一个枚举的实例可以使用枚举项列表中任意单一项的值。枚举在各个语言当中都有着广泛的应用,通常用来表示诸如颜色、方式、类别、状态等等数目有限、形式离散、表达又极为明确的量。Java从JDK5开始,引入了对枚举的支持。
EnumUtil
用于对未知枚举类型进行操作。
方法
首先我们定义一个枚举对象:
1 |
|
getNames
获取枚举类中所有枚举对象的name列表。栗子:
1 |
|
getFieldValues
获得枚举类中各枚举对象下指定字段的值。栗子:
1 |
|
getEnumMap
获取枚举字符串值和枚举对象的Map对应,使用LinkedHashMap保证有序,结果中键为枚举名,值为枚举对象。栗子:
1 |
|
getNameFieldMap
获得枚举名对应指定字段值的Map,键为枚举名,值为字段值。栗子:
1 |
|
命令行工具-RuntimeUtil
介绍
在Java世界中,如果想与其它语言打交道,处理调用接口,或者JNI,就是通过本地命令方式调用了。Hutool封装了JDK的Process类,用于执行命令行命令(在Windows下是cmd,在Linux下是shell命令)。
方法
基础方法
exec
执行命令行命令,返回Process对象,Process可以读取执行命令后的返回内容的流
快捷方法
execForStr
执行系统命令,返回字符串execForLines
执行系统命令,返回行列表
使用
1 |
|
执行这个命令后,在Windows下可以获取网卡信息。
数字工具-NumberUtil
由来
数字工具针对数学运算做工具性封装
使用
加减乘除
NumberUtil.add
针对数字类型做加法NumberUtil.sub
针对数字类型做减法NumberUtil.mul
针对数字类型做乘法NumberUtil.div
针对数字类型做除法,并提供重载方法用于规定除不尽的情况下保留小数位数和舍弃方式。
以上四种运算都会将double转为BigDecimal后计算,解决float和double类型无法进行精确计算的问题。这些方法常用于商业计算。
保留小数
保留小数的方法主要有两种:
NumberUtil.round
方法主要封装BigDecimal中的方法来保留小数,返回BigDecimal,这个方法更加灵活,可以选择四舍五入或者全部舍弃等模式。
1 |
|
NumberUtil.roundStr
方法主要封装String.format
方法,舍弃方式采用四舍五入。
1 |
|
decimalFormat
针对 DecimalFormat.format
进行简单封装。按照固定格式对double或long类型的数字做格式化操作。
1 |
|
格式中主要以 # 和 0 两种占位符号来指定数字长度。0 表示如果位数不足则以 0 填充,# 表示只要有可能就把数字拉上这个位置。
- 0 -> 取一位整数
- 0.00 -> 取一位整数和两位小数
- 00.000 -> 取两位整数和三位小数
- # -> 取所有整数部分
- #.##% -> 以百分比方式计数,并取两位小数
- #.#####E0 -> 显示为科学计数法,并取五位小数
- ,### -> 每三位以逗号进行分隔,例如:299,792,458
- 光速大小为每秒,###米 -> 将格式嵌入文本
关于格式的更多说明,请参阅:Java DecimalFormat的主要功能及使用方法
是否为数字
NumberUtil.isNumber
是否为数字NumberUtil.isInteger
是否为整数NumberUtil.isDouble
是否为浮点数NumberUtil.isPrimes
是否为质数
随机数
NumberUtil.generateRandomNumber
生成不重复随机数 根据给定的最小数字和最大数字,以及随机数的个数,产生指定的不重复的数组。NumberUtil.generateBySet
生成不重复随机数 根据给定的最小数字和最大数字,以及随机数的个数,产生指定的不重复的数组。
整数列表
NumberUtil.range
方法根据范围和步进,生成一个有序整数列表。 NumberUtil.appendRange
将给定范围内的整数添加到已有集合中
其它
NumberUtil.factorial
阶乘NumberUtil.sqrt
平方根NumberUtil.divisor
最大公约数NumberUtil.multiple
最小公倍数NumberUtil.getBinaryStr
获得数字对应的二进制字符串NumberUtil.binaryToInt
二进制转intNumberUtil.binaryToLong
二进制转longNumberUtil.compare
比较两个值的大小NumberUtil.toStr
数字转字符串,自动并去除尾小数点儿后多余的0
数组工具-ArrayUtil
介绍
数组工具中的方法在2.x版本中都在CollectionUtil中存在,3.x之后版本(包括4.x版本)中拆分出来作为ArrayUtil。数组工具类主要针对原始类型数组和泛型数组相关方案进行封装。
数组工具类主要是解决对象数组(包括包装类型数组)和原始类型数组使用方法不统一的问题。
方法
判空
数组的判空类似于字符串的判空,标准是null
或者数组长度为0,ArrayUtil中封装了针对原始类型和泛型数组的判空和判非空:
判断空
1
2
3
4int[] a = {};
int[] b = null;
ArrayUtil.isEmpty(a);
ArrayUtil.isEmpty(b);判断非空
1
2int[] a = {1,2};
ArrayUtil.isNotEmpty(a);
新建泛型数组
Array.newInstance
并不支持泛型返回值,在此封装此方法使之支持泛型返回值。
1 |
|
调整大小
使用 ArrayUtil.resize
方法生成一个新的重新设置大小的数组。
合并数组
ArrayUtil.addAll
方法采用可变参数方式,将多个泛型数组合并为一个数组。
克隆
数组本身支持clone方法,因此确定为某种类型数组时调用ArrayUtil.clone(T[])
,不确定类型的使用ArrayUtil.clone(T)
,两种重载方法在实现上有所不同,但是在使用中并不能感知出差别。
泛型数组调用原生克隆
1
2
3Integer[] b = {1,2,3};
Integer[] cloneB = ArrayUtil.clone(b);
Assert.assertArrayEquals(b, cloneB);非泛型数组(原始类型数组)调用第二种重载方法
1
2
3int[] a = {1,2,3};
int[] clone = ArrayUtil.clone(a);
Assert.assertArrayEquals(a, clone);
有序列表生成
ArrayUtil.range
方法有三个重载,这三个重载配合可以实现支持步进的有序数组或者步进为1的有序数组。这种列表生成器在Python中做为语法糖存在。
拆分数组
ArrayUtil.split
方法用于拆分一个byte数组,将byte数组平均分成几等份,常用于消息拆分。
过滤
ArrayUtil.filter
方法用于过滤已有数组元素,只针对泛型数组操作,原始类型数组并未提供。 方法中Filter
接口用于返回boolean值决定是否保留。
过滤数组,只保留偶数
1 |
|
编辑
对已有数组编辑,获得编辑后的值。
1 |
|
zip
ArrayUtil.zip
方法传入两个数组,第一个数组为key,第二个数组对应位置为value,此方法在Python中为zip()函数。
1 |
|
是否包含元素
ArrayUtil.contains
方法只针对泛型数组,检测指定元素是否在数组中。
包装和拆包
在原始类型元素和包装类型中,Java实现了自动包装和拆包,但是相应的数组无法实现,于是便是用ArrayUtil.wrap
和ArrayUtil.unwrap
对原始类型数组和包装类型数组进行转换。
判断对象是否为数组
ArrayUtil.isArray
方法封装了obj.getClass().isArray()
。
转为字符串
ArrayUtil.toString
通常原始类型的数组输出为字符串时无法正常显示,于是封装此方法可以完美兼容原始类型数组和包装类型数组的转为字符串操作。ArrayUtil.join
方法使用间隔符将一个数组转为字符串,比如[1,2,3,4]这个数组转为字符串,间隔符使用“-”的话,结果为 1-2-3-4,join方法同样支持泛型数组和原始类型数组。
toArray
ArrayUtil.toArray
方法针对ByteBuffer转数组提供便利。
随机工具-RandomUtil
说明
RandomUtil
主要针对JDK中Random
对象做封装,严格来说,Java产生的随机数都是伪随机数,因此Hutool封装后产生的随机结果也是伪随机结果。不过这种随机结果对于大多数情况已经够用。
使用
RandomUtil.randomInt
获得指定范围内的随机数
例如我们想产生一个[10, 100)的随机数,则:
1 |
|
RandomUtil.randomBytes
随机bytes,一般用于密码或者salt生成
1 |
|
RandomUtil.randomEle
随机获得列表中的元素RandomUtil.randomEleSet
随机获得列表中的一定量的不重复元素,返回Set
1 |
|
RandomUtil.randomString
获得一个随机的字符串(只包含数字和字符)RandomUtil.randomNumbers
获得一个只包含数字的字符串RandomUtil.weightRandom
权重随机生成器,传入带权重的对象,然后根据权重随机获取对象
唯一ID工具-IdUtil
介绍
在分布式环境中,唯一ID生成应用十分广泛,生成方法也多种多样,Hutool针对一些常用生成策略做了简单封装。
唯一ID生成器的工具类,涵盖了:
- UUID
- ObjectId(MongoDB)
- Snowflake(Twitter)
使用
UUID
UUID全称通用唯一识别码(universally unique identifier),JDK通过java.util.UUID
提供了 Leach-Salz 变体的封装。在Hutool中,生成一个UUID字符串方法如下:
1 |
|
说明 Hutool重写
java.util.UUID
的逻辑,对应类为cn.hutool.core.lang.UUID
,使生成不带-的UUID字符串不再需要做字符替换,性能提升一倍左右。
ObjectId
ObjectId是MongoDB数据库的一种唯一ID生成策略,是UUID version1的变种,详细介绍可见:服务化框架-分布式Unique ID的生成方法一览。
Hutool针对此封装了cn.hutool.core.lang.ObjectId
,快捷创建方法为:
1 |
|
Snowflake
分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。Twitter的Snowflake 算法就是这种生成器。
使用方法如下:
1 |
|
注意
IdUtil.createSnowflake
每次调用会创建一个新的Snowflake对象,不同的Snowflake对象创建的ID可能会有重复,因此请自行维护此对象为单例,或者使用IdUtil.getSnowflake
使用全局单例对象。
压缩工具-ZipUtil
由来
在Java中,对文件、文件夹打包,压缩是一件比较繁琐的事情,我们常常引入Zip4j进行此类操作。但是很多时候,JDK中的zip包就可满足我们大部分需求。ZipUtil就是针对java.util.zip
做工具化封装,使压缩解压操作可以一个方法搞定,并且自动处理文件和目录的问题,不再需要用户判断,压缩后的文件也会自动创建文件,自动创建父目录,大大简化的压缩解压的复杂度。
方法
Zip
- 压缩
ZipUtil.zip
方法提供一系列的重载方法,满足不同需求的压缩需求,这包括:
- 打包到当前目录(可以打包文件,也可以打包文件夹,根据路径自动判断)
1 |
|
- 指定打包后保存的目的地,自动判断目标是文件还是文件夹
1 |
|
- 可选是否包含被打包的目录。比如我们打包一个照片的目录,打开这个压缩包有可能是带目录的,也有可能是打开压缩包直接看到的是文件。zip方法增加一个boolean参数可选这两种模式,以应对众多需求。
1 |
|
- 多文件或目录压缩。可以选择多个文件或目录一起打成zip包。
1 |
|
- 解压
ZipUtil.unzip
解压。同样提供几个重载,满足不同需求。
1 |
|
Gzip
Gzip是网页传输中广泛使用的压缩方式,Hutool同样提供其工具方法简化其过程。
ZipUtil.gzip
压缩,可压缩字符串,也可压缩文件 ZipUtil.unGzip
解压Gzip文件
Zlib
ZipUtil.zlib
压缩,可压缩字符串,也可压缩文件 ZipUtil.unZlib
解压zlib文件
注意 ZipUtil默认情况下使用系统编码,也就是说:
- 如果你在命令行下运行,则调用系统编码(一般Windows下为GBK、Linux下为UTF-8)
- 如果你在IDE(如Eclipse)下运行代码,则读取的是当前项目的编码(详细请查阅IDE设置,我的项目默认都是UTF-8编码,因此解压和压缩都是用这个编码)
常见问题
- 解压时报
java.lang.IllegalArgumentException:MALFORMED
错误
基本是因为编码问题,Hutool默认使用UTF-8编码,自定义为其他编码即可(一般为GBK)。
1 |
|
- 压缩并添加密码
Hutool或JDK的Zip工具并不支持添加密码,可以考虑使用Zip4j完成,以下代码来自Zip4j官网。
1 |
|
引用工具-ReferenceUtil
介绍
引用工具类,主要针对Reference 工具化封装
主要封装包括:
- SoftReference 软引用,在GC报告内存不足时会被GC回收
- WeakReference 弱引用,在GC时发现弱引用会回收其对象
- PhantomReference 虚引用,在GC时发现虚引用对象,会将PhantomReference插入ReferenceQueue。此时对象未被真正回收,要等到ReferenceQueue被真正处理后才会被回收。
方法
create
根据类型枚举创建引用。
正则工具-ReUtil
由来
在文本处理中,正则表达式几乎是全能的,但是Java的正则表达式有时候处理一些事情还是有些繁琐,所以我封装了部分常用功能。就比如说我要匹配一段文本中的某些部分,我们需要这样做:
1 |
|
其中牵涉到多个对象,想用的时候真心记不住。好吧,既然功能如此常用,我就封装一下:
1 |
|
使用
ReUtil.extractMulti
抽取多个分组然后把它们拼接起来
1 |
|
ReUtil.delFirst
删除第一个匹配到的内容
1 |
|
ReUtil.findAll
查找所有匹配文本
1 |
|
ReUtil.getFirstNumber
找到匹配的第一个数字
1 |
|
ReUtil.isMatch
给定字符串是否匹配给定正则
1 |
|
ReUtil.replaceAll
通过正则查找到字符串,然后把匹配到的字符串加入到replacementTemplate中,$1表示分组1的字符串
1 |
|
ReUtil.escape
转义给定字符串,为正则相关的特殊符号转义
1 |
|
身份证工具-IdcardUtil
由来
在日常开发中,我们对身份证的验证主要是正则方式(位数,数字范围等),但是中国身份证,尤其18位身份证每一位都有严格规定,并且最后一位为校验位。而我们在实际应用中,针对身份证的验证理应严格至此。于是IdcardUtil
应运而生。
IdcardUtil
从3.0.4版本起加入Hutool工具家族,请升级至此版本以上可使用。
介绍
IdcardUtil
现在支持大陆15位、18位身份证,港澳台10位身份证。
工具中主要的方法包括:
isValidCard
验证身份证是否合法convert15To18
身份证15位转18位getBirthByIdCard
获取生日getAgeByIdCard
获取年龄getYearByIdCard
获取生日年getMonthByIdCard
获取生日月getDayByIdCard
获取生日天getGenderByIdCard
获取性别getProvinceByIdCard
获取省份
使用
1 |
|
声明 以上两个身份证号码为随机编造的,如有雷同,纯属巧合。
信息脱敏工具-DesensitizedUtil
介绍
在数据处理或清洗中,可能涉及到很多隐私信息的脱敏工作,因此Hutool针对常用的信息封装了一些脱敏方法。
现阶段支持的脱敏数据类型包括:
- 用户id
- 中文姓名
- 身份证号
- 座机号
- 手机号
- 地址
- 电子邮件
- 密码
- 中国大陆车牌,包含普通车辆、新能源车辆
- 银行卡
整体来说,所谓脱敏就是隐藏掉信息中的一部分关键信息,用*
代替,自定义隐藏可以使用StrUtil.hide
方法完成。
使用
我们以身份证号码为例:
1 |
|
对于约定俗成的脱敏,我们可以不用指定隐藏位数,比如手机号:
1 |
|
当然还有一些简单粗暴的脱敏,比如密码,只保留了位数信息:
1 |
|
社会信用代码工具-CreditCodeUtil
介绍
法人和其他组织统一社会信用代码制度,相当于让法人和其他组织拥有了一个全国统一的“身份证号”。
规则如下:
- 第一部分:登记管理部门代码1位 (数字或大写英文字母)
- 第二部分:机构类别代码1位 (数字或大写英文字母)
- 第三部分:登记管理机关行政区划码6位 (数字)
- 第四部分:主体标识码(组织机构代码)9位 (数字或大写英文字母)
- 第五部分:校验码1位 (数字或大写英文字母)
此工具主要提供校验和随机生成。
使用
校验
1 |
|
随机社会信用代码
1 |
|
SPI加载工具-ServiceLoaderUtil
介绍
SPI(Service Provider Interface),是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
更多介绍见:https://www.jianshu.com/p/3a3edbcd8f24
使用
定义一个接口:
1 |
|
有两个实现:
1 |
|
然后在classpath的META-INF/services
下创建一个文件,叫cn.hutool.test.spi.SPIService
,内容为:
1 |
|
加载第一个可用服务,如果用户定义了多个接口实现类,只获取第一个不报错的服务。这个方法多用于同一接口多种实现的自动甄别加载, 通过判断jar是否引入,自动找到实现类。
1 |
|
HashMap扩展-Dict
由来
如果你了解Python,你一定知道Python有dict这一数据结构,也是一种KV(Key-Value)结构的数据结构,类似于Java中的Map,但是提供了更加灵活多样的使用。Hutool中的Dict对象旨在实现更加灵活的KV结构,针对强类型,提供丰富的getXXX操作,将HashMap扩展为无类型区别的数据结构。
介绍
Dict继承HashMap,其key为String类型,value为Object类型,通过实现BasicTypeGetter
接口提供针对不同类型的get方法,同时提供针对Bean的转换方法,大大提高Map的灵活性。
Hutool-db中Entity是Dict子类,做为数据的媒介。
使用
创建
1 |
|
通过链式构造,创建Dict对象,同时可以按照Map的方式使用。
获取指定类型的值
1 |
|
单例工具-Singleton
为什么会有这个类
平常我们使用单例不外乎两种方式:
- 在对象里加个静态方法getInstance()来获取。此方式可以参考 【转】线程安全的单例模式 这篇博客,可分为饿汉和饱汉模式。
- 通过Spring这类容器统一管理对象,用的时候去对象池中拿。Spring也可以通过配置决定懒汉或者饿汉模式
说实话我更倾向于第二种,但是Spring更注重的是注入,而不是拿,于是我想做Singleton这个类,维护一个单例的池,用这个单例对象的时候直接来拿就可以,这里我用的懒汉模式。我只是想把单例的管理方式换一种思路,我希望管理单例的是一个容器工具,而不是一个大大的框架,这样能大大减少单例使用的复杂性。
使用
1 |
|
总结
大家如果有兴趣可以看下这个类,实现非常简单,一个HashMap用于做为单例对象池,通过newInstance()实例化对象(不支持带参数的构造方法),无论取还是创建对象都是线程安全的(在单例对象数量非常庞大且单例对象实例化非常耗时时可能会出现瓶颈),考虑到在get的时候使双重检查锁,但是并不是线程安全的,故直接加了synchronized
做为修饰符,欢迎在此给出建议。
断言-Assert
由来
Java中有assert
关键字,但是存在许多问题:
- assert关键字需要在运行时候显式开启才能生效,否则你的断言就没有任何意义。
- 用assert代替if是陷阱之二。assert的判断和if语句差不多,但两者的作用有着本质的区别:assert关键字本意上是为测试调试程序时使用的,但如果不小心用assert来控制了程序的业务流程,那在测试调试结束后去掉assert关键字就意味着修改了程序的正常的逻辑。
- assert断言失败将面临程序的退出。
因此,并不建议使用此关键字。相应的,在Hutool中封装了更加友好的Assert类,用于断言判定。
介绍
Assert类更像是Junit中的Assert类,也很像Guava中的Preconditions,主要作用是在方法或者任何地方对参数的有效性做校验。当不满足断言条件时,会抛出IllegalArgumentException或IllegalStateException异常。
使用
1 |
|
更多方法
- isTrue 是否True
- isNull 是否是null值,不为null抛出异常
- notNull 是否非null值
- notEmpty 是否非空
- notBlank 是否非空白符
- notContain 是否为子串
- notEmpty 是否非空
- noNullElements 数组中是否包含null元素
- isInstanceOf 是否类实例
- isAssignable 是子类和父类关系
- state 会抛出IllegalStateException异常
二进码十进数-BCD
介绍
BCD码(Binary-Coded Decimal)亦称二进码十进数或二-十进制代码,BCD码这种编码形式利用了四个位元来储存一个十进制的数码,使二进制和十进制之间的转换得以快捷的进行。
这种编码技巧最常用于会计系统的设计里,因为会计制度经常需要对很长的数字串作准确的计算。相对于一般的浮点式记数法,采用BCD码,既可保存数值的精确度,又可免却使电脑作浮点运算时所耗费的时间。此外,对于其他需要高精确度的计算,BCD编码亦很常用。
BCD码是四位二进制码, 也就是将十进制的数字转化为二进制, 但是和普通的转化有一点不同, 每一个十进制的数字0-9都对应着一个四位的二进制码,对应关系如下: 十进制0 对应 二进制0000 ;十进制1 对应二进制0001 ……. 9 1001 接下来的10就有两个上述的码来表示 10 表示为00010000 也就是BCD码是遇见1001就产生进位,不象普通的二进制码,到1111才产生进位10000
方法
1 |
|
控制台打印封装-Console
由来
编码中我们常常需要调试输出一些信息,除了打印日志,最长用的要数System.out
和System.err
比如我们打印一个Hello World,可以这样写:
1 |
|
但是面对纷杂的打印需求,System.out.println
无法满足,比如:
- 不支持参数,对象打印需要拼接字符串
- 不能直接打印数组,需要手动调用
Arrays.toString
考虑到以上问题,我封装了Console
对象。
Console对象的使用更加类似于Javascript的
console.log()
方法,这也是借鉴了JS的一个语法糖。
使用
Console.log
这个方法基本等同于System.out.println
,但是支持类似于Slf4j的字符串模板语法,同时也会自动将对象(包括数组)转为字符串形式。
1 |
|
Console.error
这个方法基本等同于System.err.println
,,但是支持类似于Slf4j的字符串模板语法,同时也会自动将对象(包括数组)转为字符串形式。
字段验证器-Validator
作用
验证给定字符串是否满足指定条件,一般用在表单字段验证里。
此类中全部为静态方法。
使用
判断验证
直接调用Validator.isXXX(String value)
既可验证字段,返回是否通过验证。
例如:
1 |
|
表示验证给定字符串是否复合电子邮件格式。
其他验证信息请参阅Validator
类
如果Validator里的方法无法满足自己的需求,那还可以调用
1 |
|
来通过正则表达式灵活的验证内容。
异常验证
除了手动判断,我们有时需要在判断未满足条件时抛出一个异常,Validator同样提供异常验证机制:
1 |
|
因为内容中包含非中文字符,因此会抛出ValidateException。
字符串格式化-StrFormatter
由来
我一直对Slf4j的字符串格式化情有独钟,通过{}
这种简单的占位符完成字符串的格式化。于是参考Slf4j的源码,便有了StrFormatter
。
StrFormatter.format的快捷使用方式为
StrUtil.format
,推荐使用后者。
使用
1 |
|
树结构工具-TreeUtil
介绍
考虑到菜单等需求的普遍性,有用户提交了一个扩展性极好的树状结构实现。这种树状结构可以根据配置文件灵活的定义节点之间的关系,也能很好的兼容关系数据库中数据。实现
1 |
|
树状结构中最大的问题就是关系问题,在数据库中,每条数据通过某个字段关联自己的父节点,每个业务中这个字段的名字都不同,如何解决这个问题呢?
PR的提供者提供了一种解决思路:自定义字段名,节点不再是一个bean,而是一个map,实现灵活的字段名定义。
使用
定义结构
我们假设要构建一个菜单,可以实现系统管理和店铺管理,菜单的样子如下:
1 |
|
那这种结构如何保存在数据库中呢?一般是这样的:
id | parentId | name | weight |
---|---|---|---|
1 | 0 | 系统管理 | 5 |
11 | 1 | 用户管理 | 10 |
111 | 1 | 用户添加 | 11 |
2 | 0 | 店铺管理 | 5 |
21 | 2 | 商品管理 | 10 |
221 | 2 | 添加添加 | 11 |
我们看到,每条数据根据parentId
相互关联并表示层级关系,parentId
在这里也叫外键。
构建Tree
1 |
|
TreeNode表示一个抽象的节点,也表示数据库中一行数据。 如果有其它数据,可以调用
setExtra
添加扩展字段。
1 |
|
因为两个Tree是平级的,再没有上层节点,因此为List。
自定义字段名
1 |
|
通过TreeNodeConfig我们可以自定义节点的名称、关系节点id名称,这样就可以和不同的数据库做对应。
Bean工具-BeanUtil
什么是Bean
把一个拥有对属性进行set和get方法的类,我们就可以称之为JavaBean。实际上JavaBean就是一个Java类,在这个Java类中就默认形成了一种规则——对属性进行设置和获得。而反之将说Java类就是一个JavaBean,这种说法是错误的,因为一个java类中不一定有对属性的设置和获得的方法(也就是不一定有set和get方法)。
通常Java中对Bean的定义是包含setXXX和getXXX方法的对象,在Hutool中,采取一种简单的判定Bean的方法:是否存在只有一个参数的setXXX方法。
Bean工具类主要是针对这些setXXX和getXXX方法进行操作,比如将Bean对象转为Map等等。
方法
是否为Bean对象
BeanUtil.isBean
方法根据是否存在只有一个参数的setXXX方法或者public类型的字段来判定是否是一个Bean对象。这样的判定方法主要目的是保证至少有一个setXXX方法用于属性注入。
1 |
|
内省 Introspector
把一类中需要进行设置和获得的属性访问权限设置为private(私有的)让外部的使用者看不见摸不着,而通过public(共有的)set和get方法来对其属性的值来进行设置和获得,而内部的操作具体是怎样的?外界使用的人不用知道,这就称为内省。
Hutool中对内省的封装包括:
BeanUtil.getPropertyDescriptors
获得Bean字段描述数组
1 |
|
BeanUtil.getFieldNamePropertyDescriptorMap
获得字段名和字段描述MapBeanUtil.getPropertyDescriptor
获得Bean类指定属性描述
Bean属性注入
BeanUtil.fillBean
方法是bean注入的核心方法,此方法传入一个ValueProvider接口,通过实现此接口来获得key对应的值。CopyOptions参数则提供一些注入属性的选项。
CopyOptions的配置项包括:
editable
限制的类或接口,必须为目标对象的实现接口或父类,用于限制拷贝的属性,例如一个类我只想复制其父类的一些属性,就可以将editable设置为父类。ignoreNullValue
是否忽略空值,当源对象的值为null时,true: 忽略而不注入此值,false: 注入nullignoreProperties
忽略的属性列表,设置一个属性列表,不拷贝这些属性值ignoreError
是否忽略字段注入错误
可以通过CopyOptions.create()
方法创建一个默认的配置项,通过setXXX方法设置每个配置项。
ValueProvider接口需要实现两个方法:
value
方法是通过key和目标类型来从任何地方获取一个值,并转换为目标类型,如果返回值不和目标类型匹配,将会自动调用Convert.convert
方法转换。containsKey
方法主要是检测是否包含指定的key,如果不包含这个key,其对应的属性将会忽略注入。
首先定义两个bean:
1 |
|
然后注入这个bean:
1 |
|
同时,Hutool还提供了BeanUtil.toBean
方法,此处并不是传Bean对象,而是Bean类,Hutool会自动调用默认构造方法创建对象。
基于BeanUtil.fillBean
方法Hutool还提供了Map对象键值对注入Bean,其方法有:
BeanUtil.fillBeanWithMap
使用Map填充bean
1 |
|
BeanUtil.fillBeanWithMapIgnoreCase
使用Map填充bean,忽略大小写
1 |
|
同时提供了map转bean的方法,与fillBean不同的是,此处并不是传Bean对象,而是Bean类,Hutool会自动调用默认构造方法创建对象。当然,前提是Bean类有默认构造方法(空构造),这些方法有:
BeanUtil.toBean
1 |
|
BeanUtil.toBeanIgnoreCase
1 |
|
Bean转为Map
BeanUtil.beanToMap
方法则是将一个Bean对象转为Map对象。
1 |
|
Bean转Bean
Bean之间的转换主要是相同属性的复制,因此方法名为copyProperties
,此方法支持Bean和Map之间的字段复制。
BeanUtil.copyProperties
方法同样提供一个CopyOptions
参数用于自定义属性复制。
1 |
|
Alias注解
5.x的Hutool中增加了一个自定义注解:@Alias
,通过此注解可以给Bean的字段设置别名。
首先我们给Bean加上注解:
1 |
|
同样Alias注解支持注入Bean时的别名:
1 |
|
DynaBean
介绍
DynaBean是使用反射机制动态操作JavaBean的一个封装类,通过这个类,可以通过字符串传入name方式动态调用get和set方法,也可以动态创建JavaBean对象,亦或者执行JavaBean中的方法。
使用
我们先定义一个JavaBean:
1 |
|
创建
1 |
|
操作
我们通过DynaBean来包装并操作这个Bean
1 |
|
这样我们就可以像操作Map一样动态操作JavaBean
invoke
除了标准的get和set方法,也可以调用invoke方法执行对象中的任意方法:
1 |
|
说明: DynaBean默认实现了hashCode、equals和toString三个方法,这三个方法也是默认调用原对象的相应方法操作。
表达式解析-BeanPath
由来
很多JavaBean嵌套有很多层对象,这其中还夹杂着Map、Collection等对象,因此获取太深的嵌套对象会让代码变得冗长不堪。因此我们可以考虑使用一种表达式还获取指定深度的对象,于是BeanResolver应运而生。
原理
通过传入一个表达式,按照表达式的规则获取bean下指定的对象。
表达式分为两种:
.
表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值[]
表达式,可以获取集合等对象中对应index的值
栗子:
person
获取Bean对象下person字段的值,或者Bean本身如果是Person对象,返回本身。person.name
获取Bean中person字段下name字段的值,或者Bean本身如果是Person对象,返回其name字段的值。persons[3]
获取persons字段下第三个元素的值(假设person是数组或Collection对象)person.friends[5].name
获取person字段下friends列表(或数组)的第5个元素对象的name属性
使用
由于嵌套Bean定义过于复杂,在此我们省略,有兴趣的可以看下这里:cn.hutool.core.lang.test.bean(src/test/java下)下定义了测试用例用的bean。
首先我们创建这个复杂的Bean(实际当中这个复杂的Bean可能是从数据库中获取,或者从JSON转入)
这个复杂Bean的关系是这样的:
定义一个Map包含用户信息(UserInfoDict)和一个标志位(flag),用户信息包括一些基本信息和一个考试信息列表(ExamInfoDict)。
1 |
|
下面,我们使用BeanPath
获取这个Map下此用户第一门考试的ID:
1 |
|
只需两句(甚至一句)即可完成复杂Bean中各层次对象的获取。
说明: 为了简化
BeanPath
的使用,Hutool在BeanUtil中也加入了快捷入口方法:BeanUtil.getProperty
,这个方法的命名更容易理解(毕竟BeanPath不但可以解析Bean,而且可以解析Map和集合)。
Bean描述-BeanDesc
介绍
Hutool封装了Bean的信息描述来将一个Bean的相关信息全部通过反射解析出来,此类类似于JDK的BeanInfo
,也可以理解为是这个类的强化版本。
BeanDesc包含所有字段(属性)及对应的Getter方法和Setter方法,与BeanInfo
不同的是,BeanDesc
要求属性和getter、setter必须严格对应,即如果有非public属性,没有getter,则不能获取属性值,没有setter也不能注入属性值。
属性和getter、setter关联规则如下:
- 忽略字段和方法名的大小写(匹配时)
- 字段名是XXX,则Getter查找getXXX、isXXX、getIsXXX
- 字段名是XXX,Setter查找setXXX、setIsXXX
- Setter忽略参数值与字段值不匹配的情况,因此有多个参数类型的重载时,会调用首次匹配的
使用
我们定义一个较为复杂的Bean:
1 |
|
字段getter方法获取
- 一般字段
1 |
|
- Boolean字段
我们会发现User
中的boolean字段叫做isAdmin
,此时同名的getter也可以获取到:
1 |
|
当然,用户如果觉得isIsXXX
才是正确的,BeanDesc
也可以完美获取,我们以isSuper
字段为例:
1 |
|
字段属性赋值
1 |
|
空检查属性获取-Opt
介绍
在嵌套对象的属性获取中,由于子对象无法得知是否为null
,每次获取属性都要检查属性兑现是否为null,使得代码会变得特备臃肿,因此使用Opt
来优雅的链式获取属性对象值。
声明:此类的作者:阿超 ,PR来自:https://gitee.com/dromara/hutool/pulls/426
使用
我们先定义一个嵌套的Bean:
1 |
|
假设我们想获取address
属性,则:
1 |
|
由于school对象的值为null
,一般直接获取会报空指针,使用Opt
即可避免判断。
ofBlankAble函数基于ofNullable的逻辑下,额外进行了空字符串判断
1
2
3// ofBlankAble相对于ofNullable考虑了字符串为空串的情况
String hutool = OptionalBean.ofBlankAble("").orElse("hutool");
Assert.assertEquals("hutool", hutool);原版Optional有区别的是,get不会抛出NoSuchElementException
如果想使用原版Optional中的get这样,获取一个一定不为空的值,则应该使用orElseThrow
1
2
3
4// 和原版Optional有区别的是,get不会抛出NoSuchElementException
// 如果想使用原版Optional中的get这样,获取一个一定不为空的值,则应该使用orElseThrow
Object opt = OptionalBean.ofNullable(null).get();
Assert.assertNull(opt);这是参考了jdk11 Optional中的新函数isEmpty,用于判断不存在值的情况
1
2
3
4// 这是参考了jdk11 Optional中的新函数
// 判断包裹内元素是否为空,注意并没有判断空字符串的情况
boolean isEmpty = OptionalBean.empty().isEmpty();
Assert.assertTrue(isEmpty);灵感来源于jdk9 Optional中的新函数ifPresentOrElse,用于 存在值时执行某些操作,不存在值时执行另一个操作,支持链式编程
1
2
3
4// 灵感来源于jdk9 Optional中的新函数ifPresentOrElse
// 存在就打印对应的值,不存在则用{@code System.err.println}打印另一句字符串
OptionalBean.ofNullable("Hello Hutool!").ifPresentOrElse(System.out::println, () -> System.err.println("Ops!Something is wrong!"));
OptionalBean.empty().ifPresentOrElse(System.out::println, () -> System.err.println("Ops!Something is wrong!"));新增了peek函数,相当于ifPresent的链式调用(个人常用)
1
2
3
4
5User user = new User();
// 相当于ifPresent的链式调用
OptionalBean.ofNullable("hutool").peek(user::setUsername).peek(user::setNickname);
Assert.assertEquals("hutool", user.getNickname());
Assert.assertEquals("hutool", user.getUsername());
// 注意,传入的lambda中,对包裹内的元素执行赋值操作并不会影响到原来的元素 String name = OptionalBean.ofNullable(“hutool”).peek(username -> username = “123”).peek(username -> username = “456”).get(); Assert.assertEquals(“hutool”, name);
1 |
|
对orElseThrow进行了重载,支持 双冒号+自定义提示语 写法,比原来的
1
orElseThrow(() -> new IllegalStateException("Ops!Something is wrong!"))
更加优雅,修改后写法为:
1
orElseThrow(IllegalStateException::new, "Ops!Something is wrong!")
学习
经常有朋友问我,你这个Opt
,参数怎么都是一些lambda
,我怎么知道对应的lambda
怎么写呢?
这函数式编程,真是一件美事啊~
对于这种情况,我们依靠我们强大的idea
即可
例如此处我写到这里写不会了
1 |
|
这里idea
为我们提示了参数类型,可这个Function
我也不知道它是个什么
实际上,我们new
一个就好了
1 |
|
这里idea
提示了剩下的代码,我们选Function
就行了,接下来如下:
1 |
|
此处开始编译报错了,不要着急,我们这里根据具体操作选取返回值
例如我这里是想判断user
是否为空,不为空时调用getSchool
,从而获取其中的返回值String
类型的school
我们就如下写法,将第二个泛型,也就是象征返回值的泛型改为String
:
1 |
|
然后我们使用idea
的修复所有,默认快捷键alt
+回车
1 |
|
选择第一个Implement methods
即可,这时候弹出一个框,提示让你选择你想要实现的方法
这里就选择我们的apply
方法吧,按下一个回车就可以了,或者点击选中apply
,再按一下OK
按钮
1 |
|
此时此刻,代码变成了这样子
1 |
|
这里重写的方法里面就写你自己的逻辑(别忘了补全后面的分号)
1 |
|
我们可以看到,上边的new Function<User, String>()
变成了灰色
我们在它上面按一下alt
+enter
(回车)
1 |
|
选择第一个Replace with lambda
,就会自动缩写为lambda
啦
1 |
|
如果选择第二个,则会缩写为我们双冒号格式
1 |
|
看,是不是很简单!
集合工具-CollUtil
介绍
这个工具主要增加了对数组、集合类的操作。
join
方法
将集合转换为字符串,这个方法还是挺常用,是StrUtil.split
的反方法。这个方法的参数支持各种类型对象的集合,最后连接每个对象时候调用其toString()
方法。栗子如下:
1 |
|
sortPageAll
、sortPageAll2
方法
这个方法其实是一个真正的组合方法,功能是:将给定的多个集合放到一个列表(List
)中,根据给定的Comparator
对象排序,然后分页取数据。这个方法非常类似于数据库多表查询后排序分页,这个方法存在的意义也是在此。sortPageAll2
功能和sortPageAll
的使用方式和结果是 一样的,区别是sortPageAll2
使用了BoundedPriorityQueue
这个类来存储组合后的列表,不知道哪种性能更好一些,所以就都保留了。使用此方法,栗子如下:
1 |
|
sortEntrySetToList
方法
这个方法主要是对Entry<Long, Long>
按照Value的值做排序,使用局限性较大,我已经忘记哪里用到过了……
popPart
方法
这个方法传入一个栈对象,然后弹出指定数目的元素对象,弹出是指pop()
方法,会从原栈中删掉。
append
方法
在给定数组里末尾加一个元素,其实List.add()也是这么实现的,这个方法存在的意义是只有少量的添加元素时使用,因为内部使用了System.arraycopy
,每调用一次就要拷贝数组一次。这个方法也是为了在某些只能使用数组的情况下使用,省去了先要转成List
,添加元素,再转成Array。
7. resize
方法
重新调整数据的大小,如果调整后的大小比原来小,截断,如果比原来大,则多出的位置空着。(貌似List在扩充的时候会用到类似的方法)
addAll
方法
将多个数据合并成一个数组
sub
方法
对集合切片,其他类型的集合会转换成List
,封装List.subList
方法,自动修正越界等问题,完全避免IndexOutOfBoundsException
异常。
isEmpty
、isNotEmpty
方法
判断集合是否为空(包括null和没有元素的集合)。
zip
方法
此方法也是来源于Python的一个语法糖,给定两个集合,然后两个集合中的元素一一对应,成为一个Map。此方法还有一个重载方法,可以传字符,然后给定分分隔符,字符串会被split成列表。栗子:
1 |
|
列表工具-ListUtil
介绍
List在集合中中使用最为频繁,因此新版本的Hutool中针对List
单独封装了工具方法。
使用
过滤列表
1 |
|
获取满足指定规则所有的元素的位置
1 |
|
其他方法与CollUtil
工具类似,很多工具也有重复。
拆分
对集合按照指定长度分段,每一个段为单独的集合,返回这个集合的列表:
1 |
|
也可以平均拆分,即平均分成N份,每份的数量差不超过1:
1 |
|
编辑元素
我们可以针对集合中所有元素按照给定的lambda定义规则修改元素:
1 |
|
查找位置
1 |
|
列表截取
1 |
|
排序
如我们想按照bean对象的order字段值排序:
1 |
|
元素交换
1 |
|
Iterator工具-IterUtil
来源
最早此工具类中的方法是在CollUtil中的,由于经过抽象,因此单独拿出来以适应更广的场景。
方法介绍
isEmpty
是否为null或者无元素isNotEmpty
是否为非null或者至少一个元素hasNull
是否有null元素isAllNull
是否全部为null元素countMap
根据集合返回一个元素计数的Map,所谓元素计数就是假如这个集合中某个元素出现了n次,那将这个元素做为key,n做为valuejoin
使用分隔符将集合转换为字符串toMap
toMap Entry列表转Map,或者key和value单独列表转MapasIterator
Enumeration转IteratorasIterable
Iterator转IterablegetFirst
获取列表的第一个元素getElementType
获取元素类型
有界优先队列-BoundedPriorityQueue
简介
举个例子。我有一个用户表,这个表根据用户名被Hash到不同的数据库实例上,我要找出这些用户中最热门的5个,怎么做?我是这么做的:
- 在每个数据库实例上找出最热门的5个
- 将每个数据库实例上的这5条数据按照热门程度排序,最后取出前5条
这个过程看似简单,但是你应用服务器上的代码要写不少。首先需要Query N个列表,加入到一个新列表中,排序,再取前5。这个过程不但代码繁琐,而且牵涉到多个列表,非常浪费空间。
于是,BoundedPriorityQueue
应运而生。
先看Demo:
1 |
|
原理非常简单。设定好队列的容量,然后把所有的数据add或者offer进去(两个方法相同),就会得到前5条数据了。
线程安全的HashSet-ConcurrentHashSet
简介
我们知道,JDK提供了线程安全的HashMap:ConcurrentHashMap,但是没有提供对应的ConcurrentHashSet,Hutool借助ConcurrentHashMap封装了线程安全的ConcurrentHashSet。
使用
与普通的HashSet使用一致:
1 |
|
集合串行流工具-CollStreamUtil
介绍
Java8中的新特性之一就是Stream,Hutool针对常用操作做了一些封装
使用
集合转Map
1 |
|
我们可以建立一个学生id和学生对象之间的map:
1 |
|
我们也可以自定义Map的key和value放的内容,如我们可以将学生信息的id和姓名生成map:
1 |
|
分组
我们将学生按照班级分组:
1 |
|
转换提取
我们可以将学生信息列表转换提取为姓名的列表:
1 |
|
合并
合并两个相同key类型的map,可自定义合并的lambda,将key value1 value2合并成最终的类型,注意value可能为空的情况。
1 |
|
定义merge规则:
1 |
|
行遍历器-LineIter
介绍
此工具分别参考和Apache Commons io
和Guava
项目。
将Reader包装为一个按照行读取的Iterator。
使用
1 |
|
Map工具-MapUtil
介绍
MapUtil是针对Map的一一列工具方法的封装,包括getXXX的快捷值转换方法。
方法
isEmpty
、isNotEmpty
判断Map为空和非空方法,空的定义为null或没有值newHashMap
快速创建多种类型的HashMap实例createMap
创建自定义的Map类型的Mapof
此方法将一个或多个键值对加入到一个新建的Map中,下面是栗子:
1 |
|
toListMap
行转列,合并相同的键,值合并为列表,将Map列表中相同key的值组成列表做为Map的value,例如传入数据是:
1 |
|
结果为:
1 |
|
toMapList
列转行。将Map中值列表分别按照其位置与key组成新的map,例如传入数据:
1 |
|
结果为:
1 |
|
join
、joinIgnoreNull
、sortJoin
将Map按照给定的分隔符转换为字符串,此方法一般用于签名。
1 |
|
filter
过滤过程通过传入的Editor实现来返回需要的元素内容,这个Editor实现可以实现以下功能:1、过滤出需要的对象,如果返回null表示这个元素对象抛弃 2、修改元素对象,返回集合中为修改后的对象
1 |
|
结果为
1 |
|
reverse
Map的键和值互换
1 |
|
结果为:
1 |
|
sort
排序MapgetAny
获取Map的部分key生成新的Mapget
、getXXX
获取Map中指定类型的值
双向查找Map-BiMap
介绍
我们知道在Guava中提供了一种特殊的Map结构,叫做BiMap,它实现了一种双向查找的功能,即根据key查找value和根据value查找key,Hutool也同样提供此对象。
BiMap要求key和value都不能重复(非强制要求),如果key重复了,后加入的键值对会覆盖之前的键值对,如果value重复了,则会按照不确定的顺序覆盖key,这完全取决于map实现。比如HashMap无序(按照hash顺序),则谁覆盖谁和hash算法有关;如果是LinkedHashMap,则有序,是后加入的覆盖先加入的。
使用
1 |
|
可重复键值Map-TableMap
介绍
有时候我们需要键值对一一对应,但是又有可能有重复的键,也可能有重复的值,就像一个2列的表格一样:
键 | 值 |
---|---|
key1 | value1 |
key2 | value2 |
因此,Hutool创建了TableMap
这类数据结构,通过键值单独建立List方式,使键值对一一对应,实现正向和反向两种查找。
当然,这种Map无论是正向还是反向,都是遍历列表查找过程,相比标准的HashMap要慢,数据越多越慢。
使用
1 |
|
Base62编码解码-Base62
介绍
Base62编码是由10个数字、26个大写英文字母和26个小写英文字母组成,多用于安全领域和短URL生成。
使用
1 |
|
Base64编码解码-Base64
介绍
Base64编码是用64(2的6次方)个ASCII字符来表示256(2的8次方)个ASCII字符,也就是三位二进制数组经过编码后变为四位的ASCII字符显示,长度比原来增加1/3。
使用
1 |
|
Base32编码解码-Base32
介绍
Base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码。所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足。
使用
1 |
|
莫尔斯电码-Morse
介绍
摩尔斯电码也被称作摩斯密码,是一种时通时断的信号代码,通过不同的排列顺序来表达不同的英文字母、数字和标点符号。
摩尔斯电码是由点dot(.)划dash(-)这两种符号所组成的。
实现
编码
1 |
|
解码
1 |
|
BCD码-BCD
介绍
BCD码(Binary-Coded Decimal)亦称二进码十进数或二-十进制代码。
BCD码这种编码形式利用了四个位元来储存一个十进制的数码,使二进制和十进制之间的转换得以快捷的进行。
使用
1 |
|
回转N位密码-Rot
介绍
RotN(rotate by N places),回转N位密码,是一种简易的替换式密码,也是过去在古罗马开发的凯撒加密的一种变体。
使用
以Rot-13为例:
1 |
|
Punycode实现-PunyCode.md
介绍
Punycode是一个根据RFC 3492标准而制定的编码系统,主要用于把域名从地方语言所采用的Unicode编码转换成为可用于DNS系统的编码。
具体见:RFC 3492
使用
1 |
|
CSV文件处理工具-CsvUtil
介绍
逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。
Hutool针对此格式,参考FastCSV项目做了对CSV文件读写的实现(Hutool实现完全独立,不依赖第三方)
CsvUtil
是CSV工具类,主要封装了两个方法:
- getReader 用于对CSV文件读取
- getWriter 用于生成CSV文件
这两个方法分别获取CsvReader
对象和CsvWriter
,从而独立完成CSV文件的读写。
使用
读取CSV文件
读取为CsvRow
1 |
|
CsvRow
对象还记录了一些其他信息,包括原始行号等。
读取为Bean列表
首先测试的CSV:test_bean.csv
:
1 |
|
- 定义Bean:
1 |
|
- 读取
1 |
|
- 输出:
1 |
|
生成CSV文件
1 |
|
乱码问题
CSV文件本身为一种简单文本格式,有编码区分,你可以使用任意编码。
但是当使用Excel读取CSV文件时,如果你的编码与系统编码不一致,会出现乱码的情况,解决方案如下:
- 可以将csv文本编码设置为与系统一致,如Windows下可以设置GBK
- 可以增加BOM头来指定编码,这样Excel可以自动识别bom头的编码完成解析。
可复用字符串生成器-StrBuilder
介绍
在JDK提供的StringBuilder
中,拼接字符串变得更加高效和灵活,但是生成新的字符串需要重新构建StringBuilder
对象,造成性能损耗和内存浪费,因此Hutool提供了可复用的StrBuilder
。
使用
StrBuilder
和StringBuilder
使用方法基本一致,只是多了reset
方法可以重新构建一个新的字符串而不必开辟新内存。
1 |
|
多次构建字符串性能测试
我们模拟创建1000000次字符串对两者性能对比,采用TimeInterval
计时:
1 |
|
测试结果为:
1 |
|
性能几乎翻倍。也欢迎用户自行测试。
Unicode编码转换工具-UnicodeUtil
介绍
此工具主要针对类似于\\u4e2d\\u6587
这类Unicode字符做一些特殊转换。
使用
字符串转Unicode符
1 |
|
Unicode转字符串
1 |
|
由于\\u111
为非Unicode字符串,因此原样输出。
字符串切割-StrSplitter
由来
在Java的String对象中提供了split方法用于通过某种字符串分隔符来把一个字符串分割为数组。但是有的时候我们对这种操作有不同的要求,默认方法无法满足,这包括:
- 分割限制分割数
- 分割后每个字符串是否需要去掉两端空格
- 是否忽略空白片
- 根据固定长度分割
- 通过正则分隔
因此,StrSplitter
应运而生。StrSplitter
中全部为静态方法,方便快捷调用。
方法
基础方法
split
切分字符串,众多可选参数,返回结果为List splitToArray
切分字符串,返回结果为数组 splitsplitByRegex
根据正则切分字符串 splitByLength
根据固定长度切分字符串
栗子:
1 |
|
特殊方法
splitPath
切分字符串,分隔符为”/“ splitPathToArray
切分字符串,分隔符为”/“,返回数组。
注解工具-AnnotationUtil
介绍
封装了注解获取等方法的工具类。
使用
方法介绍
- 注解获取相关方法:
getAnnotations
获取指定类、方法、字段、构造等上的注解列表getAnnotation
获取指定类型注解getAnnotationValue
获取指定注解属性的值
例子:
我们定义一个注解:
1 |
|
给需要的类加上注解:
1 |
|
获取注解中的值:
1 |
|
- 注解属性获取相关方法:
getRetentionPolicy
获取注解类的保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为 CLASSgetTargetType
获取注解类可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等isDocumented
是否会保存到 Javadoc 文档中isInherited
是否可以被继承,默认为 false
更多方法见API文档:
https://apidoc.gitee.com/loolly/hutool/cn/hutool/core/annotation/AnnotationUtil.html
比较工具-CompareUtil
介绍
在JDK提供的比较器中,对于null
的比较没有考虑,Hutool封装了相关比较,可选null是按照最大值还是最小值对待。
1 |
|
版本比较器-VersionComparator
介绍
版本比较器用于比较版本号,支持的格式包括:
- x.x.x(1.3.20)
- x.x.yyyyMMdd(6.82.20160101)
- 带字母的版本(8.5a/8.5c)
- 带V的版本(V8.5)
使用
1 |
|
异常工具-ExceptionUtil
介绍
针对异常封装,例如包装为RuntimeException
。
方法
包装异常
假设系统抛出一个非Runtime异常,我们需要包装为Runtime异常,那么:
1 |
|
获取入口方法
1 |
|
异常转换
如果我们想把异常转换指定异常为来自或者包含指定异常,那么:
1 |
|
其他方法
getMessage
获得完整消息,包括异常名wrapRuntime
使用运行时异常包装编译异常getCausedBy
获取由指定异常类引起的异常isCausedBy
判断是否由指定异常类引起stacktraceToString
堆栈转为完整字符串
其它方法见API文档:
https://apidoc.gitee.com/dromara/hutool/cn/hutool/core/exceptions/ExceptionUtil.html
数学相关-MathUtil
介绍
此工具是NumberUtil的一个补充,NumberUtil偏向于简单数学计算的封装,MathUtil偏向复杂数学计算。
方法
- 排列
arrangementCount
计算排列数arrangementSelect
排列选择(从列表中选择n个排列)
- 组合
combinationCount
计算组合数,即C(n, m) = n!/((n-m)! * m!)combinationSelect
组合选择(从列表中选择n个组合)