工具类
概述
包含内容
此包中的工具类为未经过分类的一些工具类,提供一些常用的工具方法。
此包中根据用途归类为XXXUtil,提供大量的工具方法。在工具类中,主要以类方法(static方法)为主,且各个类无法实例化为对象,一个方法是一个独立功能,无相互影响。
关于工具类的说明和使用,请参阅下面的章节。
字符串工具-StrUtil
由来
这个工具的用处类似于Apache Commons Lang (opens new window)中的StringUtil
,之所以使用StrUtil
而不是使用StringUtil
是因为前者更短,而且Str
这个简写我想已经深入人心了,大家都知道是字符串的意思。常用的方法例如isBlank
、isNotBlank
、isEmpty
、isNotEmpty
这些我就不做介绍了,判断字符串是否为空,下面我说几个比较好用的功能。
方法
1. hasBlank
、hasEmpty
方法
就是给定一些字符串,如果一旦有空的就返回true,常用于判断好多字段是否有空的(例如web表单数据)。
这两个方法的区别是hasEmpty
只判断是否为null或者空字符串(""),hasBlank
则会把不可见字符也算做空,isEmpty
和isBlank
同理。
2. removePrefix
、removeSuffix
方法
这两个是去掉字符串的前缀后缀的,例如去个文件名的扩展名啥。
String fileName = StrUtil.removeSuffix("pretty_girl.jpg", ".jpg") //fileName -> pretty_girl
还有忽略大小写的removePrefixIgnoreCase
和removeSuffixIgnoreCase
都比较实用。
3. sub
方法
不得不提一下这个方法,有人说String有了subString你还写它干啥,我想说subString方法越界啥的都会报异常,你还得自己判断,难受死了,我把各种情况判断都加进来了,而且index的位置还支持负数哦,-1表示最后一个字符前(这个思想来自于Python,如果学过Python的应该会很喜欢的),还有就是如果不小心把第一个位置和第二个位置搞反了,也会自动修正(例如想截取第4个和第2个字符之间的部分也是可以的哦~) 举个栗子
String str = "abcdefgh";
String strSub1 = StrUtil.sub(str, 2, 3); //strSub1 -> c
String strSub2 = StrUtil.sub(str, 2, -3); //strSub2 -> cde
String strSub3 = StrUtil.sub(str, 3, 2); //strSub2 -> c
需要注意的是,-1
表示最后一个字符,但是因为sub
方法的结束index是不包含的,因此传-1
最后一个字符是取不到的:
String str = "abcdefgh";
String strSub1 = StrUtil.sub(str, 2, -1); // cdefg
如果想截取后半段,可以使用StrUtil.subSuf
方法。
4. str
、bytes
方法
好吧,我承认把String.getByte(String charsetName)
方法封装在这里了,原生的String.getByte()
这个方法太坑了,使用系统编码,经常会有人跳进来导致乱码问题,所以我就加了这两个方法强制指定字符集了,包了个try抛出一个运行时异常,省的我得在我业务代码里处理那个恶心的UnsupportedEncodingException
。
5. format方法
我会告诉你这是我最引以为豪的方法吗?灵感来自slf4j,可以使用字符串模板代替字符串拼接,我也自己实现了一个,而且变量的标识符都一样,神马叫无缝兼容~~来,上栗子(吃多了上火吧……)
String template = "{}爱{},就像老鼠爱大米";
String str = StrUtil.format(template, "我", "你"); //str -> 我爱你,就像老鼠爱大米
参数我定义成了Object类型,如果传别的类型的也可以,会自动调用toString()方法的。
6. 定义的一些常量
为了方便,我定义了一些比较常见的字符串常量在里面,像点、空串、换行符等等,还有HTML中的一些转义字符。
更多方法请参阅API文档。
16进制工具-HexUtil
介绍
十六进制(简写为hex或下标16)在数学中是一种逢16进1的进位制,一般用数字0到9和字母A到F表示(其中:A~F即10~15)。例如十进制数57,在二进制写作111001,在16进制写作39。
像java,c这样的语言为了区分十六进制和十进制数值,会在十六进制数的前面加上 0x,比如0x20是十进制的32,而不是十进制的20。HexUtil
就是将字符串或byte数组与16进制表示转换的工具类。
用于
16进制一般针对无法显示的一些二进制进行显示,常用于: 1、图片的字符串表现形式 2、加密解密 3、编码转换
使用
HexUtil
主要以encodeHex
和decodeHex
两个方法为核心,提供一些针对字符串的重载方法。
String str = "我是一个字符串";
String hex = HexUtil.encodeHexStr(str, CharsetUtil.CHARSET_UTF_8);
//hex是:
//e68891e698afe4b880e4b8aae5ad97e7aca6e4b8b2
String decodedStr = HexUtil.decodeHexStr(hex);
//解码后与str相同
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算法intHash
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://头的地址做简单补全。
String url = "http://www.hutool.cn//aaa/bbb";
// 结果为:http://www.hutool.cn/aaa/bbb
String normalize = URLUtil.normalize(url);
url = "http://www.hutool.cn//aaa/\\bbb?a=1&b=2";
// 结果为:http://www.hutool.cn/aaa/bbb?a=1&b=2
normalize = URLUtil.normalize(url);
URLUtil.encode
封装URLEncoder.encode
,将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。
String body = "366466 - 副本.jpg";
// 结果为:366466%20-%20%E5%89%AF%E6%9C%AC.jpg
String encode = URLUtil.encode(body);
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节点等信息
栗子:
<?xml version="1.0" encoding="utf-8"?>
<returnsms>
<returnstatus>Success(成功)</returnstatus>
<message>ok</message>
<remainpoint>1490</remainpoint>
<taskID>885</taskID>
<successCounts>1</successCounts>
</returnsms>
Document docResult=XmlUtil.readXML(xmlFile);
//结果为“ok”
Object value = XmlUtil.getByXPath("//returnsms/message", docResult, XPathConstants.STRING);
总结
XmlUtil只是w3c dom的简单工具化封装,减少操作dom的难度,如果项目对XML依赖较大,依旧推荐Dom4j框架。
对象工具-ObjectUtil
由来
在我们的日常使用中,有些方法是针对Object通用的,这些方法不区分何种对象,针对这些方法,Hutool封装为ObjectUtil
。
方法
默认值
借助于lambda表达式,ObjectUtil可以完成判断给定的值是否为null,不为null执行特定逻辑的功能。
final String dateStr = null;
// 此处判断如果dateStr为null,则调用`Instant.now()`,不为null则执行`DateUtil.parse`
Instant result1 = ObjectUtil.defaultIfNull(dateStr,
() -> DateUtil.parse(dateStr, DatePattern.NORM_DATETIME_PATTERN).toInstant(), Instant.now());
ObjectUtil.equal
比较两个对象是否相等,相等需满足以下条件之一:
obj1== null && obj2==null
obj1.equals(obj2)
Object a = null;
Object b = null;
// true
ObjectUtil.equals(a, b);
ObjectUtil.length
计算对象长度,如果是字符串调用其length方法,集合类调用其size方法,数组调用其length属性,其他可遍历对象遍历计算长度。
支持的类型包括:
CharSequence
Collection
Map
Iterator
Enumeration
Array
int[] array = new int[]{1,2,3,4,5};
// 5
int length = ObjectUtil.length(array);
Map<String, String> map = new HashMap<>();
map.put("a", "a1");
map.put("b", "b1");
map.put("c", "c1");
// 3
length = ObjectUtil.length(map);
ObjectUtil.contains
对象中是否包含元素。
支持的对象类型包括:
String
Collection
Map
Iterator
Enumeration
Array
int[] array = new int[]{1,2,3,4,5};
// true
final boolean contains = ObjectUtil.contains(array, 1);
判断是否为null
ObjectUtil.isNull
ObjectUtil.isNotNull
注意:此方法不能判断对象中字段为空的情况,如果需要检查Bean对象中字段是否全空,请使用
BeanUtil.isEmpty
。
克隆
ObjectUtil.clone
克隆对象,如果对象实现Cloneable接口,调用其clone方法,如果实现Serializable接口,执行深度克隆,否则返回null
。
class Obj extends CloneSupport<Obj> {
public String doSomeThing() {
return "OK";
}
}
Obj obj = new Obj();
Obj obj2 = ObjectUtil.clone(obj);
// OK
obj2.doSomeThing();
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
int a = 1;
// true
final boolean basicType = ObjectUtil.isBasicType(a);
反射工具-ReflectUtil
介绍
Java的反射机制,可以让语言变得更加灵活,对对象的操作也更加“动态”,因此在某些情况下,反射可以做到事半功倍的效果。Hutool针对Java的反射机制做了工具化封装,封装包括:
获取构造方法
获取字段
获取字段值
获取方法
执行方法(对象方法和静态方法)
使用
获取某个类的所有方法
Method[] methods = ReflectUtil.getMethods(ExamInfoDict.class);
获取某个类的指定方法
Method method = ReflectUtil.getMethod(ExamInfoDict.class, "getId");
构造对象
ReflectUtil.newInstance(ExamInfoDict.class);
执行方法
class TestClass {
private int a;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
TestClass testClass = new TestClass();
ReflectUtil.invoke(testClass, "setA", 10);
泛型类型工具-TypeUtil
介绍
针对 java.lang.reflect.Type
的工具类封装,最主要功能包括:
获取方法的参数和返回值类型(包括Type和Class)
获取泛型参数类型(包括对象的泛型参数或集合元素的泛型类型)
方法
首先我们定义一个类:
public class TestClass {
public List<String> getList(){
return new ArrayList<>();
}
public Integer intTest(Integer integer) {
return 1;
}
}
getClass
获得Type对应的原始类
getParamType
Method method = ReflectUtil.getMethod(TestClass.class, "intTest", Integer.class);
Type type = TypeUtil.getParamType(method, 0);
// 结果:Integer.class
获取方法参数的泛型类型
getReturnType
获取方法的返回值类型
Method method = ReflectUtil.getMethod(TestClass.class, "getList");
Type type = TypeUtil.getReturnType(method);
// 结果:java.util.List<java.lang.String>
getTypeArgument
获取泛型类子类中泛型的填充类型。
Method method = ReflectUtil.getMethod(TestClass.class, "getList");
Type type = TypeUtil.getReturnType(method);
Type type2 = TypeUtil.getTypeArgument(type);
// 结果:String.class
分页工具-PageUtil
由来
分页工具类并不是数据库分页的封装,而是分页方式的转换。在我们手动分页的时候,常常使用页码+每页个数的方式,但是有些数据库需要使用开始位置和结束位置来表示。很多时候这种转换容易出错(边界问题),于是封装了PageUtil工具类。
使用
transToStartEnd
将页数和每页条目数转换为开始位置和结束位置。 此方法用于包括结束位置的分页方法。
例如:
页码:0,每页10 -> [0, 10]
页码:1,每页10 -> [10, 20]
int[] startEnd1 = PageUtil.transToStartEnd(0, 10);//[0, 10]
int[] startEnd2 = PageUtil.transToStartEnd(1, 10);//[10, 20]
方法中,页码从0开始,位置从0开始
totalPage
根据总数和每页个数计算总页数
int totalPage = PageUtil.totalPage(20, 3);//7
分页彩虹算法
此方法来自:https://github.com/iceroot/iceroot/blob/master/src/main/java/com/icexxx/util/IceUtil.java
在页面上显示下一页时,常常需要显示前N页和后N页,PageUtil.rainbow
作用于此。
例如我们当前页为第5页,共有20页,只显示6个页码,显示的分页列表应为:
上一页 3 4 [5] 6 7 8 下一页
//参数意义分别为:当前页、总页数、每屏展示的页数
int[] rainbow = PageUtil.rainbow(5, 20, 6);
//结果:[3, 4, 5, 6, 7, 8]
剪贴板工具-ClipboardUtil
介绍
在Hutool群友的强烈要求下,在3.2.0+ 中新增了ClipboardUtil
这个类用于简化操作剪贴板(当然使用场景被局限)。
使用
ClipboardUtil
封装了几个常用的静态方法:
通用方法
getClipboard
获取系统剪贴板set
设置内容到剪贴板get
获取剪贴板内容
针对文本
setStr
设置文本到剪贴板getStr
从剪贴板获取文本
针对Image对象(图片)
setImage
设置图片到剪贴板getImage
从剪贴板获取图片
类工具-ClassUtil
类处理工具 ClassUtil
这个工具主要是封装了一些反射的方法,使调用更加方便。而这个类中最有用的方法是scanPackage
方法,这个方法会扫描classpath下所有类,这个在Spring中是特性之一,主要为Hulu (opens new window)框架中类扫描的一个基础。下面介绍下这个类中的方法。
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(boolean类型返回false)
非原始类型返回null
其它
更多详细的方法描述见:
https://apidoc.gitee.com/loolly/hutool/cn/hutool/core/util/ClassUtil.html
枚举工具-EnumUtil
介绍
枚举(enum)算一种“语法糖”,是指一个经过排序的、被打包成一个单一实体的项列表。一个枚举的实例可以使用枚举项列表中任意单一项的值。枚举在各个语言当中都有着广泛的应用,通常用来表示诸如颜色、方式、类别、状态等等数目有限、形式离散、表达又极为明确的量。Java从JDK5开始,引入了对枚举的支持。
EnumUtil
用于对未知枚举类型进行操作。
方法
首先我们定义一个枚举对象:
//定义枚举
public enum TestEnum{
TEST1("type1"), TEST2("type2"), TEST3("type3");
private TestEnum(String type) {
this.type = type;
}
private String type;
public String getType() {
return this.type;
}
}
getNames
获取枚举类中所有枚举对象的name列表。栗子:
List<String> names = EnumUtil.getNames(TestEnum.class);
//结果:[TEST1, TEST2, TEST3]
getFieldValues
获得枚举类中各枚举对象下指定字段的值。栗子:
List<Object> types = EnumUtil.getFieldValues(TestEnum.class, "type");
//结果:[type1, type2, type3]
getBy
根据传入lambda和值获得对应枚举。栗子🌰:
TestEnum testEnum = EnumUtil.getBy(TestEnum::ordinal, 1);
//结果:TEST2
getFieldBy
根据传入lambda和值获得对应枚举的值。栗子🌰:
String type = EnumUtil.getFieldBy(TestEnum::getType, Enum::ordinal, 1);
// 结果:“type2”
getEnumMap
获取枚举字符串值和枚举对象的Map对应,使用LinkedHashMap保证有序,结果中键为枚举名,值为枚举对象。栗子:
Map<String,TestEnum> enumMap = EnumUtil.getEnumMap(TestEnum.class);
enumMap.get("TEST1") // 结果为:TestEnum.TEST1
getNameFieldMap
获得枚举名对应指定字段值的Map,键为枚举名,值为字段值。栗子:
Map<String, Object> enumMap = EnumUtil.getNameFieldMap(TestEnum.class, "type");
enumMap.get("TEST1") // 结果为:type1
命令行工具-RuntimeUtil
介绍
在Java世界中,如果想与其它语言打交道,处理调用接口,或者JNI,就是通过本地命令方式调用了。Hutool封装了JDK的Process类,用于执行命令行命令(在Windows下是cmd,在Linux下是shell命令)。
方法
基础方法
exec
执行命令行命令,返回Process对象,Process可以读取执行命令后的返回内容的流
快捷方法
execForStr
执行系统命令,返回字符串execForLines
执行系统命令,返回行列表
使用
String str = RuntimeUtil.execForStr("ipconfig");
执行这个命令后,在Windows下可以获取网卡信息。
数字工具-NumberUtil
由来
数字工具针对数学运算做工具性封装
使用
加减乘除
NumberUtil.add
针对数字类型做加法NumberUtil.sub
针对数字类型做减法NumberUtil.mul
针对数字类型做乘法NumberUtil.div
针对数字类型做除法,并提供重载方法用于规定除不尽的情况下保留小数位数和舍弃方式。
以上四种运算都会将double转为BigDecimal后计算,解决float和double类型无法进行精确计算的问题。这些方法常用于商业计算。
保留小数
保留小数的方法主要有两种:
NumberUtil.round
方法主要封装BigDecimal中的方法来保留小数,返回BigDecimal,这个方法更加灵活,可以选择四舍五入或者全部舍弃等模式。
double te1=123456.123456;
double te2=123456.128456;
Console.log(round(te1,4));//结果:123456.1235
Console.log(round(te2,4));//结果:123456.1285
NumberUtil.roundStr
方法主要封装String.format
方法,舍弃方式采用四舍五入。
double te1=123456.123456;
double te2=123456.128456;
Console.log(roundStr(te1,2));//结果:123456.12
Console.log(roundStr(te2,2));//结果:123456.13
decimalFormat
针对 DecimalFormat.format
进行简单封装。按照固定格式对double或long类型的数字做格式化操作。
long c=299792458;//光速
String format = NumberUtil.decimalFormat(",###", c);//299,792,458
格式中主要以 # 和 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中封装了针对原始类型和泛型数组的判空和判非空:
判断空
int[] a = {};
int[] b = null;
ArrayUtil.isEmpty(a);
ArrayUtil.isEmpty(b);
判断非空
int[] a = {1,2};
ArrayUtil.isNotEmpty(a);
新建泛型数组
Array.newInstance
并不支持泛型返回值,在此封装此方法使之支持泛型返回值。
String[] newArray = ArrayUtil.newArray(String.class, 3);
调整大小
使用 ArrayUtil.resize
方法生成一个新的重新设置大小的数组。
合并数组
ArrayUtil.addAll
方法采用可变参数方式,将多个泛型数组合并为一个数组。
克隆
数组本身支持clone方法,因此确定为某种类型数组时调用ArrayUtil.clone(T[])
,不确定类型的使用ArrayUtil.clone(T)
,两种重载方法在实现上有所不同,但是在使用中并不能感知出差别。
泛型数组调用原生克隆
Integer[] b = {1,2,3};
Integer[] cloneB = ArrayUtil.clone(b);
Assert.assertArrayEquals(b, cloneB);
非泛型数组(原始类型数组)调用第二种重载方法
int[] 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值决定是否保留。
过滤数组,只保留偶数
Integer[] a = {1,2,3,4,5,6};
// [2,4,6]
Integer[] filter = ArrayUtil.filter(a, (Editor<Integer>) t -> (t % 2 == 0) ? t : null);
对已有数组编辑,获得编辑后的值。
Integer[] a = {1, 2, 3, 4, 5, 6};
// [1, 20, 3, 40, 5, 60]
Integer[] filter = ArrayUtil.filter(a, (Editor<Integer>) t -> (t % 2 == 0) ? t * 10 : t);
编辑
修改元素对象,此方法会修改原数组。
Integer[] a = {1, 2, 3, 4, 5, 6};
// [1, 20, 3, 40, 5, 60]
ArrayUtil.edit(a, t -> (t % 2 == 0) ? t * 10 : t);
zip
ArrayUtil.zip
方法传入两个数组,第一个数组为key,第二个数组对应位置为value,此方法在Python中为zip()函数。
String[] keys = {"a", "b", "c"};
Integer[] values = {1,2,3};
Map<String, Integer> map = ArrayUtil.zip(keys, values, true);
//{a=1, b=2, c=3}
是否包含元素
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)的随机数,则:
int c = RandomUtil.randomInt(10, 100);
RandomUtil.randomBytes
随机bytes,一般用于密码或者salt生成
byte[] c = RandomUtil.randomBytes(10);
RandomUtil.randomEle
随机获得列表中的元素RandomUtil.randomEleSet
随机获得列表中的一定量的不重复元素,返回LinkedHashSet
Set<Integer> set = RandomUtil.randomEleSet(CollUtil.newArrayList(1, 2, 3, 4, 5, 6), 2);
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字符串方法如下:
//生成的UUID是带-的字符串,类似于:a5c8a5e8-df2b-4706-bea4-08d0939410e3
String uuid = IdUtil.randomUUID();
//生成的是不带-的字符串,类似于:b17f24ff026d40949c85a24f4f375d42
String simpleUUID = IdUtil.simpleUUID();
说明 Hutool重写
java.util.UUID
的逻辑,对应类为cn.hutool.core.lang.UUID
,使生成不带-的UUID字符串不再需要做字符替换,性能提升一倍左右。
ObjectId
ObjectId是MongoDB数据库的一种唯一ID生成策略,是UUID version1的变种,详细介绍可见:服务化框架-分布式Unique ID的生成方法一览。
Hutool针对此封装了cn.hutool.core.lang.ObjectId
,快捷创建方法为:
//生成类似:5b9e306a4df4f8c54a39fb0c
String id = ObjectId.next();
//方法2:从Hutool-4.1.14开始提供
String id2 = IdUtil.objectId();
Snowflake
分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。Twitter的Snowflake 算法就是这种生成器。
使用方法如下:
//参数1为终端ID
//参数2为数据中心ID
Snowflake snowflake = IdUtil.getSnowflake(1, 1);
long id = snowflake.nextId();
//简单使用
long id = IdUtil.getSnowflakeNextId();
String id = IdUtil.getSnowflakeNextIdStr();
注意
IdUtil.createSnowflake
每次调用会创建一个新的Snowflake对象,不同的Snowflake对象创建的ID可能会有重复,因此请自行维护此对象为单例,或者使用IdUtil.getSnowflake
使用全局单例对象。
压缩工具-ZipUtil
由来
在Java中,对文件、文件夹打包,压缩是一件比较繁琐的事情,我们常常引入Zip4j进行此类操作。但是很多时候,JDK中的zip包就可满足我们大部分需求。ZipUtil就是针对java.util.zip
做工具化封装,使压缩解压操作可以一个方法搞定,并且自动处理文件和目录的问题,不再需要用户判断,压缩后的文件也会自动创建文件,自动创建父目录,大大简化的压缩解压的复杂度。
方法
Zip
压缩
ZipUtil.zip
方法提供一系列的重载方法,满足不同需求的压缩需求,这包括:
打包到当前目录(可以打包文件,也可以打包文件夹,根据路径自动判断)
//将aaa目录下的所有文件目录打包到d:/aaa.zip
ZipUtil.zip("d:/aaa");
指定打包后保存的目的地,自动判断目标是文件还是文件夹
//将aaa目录下的所有文件目录打包到d:/bbb/目录下的aaa.zip文件中
// 此处第二个参数必须为文件,不能为目录
ZipUtil.zip("d:/aaa", "d:/bbb/aaa.zip");
//将aaa目录下的所有文件目录打包到d:/bbb/目录下的ccc.zip文件中
ZipUtil.zip("d:/aaa", "d:/bbb/ccc.zip");
可选是否包含被打包的目录。比如我们打包一个照片的目录,打开这个压缩包有可能是带目录的,也有可能是打开压缩包直接看到的是文件。zip方法增加一个boolean参数可选这两种模式,以应对众多需求。
//将aaa目录以及其目录下的所有文件目录打包到d:/bbb/目录下的ccc.zip文件中
ZipUtil.zip("d:/aaa", "d:/bbb/ccc.zip", true);
多文件或目录压缩。可以选择多个文件或目录一起打成zip包。
ZipUtil.zip(FileUtil.file("d:/bbb/ccc.zip"), false,
FileUtil.file("d:/test1/file1.txt"),
FileUtil.file("d:/test1/file2.txt"),
FileUtil.file("d:/test2/file1.txt"),
FileUtil.file("d:/test2/file2.txt")
);
解压
ZipUtil.unzip
解压。同样提供几个重载,满足不同需求。
//将test.zip解压到e:\\aaa目录下,返回解压到的目录
File unzip = ZipUtil.unzip("E:\\aaa\\test.zip", "e:\\aaa");
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)。
//将test.zip解压到e:\\aaa目录下,返回解压到的目录
File unzip = ZipUtil.unzip("E:\\aaa\\test.zip", "e:\\aaa", CharsetUtil.CHARSET_GBK);
压缩并添加密码
Hutool或JDK的Zip工具并不支持添加密码,可以考虑使用Zip4j完成,以下代码来自Zip4j官网。
ZipParameters zipParameters = new ZipParameters();
zipParameters.setEncryptFiles(true);
zipParameters.setEncryptionMethod(EncryptionMethod.AES);
// Below line is optional. AES 256 is used by default. You can override it to use AES 128. AES 192 is supported only for extracting.
zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256);
List<File> filesToAdd = Arrays.asList(
new File("somefile"),
new File("someotherfile")
);
ZipFile zipFile = new ZipFile("filename.zip", "password".toCharArray());
zipFile.addFiles(filesToAdd, zipParameters);
引用工具-ReferenceUtil
介绍
引用工具类,主要针对Reference 工具化封装
主要封装包括:
SoftReference 软引用,在GC报告内存不足时会被GC回收
WeakReference 弱引用,在GC时发现弱引用会回收其对象
PhantomReference 虚引用,在GC时发现虚引用对象,会将PhantomReference插入ReferenceQueue。此时对象未被真正回收,要等到ReferenceQueue被真正处理后才会被回收。
方法
create
根据类型枚举创建引用。
正则工具-ReUtil
由来
在文本处理中,正则表达式几乎是全能的,但是Java的正则表达式有时候处理一些事情还是有些繁琐,所以我封装了部分常用功能。就比如说我要匹配一段文本中的某些部分,我们需要这样做:
String content = "ZZZaaabbbccc中文1234";
Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
String result= matcher.group();
}
其中牵涉到多个对象,想用的时候真心记不住。好吧,既然功能如此常用,我就封装一下:
/**
* 获得匹配的字符串
*
* @param pattern 编译后的正则模式
* @param content 被匹配的内容
* @param groupIndex 匹配正则的分组序号
* @return 匹配后得到的字符串,未匹配返回null
*/
public static String get(Pattern pattern, String content, int groupIndex) {
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
return matcher.group(groupIndex);
}
return null;
}
/**
* 获得匹配的字符串
*
* @param regex 匹配的正则
* @param content 被匹配的内容
* @param groupIndex 匹配正则的分组序号
* @return 匹配后得到的字符串,未匹配返回null
*/
public static String get(String regex, String content, int groupIndex) {
Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
return get(pattern, content, groupIndex);
}
使用
ReUtil.extractMulti
抽取多个分组然后把它们拼接起来
String content = "ZZZaaabbbccc中文1234";
String resultExtractMulti = ReUtil.extractMulti("(\\w)aa(\\w)", content, "$1-$2");
Assert.assertEquals("Z-a", resultExtractMulti);
ReUtil.delFirst
删除第一个匹配到的内容
String content = "ZZZaaabbbccc中文1234";
String resultDelFirst = ReUtil.delFirst("(\\w)aa(\\w)", content);
Assert.assertEquals("ZZbbbccc中文1234", resultDelFirst);
ReUtil.findAll
查找所有匹配文本
String content = "ZZZaaabbbccc中文1234";
List<String> resultFindAll = ReUtil.findAll("\\w{2}", content, 0, new ArrayList<String>());
// 结果:["ZZ", "Za", "aa", "bb", "bc", "cc", "12", "34"]
ReUtil.getFirstNumber
找到匹配的第一个数字
Integer resultGetFirstNumber = ReUtil.getFirstNumber(content);
// 结果:1234
ReUtil.isMatch
给定字符串是否匹配给定正则
String content = "ZZZaaabbbccc中文1234";
boolean isMatch = ReUtil.isMatch("\\w+[\u4E00-\u9FFF]+\\d+", content);
Assert.assertTrue(isMatch);
ReUtil.replaceAll
通过正则查找到字符串,然后把匹配到的字符串加入到replacementTemplate中,$1表示分组1的字符串
String content = "ZZZaaabbbccc中文1234";
//此处把1234替换为 ->1234<-
String replaceAll = ReUtil.replaceAll(content, "(\\d+)", "->$1<-");
Assert.assertEquals("ZZZaaabbbccc中文->1234<-", replaceAll);
ReUtil.escape
转义给定字符串,为正则相关的特殊符号转义
String escape = ReUtil.escape("我有个$符号{}");
// 结果:我有个\\$符号\\{\\}
身份证工具-IdcardUtil
由来
在日常开发中,我们对身份证的验证主要是正则方式(位数,数字范围等),但是中国身份证,尤其18位身份证每一位都有严格规定,并且最后一位为校验位。而我们在实际应用中,针对身份证的验证理应严格至此。于是IdcardUtil
应运而生。
IdcardUtil
从3.0.4版本起加入Hutool工具家族,升级至此版本以上可使用。
介绍
IdcardUtil
现在支持大陆15位、18位身份证,港澳台10位身份证。
工具中主要的方法包括:
isValidCard
验证身份证是否合法convert15To18
身份证15位转18位getBirthByIdCard
获取生日getAgeByIdCard
获取年龄getYearByIdCard
获取生日年getMonthByIdCard
获取生日月getDayByIdCard
获取生日天getGenderByIdCard
获取性别getProvinceByIdCard
获取省份
使用
String ID_18 = "321083197812162119";
String ID_15 = "150102880730303";
//是否有效
boolean valid = IdcardUtil.isValidCard(ID_18);
boolean valid15 = IdcardUtil.isValidCard(ID_15);
//转换
String convert15To18 = IdcardUtil.convert15To18(ID_15);
Assert.assertEquals(convert15To18, "150102198807303035");
//年龄
DateTime date = DateUtil.parse("2017-04-10");
int age = IdcardUtil.getAgeByIdCard(ID_18, date);
Assert.assertEquals(age, 38);
int age2 = IdcardUtil.getAgeByIdCard(ID_15, date);
Assert.assertEquals(age2, 28);
//生日
String birth = IdcardUtil.getBirthByIdCard(ID_18);
Assert.assertEquals(birth, "19781216");
String birth2 = IdcardUtil.getBirthByIdCard(ID_15);
Assert.assertEquals(birth2, "19880730");
//省份
String province = IdcardUtil.getProvinceByIdCard(ID_18);
Assert.assertEquals(province, "江苏");
String province2 = IdcardUtil.getProvinceByIdCard(ID_15);
Assert.assertEquals(province2, "内蒙古");
声明 以上两个身份证号码为随机编造的,如有雷同,纯属巧合。
信息脱敏工具-DesensitizedUtil
介绍
在数据处理或清洗中,可能涉及到很多隐私信息的脱敏工作,因此Hutool针对常用的信息封装了一些脱敏方法。
现阶段支持的脱敏数据类型包括:
用户id
中文姓名
身份证号
座机号
手机号
地址
电子邮件
密码
中国大陆车牌,包含普通车辆、新能源车辆
银行卡
整体来说,所谓脱敏就是隐藏掉信息中的一部分关键信息,用*
代替,自定义隐藏可以使用StrUtil.hide
方法完成。
使用
我们以身份证号码为例:
// 5***************1X
DesensitizedUtil.idCardNum("51343620000320711X", 1, 2);
对于约定俗成的脱敏,我们可以不用指定隐藏位数,比如手机号:
// 180****1999
DesensitizedUtil.mobilePhone("18049531999");
当然还有一些简单粗暴的脱敏,比如密码,只保留了位数信息:
// **********
DesensitizedUtil.password("1234567890");
鸣谢 此工具类来自于
dazer and neusoft and qiaomu
贡献,看着像三个人……
社会信用代码工具-CreditCodeUtil
介绍
法人和其他组织统一社会信用代码制度,相当于让法人和其他组织拥有了一个全国统一的“身份证号”。
规则如下:
第一部分:登记管理部门代码1位 (数字或大写英文字母)
第二部分:机构类别代码1位 (数字或大写英文字母)
第三部分:登记管理机关行政区划码6位 (数字)
第四部分:主体标识码(组织机构代码)9位 (数字或大写英文字母)
第五部分:校验码1位 (数字或大写英文字母)
此工具主要提供校验和随机生成。
使用
校验
String testCreditCode = "91310110666007217T";
// true
CreditCodeUtil.isCreditCode(testCreditCode);
随机社会信用代码
final String s = CreditCodeUtil.randomCreditCode();
SPI加载工具-ServiceLoaderUtil
介绍
SPI(Service Provider Interface),是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
更多介绍见:https://www.jianshu.com/p/3a3edbcd8f24
使用
定义一个接口:
package cn.hutool.test.spi;
public interface SPIService {
void execute();
}
有两个实现:
package cn.hutool.test.spi;
public class SpiImpl1 implements SPIService{
public void execute() {
Console.log("SpiImpl1.execute()");
}
}
package cn.hutool.test.spi;
public class SpiImpl2 implements SPIService{
public void execute() {
Console.log("SpiImpl2.execute()");
}
}
然后在classpath的META-INF/services
下创建一个文件,叫cn.hutool.test.spi.SPIService
,内容为:
cn.hutool.test.spi.SpiImpl1
cn.hutool.test.spi.SpiImpl2
加载第一个可用服务,如果用户定义了多个接口实现类,只获取第一个不报错的服务。这个方法多用于同一接口多种实现的自动甄别加载, 通过判断jar是否引入,自动找到实现类。
SPIService service = ServiceLoaderUtil.loadFirstAvailable(SPIService.class);
service.execute();
字符编码工具-CharsetUtil
介绍
CharsetUtil主要针对编码操作做了工具化封装,同时提供了一些常用编码常量。
常量
常量在需要编码的地方直接引用,可以很好的提高便利性。
字符串形式
ISO_8859_1
UTF_8
GBK
Charset对象形式
CHARSET_ISO_8859_1
CHARSET_UTF_8
CHARSET_GBK
方法
编码字符串转为Charset对象
CharsetUtil.charset
方法用于将编码形式字符串转为Charset对象。
转换编码
CharsetUtil.convert
方法主要是在两种编码中转换。主要针对因为编码识别错误而导致的乱码问题的一种解决方法。
系统默认编码
CharsetUtil.defaultCharset
方法是Charset.defaultCharset()
的封装方法。返回系统编码。 CharsetUtil.defaultCharsetName
方法返回字符串形式的编码类型。
类加载工具-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。
加载失败的原因可能是此类不存在或其关联引用类不存在。
语言特性
概述
介绍
语言特性,即lang包,主要针对JDK中的一些数据结构和接口的完善,包括:
caller 获取方法调用者
copier 复制器抽象接口
func 函数接口
hash 哈希算法
loader 加载器抽象接口
mutable 提供可变对象
tree 提供树状结构
其它
总之,lang包下提供了一个大杂烩,汇集了各种数据结构。
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子类,做为数据的媒介。
使用
创建
Dict dict = Dict.create()
.set("key1", 1)//int
.set("key2", 1000L)//long
.set("key3", DateTime.now());//Date
通过链式构造,创建Dict对象,同时可以按照Map的方式使用。
获取指定类型的值
Long v2 = dict.getLong("key2");//1000
单例工具-Singleton
为什么会有这个类
平常我们使用单例不外乎两种方式:
在对象里加个静态方法getInstance()来获取。此方式可以参考 【转】线程安全的单例模式这篇博客,可分为饿汉和饱汉模式。
通过Spring这类容器统一管理对象,用的时候去对象池中拿。Spring也可以通过配置决定懒汉或者饿汉模式
说实话我更倾向于第二种,但是Spring更注重的是注入,而不是拿,于是我想做Singleton这个类,维护一个单例的池,用这个单例对象的时候直接来拿就可以,这里我用的懒汉模式。我只是想把单例的管理方式换一种思路,我希望管理单例的是一个容器工具,而不是一个大大的框架,这样能大大减少单例使用的复杂性。
使用
/**
* 单例样例
* @author loolly
*
*/
public class SingletonDemo {
/**
* 动物接口
* @author loolly
*
*/
public static interface Animal{
public void say();
}
/**
* 狗实现
* @author loolly
*
*/
public static class Dog implements Animal{
@Override
public void say() {
System.out.println("汪汪");
}
}
/**
* 猫实现
* @author loolly
*
*/
public static class Cat implements Animal{
@Override
public void say() {
System.out.println("喵喵");
}
}
public static void main(String[] args) {
Animal dog = Singleton.get(Dog.class);
Animal cat = Singleton.get(Cat.class);
//单例对象每次取出为同一个对象,除非调用Singleton.destroy()或者remove方法
System.out.println(dog == Singleton.get(Dog.class)); //True
System.out.println(cat == Singleton.get(Cat.class)); //True
dog.say(); //汪汪
cat.say(); //喵喵
}
}
总结
大家如果有兴趣可以看下这个类,实现非常简单,用一个安全的ConcurrentHashMap
作为单例对象池,通过newInstance()实例化对象(支持带参数的构造方法),无论取还是创建对象都是线程安全的。
断言-Assert
由来
Java中有assert
关键字,但是存在许多问题:
assert关键字需要在运行时显式开启才能生效,否则你的断言就没有任何意义。
用assert代替if是陷阱之二。assert的判断和if语句差不多,但两者的作用有着本质的区别:assert关键字本意上是为测试调试程序时使用的,但如果不小心用assert来控制了程序的业务流程,那在测试调试结束后去掉assert关键字就意味着修改了程序的正常的逻辑。
assert断言失败将面临程序的退出。
因此,并不建议使用此关键字。相应的,在Hutool中封装了更加友好的Assert类,用于断言判定。
介绍
Assert类更像是Junit中的Assert类,也很像Guava中的Preconditions,主要作用是在方法或者任何地方对参数的有效性做校验。当不满足断言条件时,会抛出IllegalArgumentException或IllegalStateException异常。
使用
String a = null;
cn.hutool.lang.Assert.isNull(a);
更多方法
isTrue 必须为true,否则抛出IllegalArgumentException异常
isNull 必须是null值
notNull 不能是null值
notEmpty 不能为空,支持字符串,数组,集合等
notBlank 不能是空白字符串
notContain 不能包含指定的子串
noNullElements 数组中不能包含null元素
isInstanceOf 必须是指定类的实例
isAssignable 必须是子类和父类关系
state 会抛出IllegalStateException异常
二进码十进数-BCD
介绍
BCD码(Binary-Coded Decimal)亦称二进码十进数或二-十进制代码,BCD码这种编码形式利用了四个位元来储存一个十进制的数码,使二进制和十进制之间的转换得以快捷的进行。
这种编码技巧最常用于会计系统的设计里,因为会计制度经常需要对很长的数字串作准确的计算。相对于一般的浮点式记数法,采用BCD码,既可保存数值的精确度,又可免却电脑作浮点运算时所耗费的时间。此外,对于其他需要高精确度的计算,BCD编码亦很常用。
BCD码是四位二进制码, 也就是将十进制的数字转化为二进制, 但是和普通的转化有一点不同, 每一个十进制的数字0-9都对应着一个四位的二进制码,对应关系如下: 十进制0
对应 二进制0000
;十进制1
对应二进制0001
.......9
=>1001
接下来的10
就由两个上述的码来表示,表示为0001 0000
。也就是BCD码是遇见1001
就产生进位,不象普通的二进制码,到1111
才产生进位10000
方法
String strForTest = "123456ABCDEF";
//转BCD
byte[] bcd = BCD.strToBcd(strForTest);
//解码BCD
String str = BCD.bcdToStr(bcd);
Assert.assertEquals(strForTest, str);
注意
由于对ASCII的编码解码有缺陷,且这种BCD实现并不规范,因此会在6.0.0中移除
控制台打印封装-Console
由来
编码中我们常常需要调试输出一些信息,除了打印日志,最长用的要数System.out
和System.err
比如我们打印一个Hello World,可以这样写:
System.out.println("Hello World");
但是面对纷杂的打印需求,System.out.println
无法满足,比如:
不支持参数,对象打印需要拼接字符串
不能直接打印数组,需要手动调用
Arrays.toString
考虑到以上问题,我封装了Console
对象。
Console对象的使用更加类似于Javascript的
console.log()
方法,这也是借鉴了JS的一个语法糖。
使用
Console.log
这个方法基本等同于System.out.println
,但是支持类似于Slf4j的字符串模板语法,同时也会自动将对象(包括数组)转为字符串形式。
String[] a = {"abc", "bcd", "def"};
Console.log(a);//控制台输出:[abc, bcd, def]
Console.log("This is Console log for {}.", "test");
//控制台输出:This is Console log for test.
Console.error
这个方法基本等同于System.err.println
,但是支持类似于Slf4j的字符串模板语法,同时也会自动将对象(包括数组)转为字符串形式。
字段验证器-Validator
作用
验证给定字符串是否满足指定条件,一般用在表单字段验证里。
此类中全部为静态方法。
使用
判断验证
直接调用Validator.isXXX(String value)
既可验证字段,返回是否通过验证。
例如:
boolean isEmail = Validator.isEmail("loolly@gmail.com")
表示验证给定字符串是否复合电子邮件格式。
其他验证信息请参阅Validator
类
如果Validator里的方法无法满足自己的需求,那还可以调用
Validator.isMatchRegex("需要验证字段的正则表达式", "被验证内容")
通过正则表达式来灵活的验证内容。
异常验证
除了手动判断,我们有时需要在判断未满足条件时抛出一个异常,Validator同样提供异常验证机制:
Validator.validateChinese("我是一段zhongwen", "内容中包含非中文");
因为内容中包含非中文字符,因此会抛出ValidateException。
字符串格式化-StrFormatter
由来
我一直对Slf4j的字符串格式化情有独钟,通过{}
这种简单的占位符完成字符串的格式化。于是参考Slf4j的源码,便有了StrFormatter
。
StrFormatter.format的快捷使用方式为
StrUtil.format
,推荐使用后者。
使用
//通常使用
String result1 = StrFormatter.format("this is {} for {}", "a", "b");
Assert.assertEquals("this is a for b", result1);
//转义{}
String result2 = StrFormatter.format("this is \\{} for {}", "a", "b");
Assert.assertEquals("this is {} for a", result2);
//转义\
String result3 = StrFormatter.format("this is \\\\{} for {}", "a", "b");
Assert.assertEquals("this is \\a for b", result3);
树结构
树结构工具-TreeUtil
介绍
考虑到菜单等需求的普遍性,有用户提交了一个扩展性极好的树状结构实现。这种树状结构可以根据配置文件灵活的定义节点之间的关系,也能很好的兼容关系数据库中数据。实现树状结构中最大的问题就是关系问题,在数据库中,每条数据通过某个字段关联自己的父节点,每个业务中这个字段的名字都不同,如何解决这个问题呢?
关系型数据库数据 <-> Tree <-> JSON
PR的提供者提供了一种解决思路:自定义字段名,节点不再是一个bean,而是一个map,实现灵活的字段名定义。
使用
定义结构
我们假设要构建一个菜单,可以实现系统管理和店铺管理,菜单的样子如下:
系统管理
|- 用户管理
|- 添加用户
店铺管理
|- 商品管理
|- 添加商品
那这种结构如何保存在数据库中呢?一般是这样的:
我们看到,每条数据根据parentId
相互关联并表示层级关系,parentId
在这里也叫外键。
构建Tree
// 构建node列表
List<TreeNode<String>> nodeList = CollUtil.newArrayList();
nodeList.add(new TreeNode<>("1", "0", "系统管理", 5));
nodeList.add(new TreeNode<>("11", "1", "用户管理", 222222));
nodeList.add(new TreeNode<>("111", "11", "用户添加", 0));
nodeList.add(new TreeNode<>("2", "0", "店铺管理", 1));
nodeList.add(new TreeNode<>("21", "2", "商品管理", 44));
nodeList.add(new TreeNode<>("221", "2", "商品管理2", 2));
TreeNode表示一个抽象的节点,也表示数据库中一行数据。 如果有其它数据,可以调用
setExtra
添加扩展字段。
// 0表示最顶层的id是0
List<Tree<String>> treeList = TreeUtil.build(nodeList, "0");
因为两个Tree是平级的,再没有上层节点,因此为List。
自定义字段名
//配置
TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
// 自定义属性名 都有默认值的
treeNodeConfig.setWeightKey("order");
treeNodeConfig.setIdKey("rid");
// 最大递归深度
treeNodeConfig.setDeep(3);
//转换器 (含义:找出父节点为字符串零的所有子节点, 并递归查找对应的子节点, 深度最多为 3)
List<Tree<String>> treeNodes = TreeUtil.<TreeNode, String>build(nodeList, "0", treeNodeConfig,
(treeNode, tree) -> {
tree.setId(treeNode.getId());
tree.setParentId(treeNode.getParentId());
tree.setWeight(treeNode.getWeight());
tree.setName(treeNode.getName());
// 扩展属性 ...
tree.putExtra("extraField", 666);
tree.putExtra("other", new Object());
});
通过TreeNodeConfig我们可以自定义节点的名称、关系节点id名称,这样就可以和不同的数据库做对应。