JavaBean
概述
针对JavaBean已经有BeanUtil的工具封装,我认为这还不够。最近看了Apache Commons BeanUtils的DynaBean源码和Nuts中Mirror类的文档(请看这里 -> 增强反射 ),启发颇多,于是我决定在Hutool中加入DynaBean。
Dyna即Dynamic,顾名思义,通过Java反射机制操作JavaBean,以达到动态语言的某些特性。
bean包的另一个改进是针对PropertyDescriptor提供缓存。BeanInfoCache类缓存了通过内省获取到的PropertyDescriptor,以提高反射性能。
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方法用于属性注入。
boolean isBean = BeanUtil.isBean(HashMap.class);//false
内省 Introspector
把一个类中需要进行设置和获得的属性的访问权限设置为private(私有的),让外部的使用者看不见摸不着,只能通过public(共有的)set和get方法对其属性的值进行设置和获得,而内部的操作具体是怎样的?外界使用的人不用知道,这就称为内省。
Hutool中对内省的封装包括:
BeanUtil.getPropertyDescriptors
获得Bean字段描述数组
PropertyDescriptor[] propertyDescriptors = BeanUtil.getPropertyDescriptors(SubPerson.class);
BeanUtil.getFieldNamePropertyDescriptorMap
获得字段名和字段描述MapBeanUtil.getPropertyDescriptor
获得指定字段的描述
Bean属性注入
BeanUtil.fillBean
方法是bean注入的核心方法,此方法传入一个ValueProvider接口,通过实现此接口来获得key对应的值。CopyOptions参数则提供一些注入属性的选项。
CopyOptions的配置项包括:
editable
限制拷贝的属性必须为指定类中的属性,例如我只想从源对象中拷贝目标对象父类中的属性,就可以将editable设置为目标对象父类的Class。ignoreNullValue
是否忽略空值,当源对象的值为null时,true: 忽略而不注入此值,false: 注入nullignoreProperties
忽略的属性列表,设置一个属性列表,不拷贝这些属性值ignoreError
是否忽略字段注入中发生的错误
可以通过CopyOptions.create()
方法创建一个默认的配置项,通过setXXX方法设置每个配置项。
ValueProvider接口需要实现两个方法:
value
方法是通过key和目标类型来从任何地方获取一个值,并转换为目标类型,如果返回值不和目标类型匹配,将会自动调用Convert.convert
方法转换。containsKey
方法主要是检测是否包含指定的key,如果不包含这个key,其对应的属性将会忽略注入。
首先定义两个bean:
// Lombok注解
@Data
public class Person {
private String name;
private int age;
}
// Lombok注解
@Data
public class SubPerson extends Person {
public static final String SUBNAME = "TEST";
private UUID id;
private String subName;
private Boolean isSlow;
}
然后注入这个bean:
Person person = BeanUtil.fillBean(new Person(), new ValueProvider<String>() {
@Override
public Object value(String key, Class<?> valueType) {
switch (key) {
case "name":
return "张三";
case "age":
return 18;
}
return null;
}
@Override
public boolean containsKey(String key) {
//总是存在key
return true;
}
}, CopyOptions.create());
Assert.assertEquals(person.getName(), "张三");
Assert.assertEquals(person.getAge(), 18);
基于BeanUtil.fillBean
方法Hutool还提供了Map对象键值对注入Bean,其方法有:
BeanUtil.fillBeanWithMap
使用Map填充bean
HashMap<String, Object> map = CollUtil.newHashMap();
map.put("name","Joe");
map.put("age",12);
map.put("openId","DFDFSDFWERWER");
SubPerson person = BeanUtil.fillBeanWithMap(map, new SubPerson(), false);
BeanUtil.fillBeanWithMapIgnoreCase
使用Map填充bean,忽略大小写
HashMap<String, Object> map = CollUtil.newHashMap();
map.put("Name","Joe");
map.put("aGe",12);
map.put("openId","DFDFSDFWERWER");
SubPerson person = BeanUtil.fillBeanWithMapIgnoreCase(map, new SubPerson(), false);
同时,Hutool还提供了BeanUtil.toBean
方法,用于map转bean,与fillBean不同的是,此处并不是传Bean对象,而是Bean类,Hutool会自动调用默认构造方法创建对象。当然,前提是Bean类有默认构造方法(空构造),这些方法有:
BeanUtil.toBean
HashMap<String, Object> map = CollUtil.newHashMap();
map.put("a_name","Joe");
map.put("b_age",12);
// 设置别名,用于对应bean的字段名
HashMap<String, String> mapping = CollUtil.newHashMap();
mapping.put("a_name","name");
mapping.put("b_age","age");
Person person = BeanUtil.toBean(map, Person.class, CopyOptions.create().setFieldMapping(mapping));
BeanUtil.toBeanIgnoreCase
HashMap<String, Object> map = CollUtil.newHashMap();
map.put("Name","Joe");
map.put("aGe",12);
Person person = BeanUtil.toBeanIgnoreCase(map, Person.class, false);
Bean转为Map
BeanUtil.beanToMap
方法则是将一个Bean对象转为Map对象。
SubPerson person = new SubPerson();
person.setAge(14);
person.setOpenid("11213232");
person.setName("测试A11");
person.setSubName("sub名字");
Map<String, Object> map = BeanUtil.beanToMap(person);
Bean转Bean
Bean之间的转换主要是相同属性的复制,因此方法名为copyProperties
,此方法支持Bean和Map之间的字段复制。
BeanUtil.copyProperties
方法同样提供一个CopyOptions
参数用于自定义属性复制。
SubPerson p1 = new SubPerson();
p1.setSlow(true);
p1.setName("测试");
p1.setSubName("sub测试");
Map<String, Object> map = MapUtil.newHashMap();
BeanUtil.copyProperties(p1, map);
5.6.6+加入 copyToList
方法,遍历集合中每个Bean,复制其属性到另一个类型的对象中,最后返回一个新的List。
List<Student> studentList = new ArrayList<>();
Student student = new Student();
student.setName("张三");
student.setAge(123);
student.setNo(3158L);
studentList.add(student);
Student student2 = new Student();
student.setName("李四");
student.setAge(125);
student.setNo(8848L);
studentList.add(student2);
// 复制到 Person 类
List<Person> people = BeanUtil.copyToList(studentList, Person.class);
Alias注解
5.x的Hutool中增加了一个自定义注解:@Alias
,通过此注解可以给Bean的字段设置别名。
首先我们给Bean加上注解:
// Lombok注解
@Getter
@Setter
public static class SubPersonWithAlias {
@Alias("aliasSubName")
private String subName;
private Boolean slow;
SubPersonWithAlias person = new SubPersonWithAlias();
person.setSubName("sub名字");
person.setSlow(true);
// Bean转换为Map时,自动将subName修改为aliasSubName
Map<String, Object> map = BeanUtil.beanToMap(person);
// 返回"sub名字"
map.get("aliasSubName")
同样Alias注解支持注入Bean时使用别名:
Map<String, Object> map = MapUtil.newHashMap();
map.put("aliasSubName","sub名字");
map.put("slow",true);
SubPersonWithAlias subPersonWithAlias = BeanUtil.mapToBean(map, SubPersonWithAlias.class, false);
// 返回"sub名字"
subPersonWithAlias.getSubName();
DynaBean
介绍
DynaBean是使用反射机制动态操作JavaBean的一个封装类,通过这个类,可以通过传入字段名称的方式动态调用get和set方法,也可以动态创建JavaBean对象,亦或者执行JavaBean中的方法。
使用
我们先定义一个JavaBean:
// Lombok注解
@Data
public static class User{
private String name;
private int age;
public String testMethod(){
return "test for " + this.name;
}
}
创建
DynaBean bean = DynaBean.create(user);
//我们也可以通过反射构造对象
DynaBean bean2 = DynaBean.create(User.class);
操作
我们通过DynaBean来包装并操作这个Bean
User user = new User();
DynaBean bean = DynaBean.create(user);
bean.set("name", "李华");
bean.set("age", 12);
String name = bean.get("name");//输出“李华”
这样我们就可以像操作Map一样动态操作JavaBean
invoke
除了标准的get和set方法,也可以调用invoke方法执行对象中的任意方法:
//执行指定方法
Object invoke = bean2.invoke("testMethod");
Assert.assertEquals("test for 李华", invoke);
说明: 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)。
//------------------------------------------------- 考试信息列表
ExamInfoDict examInfoDict = new ExamInfoDict();
examInfoDict.setId(1);
examInfoDict.setExamType(0);
examInfoDict.setAnswerIs(1);
ExamInfoDict examInfoDict1 = new ExamInfoDict();
examInfoDict1.setId(2);
examInfoDict1.setExamType(0);
examInfoDict1.setAnswerIs(0);
ExamInfoDict examInfoDict2 = new ExamInfoDict();
examInfoDict2.setId(3);
examInfoDict2.setExamType(1);
examInfoDict2.setAnswerIs(0);
List<ExamInfoDict> examInfoDicts = new ArrayList<ExamInfoDict>();
examInfoDicts.add(examInfoDict);
examInfoDicts.add(examInfoDict1);
examInfoDicts.add(examInfoDict2);
//------------------------------------------------- 用户信息
UserInfoDict userInfoDict = new UserInfoDict();
userInfoDict.setId(1);
userInfoDict.setPhotoPath("yx.mm.com");
userInfoDict.setRealName("张三");
userInfoDict.setExamInfoDict(examInfoDicts);
Map<String, Object> tempMap = new HashMap<String, Object>();
tempMap.put("userInfo", userInfoDict);
tempMap.put("flag", 1);
下面,我们使用BeanPath
获取这个Map下此用户第一门考试的ID:
BeanPath resolver = new BeanPath("userInfo.examInfoDict[0].id");
Object result = resolver.get(tempMap);//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:
public static class User {
private String name;
private int age;
private boolean isAdmin;
private boolean isSuper;
private boolean gender;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public User setAge(int age) {
this.age = age;
return this;
}
public String testMethod() {
return "test for " + this.name;
}
public boolean isAdmin() {
return isAdmin;
}
public void setAdmin(boolean isAdmin) {
this.isAdmin = isAdmin;
}
public boolean isIsSuper() {
return isSuper;
}
public void setIsSuper(boolean isSuper) {
this.isSuper = isSuper;
}
public boolean isGender() {
return gender;
}
public void setGender(boolean gender) {
this.gender = gender;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + ", isAdmin=" + isAdmin + ", gender=" + gender + "]";
}
}
字段getter方法获取
一般字段
BeanDesc desc = BeanUtil.getBeanDesc(User.class);
// User
desc.getSimpleName();
// age
desc.getField("age").getName();
// getAge
desc.getGetter("age").getName();
// setAge
desc.getSetter("age").getName();
Boolean字段
我们会发现User
中的boolean字段叫做isAdmin
,此时同名的getter也可以获取到:
BeanDesc desc = BeanUtil.getBeanDesc(User.class);
// isAdmin
desc.getGetter("isAdmin").getName();
当然,用户如果觉得isIsXXX
才是正确的,BeanDesc
也可以完美获取,我们以isSuper
字段为例:
// isIsSuper
desc.getGetter("isSuper");
字段属性赋值
BeanDesc desc = BeanUtil.getBeanDesc(User.class);
User user = new User();
desc.getProp("name").setValue(user, "张三");
空检查属性获取-Opt
介绍
在嵌套对象的属性获取中,由于子对象无法得知是否为null
,每次获取属性都要检查属性对象是否为null,使得代码会变得特备臃肿,因此使用Opt
来优雅的链式获取属性对象值。
声明:此类的作者:阿超 ,PR来自:https://gitee.com/dromara/hutool/pulls/426
使用
我们先定义一个嵌套的Bean:
// Lombok注解
@Data
public static class User {
private String name;
private String gender;
private School school;
@Data
public static class School {
private String name;
private String address;
}
}
假设我们想获取address
属性,则:
User user = new User();
user.setName("hello");
// null
String addressValue = Opt.ofNullable(user)
.map(User::getSchool)
.map(User.School::getAddress).get();
由于school对象的值为null
,一般直接获取会报空指针,使用Opt
即可避免判断。
ofBlankAble函数基于ofNullable的逻辑下,额外进行了空字符串判断
// ofBlankAble相对于ofNullable考虑了字符串为空串的情况
String hutool = Opt.ofBlankAble("").orElse("hutool");
Assert.equals("hutool", hutool);
和原版
Optional
有区别的是,Opt.get()
不会抛出NoSuchElementException如果想像原版Optional中的get那样,获取一个一定不为空的值,则应该使用orElseThrow
Object opt = Opt.ofNullable(null).get();
Assert.isNull(isEmpty);
这是参考了jdk11 Optional中的新函数isEmpty,用于判断不存在值的情况
// 判断包裹内元素是否为空,注意并没有判断空字符串的情况
boolean isEmpty = Opt.empty().isEmpty();
Assert.isTrue(isEmpty);
灵感来源于jdk9 Optional中的新函数ifPresentOrElse,用于 存在值时执行某些操作,不存在值时执行另一个操作,支持链式编程
// 存在就打印对应的值,不存在则用System.err.println打印另一句字符串
Opt.ofNullable("Hello Hutool!").ifPresentOrElse(System.out::println, () -> System.err.println("Ops!Something is wrong!"));
Opt.empty().ifPresentOrElse(System.out::println, () -> System.err.println("Ops!Something is wrong!"));
新增了peek函数,相当于ifPresent的链式调用(个人常用)
User user = new User();
// 相当于ifPresent的链式调用
Opt.ofNullable("hutool").peek(user::setUsername).peek(user::setNickname);
Assert.equals("hutool", user.getNickname());
Assert.equals("hutool", user.getUsername());
// 注意,传入的lambda中,对包裹内的元素执行赋值操作并不会影响到原来的元素
String name = Opt.ofNullable("hutool").peek(username -> username = "123").peek(username -> username = "456").get();
Assert.equals("hutool", name);
灵感来源于jdk11 Optional中的新函数or,用于值不存在时,用别的Opt代替
// 给一个替代的Opt
String str = Opt.<String>ofNullable(null).or(() -> Opt.ofNullable("Hello hutool!")).map(String::toUpperCase).orElseThrow();
Assert.equals("HELLO HUTOOL!", str);
User user = User.builder().username("hutool").build();
Opt<User> userOpt = Opt.of(user);
// 获取昵称,获取不到则获取用户名
String name = userOpt.map(User::getNickname).or(() -> userOpt.map(User::getUsername)).get();
Assert.equals("hutool", name);
对orElseThrow进行了重载,支持 双冒号+自定义提示语 写法,比原来的更加优雅。
原来的写法:
orElseThrow(() -> new IllegalStateException("Ops!Something is wrong!"))
修改后写法:
orElseThrow(IllegalStateException::new, "Ops!Something is wrong!")
学习:
经常有朋友问我,你这个Opt
,参数怎么都是一些lambda
,我怎么知道对应的lambda
怎么写呢?
这函数式编程,真是一件美事啊~
对于这种情况,我们依靠我们强大的idea
即可
例如此处我写到这里不会写了
User user = new User();
Opt.ofNullable(user).map(|光标停在此处)
// idea会提示方法参数,如果没显示,光标放到括号里按ctrl+p主动呼出
|Function<? super User,?> mapper|
这里idea
为我们提示了参数类型,可这个Function
我也不知道它是个什么
实际上,我们new
一个就好了
Opt.ofNullable(user).map(new Fun)
|Function<User, Object>{...} (java.util.function) | <-戳我
|Func<P,R> cn.hutool.core.lang.func |
这里idea
提示了剩下的代码,我们选Function
就行了,接下来如下:
Opt.ofNullable(user).map(new Function<User, Object>() {
})
此处开始编译报错了,不要着急,我们这里根据具体操作选取返回值
例如我这里是想判断user
是否为空,不为空时调用getSchool
,从而获取其中的返回值String
类型的school
我们就如下写法,将第二个泛型,也就是象征返回值的泛型改为String
:
Opt.ofNullable(user).map(new Function<User, String>() {
})
然后我们使用idea
的修复所有,默认快捷键alt
+回车
Opt.ofNullable(user).map(new Function<User, String>() {
}) | 💡 Implement methods | <-选我
| ✍ Introduce local variable |
| ↩ Rollback changes in current line |
选择第一个Implement methods
即可,这时候弹出一个框,提示让你选择你想要实现的方法
这里就选择我们的apply
方法吧,按下一个回车就可以了,或者点击选中apply
,再按一下OK
按钮
||IJ| Select Methods to Implement X |
| |
| 👇 © | ↹ ↸ |
| -------------------------------------------------------- |
| | java.util.function.Function |
| | ⒨ 🔓 apply(t:T):R | <-选我选我
| | ⒨ 🔓 compose(before:Function<? super V,? extents T):Fu|
| | ⒨ 🔓 andThen(after:Function<? super R,? extends V>):Fu|
| | |
| | ======================================== |
| -------------------------------------------------------- |
| ☐ Copy JavaDoc |
| ☑ Insert @Override | OK | | CANCEL | | <-选完点我点我
此时此刻,代码变成了这样子
Opt.ofNullable(user).map(new Function<User, String>() {
@Override
public String apply(User user) {
return null;
}
})
这里重写的方法里面就写你自己的逻辑(别忘了补全最后小括号后面的分号)
Opt.ofNullable(user).map(new Function<User, String>() {
@Override
public String apply(User user) {
return user.getSchool();
}
});
我们可以看到,上边的new Function<User, String>()
变成了灰色
我们让光标移动到灰色字符上面,按一下alt
+enter
(回车)
Opt.ofNullable(user).map(new Function<User, String>() {
@Override | 💡 Replace with lambda > | <-选我啦
public String apply(User user) { | 💡 Replace with method reference > |
return user.getSchool(); | 💎 balabala... > |
}
});
选择第一个Replace with lambda
,就会自动缩写为lambda
啦
Opt.ofNullable(user).map(user1 -> user1.getSchool());
如果选择第二个,则会缩写为我们双冒号格式
Opt.ofNullable(user).map(User::getSchool);
看,是不是很简单!
集合类
概述
介绍
Iterator相关帮助类
集合包中封装了包括Enumeration、Iterator等的包装,这包括:
ArrayIterator
数组Iterator,便于数组利用Iterator方式遍历CopiedIterator
为了解决并发情况下Iterator遍历导致的问题而封装的IteratorEnumerationIterator
Enumeration的Iterator表现形式IteratorEnumeration
Iterator的Enumeration表现形式
同时提供了IterUtil
和CollUtil
工具类用于简化对Iterator
和集合的操作。
集合工具-CollUtil
介绍
这个工具主要增加了对数组、集合类的操作。
join
方法
将集合转换为字符串,这个方法还是挺常用的,是StrUtil.split
的反方法。这个方法的参数支持各种类型对象的集合,最后连接每个对象的时候调用其toString()
方法。栗子如下:
String[] col= new String[]{"a","b","c","d","e"};
List<String> colList = CollUtil.newArrayList(col);
String str = CollUtil.join(colList, "#"); //str -> a#b#c#d#e
sortPageAll
方法
这个方法其实是一个组合方法,功能是:将给定的多个集合放到一个列表(List
)中,根据给定的Comparator
对象排序,然后分页取数据。这个方法非常类似于数据库多表查询后排序分页,这个方法存在的意义也是在此。使用此方法,栗子如下:
//Integer比较器
Comparator<Integer> comparator = new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
//新建三个列表,CollUtil.newArrayList方法表示新建ArrayList并填充元素
List<Integer> list1 = CollUtil.newArrayList(1, 2, 3);
List<Integer> list2 = CollUtil.newArrayList(4, 5, 6);
List<Integer> list3 = CollUtil.newArrayList(7, 8, 9);
//参数表示把list1,list2,list3合并并按照从小到大排序后,取0~2个(包括第0个,不包括第2个),结果是[1,2]
@SuppressWarnings("unchecked")
List<Integer> result = CollUtil.sortPageAll(0, 2, comparator, list1, list2, list3);
System.out.println(result); //输出 [1,2]
popPart
方法
这个方法传入一个栈对象,然后弹出指定数目的元素对象,弹出是指pop()
方法,会从原栈中删掉。
append
方法
在给定数组里末尾加一个元素,其实List.add()也是这么实现的,这个方法存在的意义是只有少量的添加元素时使用,因为内部使用了System.arraycopy
,每调用一次就要拷贝数组一次。这个方法也是为了在某些只能使用数组的情况下使用,省去了先要转成List
,添加元素,再转成Array。
7. resize
方法
重新调整数据的大小,如果调整后的大小比原来小,截断,如果比原来大,则多出的位置空着。(貌似List在扩充的时候会用到类似的方法)
addAll
方法
将多个数据添加到一个Collection
中
sub
方法
对集合切片,其他类型的集合会转换成List
,封装List.subList
方法,自动修正越界等问题,完全避免IndexOutOfBoundsException
异常。
isEmpty
、isNotEmpty
方法
判断集合是否为空(包括null和没有元素的集合)。
zip
方法
此方法也是来源于Python的一个语法糖,给定两个集合,然后两个集合中的元素一一对应,组成一个Map。此方法还有一个重载方法,可以传字符,然后给定分隔符,字符串会被split成列表。栗子:
Collection<String> keys = CollUtil.newArrayList("a", "b", "c", "d");
Collection<Integer> values = CollUtil.newArrayList(1, 2, 3, 4);
// {a=1,b=2,c=3,d=4}
Map<String, Integer> map = CollUtil.zip(keys, values);
列表工具-ListUtil
介绍
List在集合中使用最为频繁,因此新版本的Hutool中针对List
单独封装了工具方法。
使用
获取满足指定规则所有的元素的位置
List<String> a = ListUtil.toLinkedList("1", "2", "3", "4", "3", "2", "1");
// [1, 5]
int[] indexArray = ListUtil.indexOfAll(a, "2"::equals);
其他方法与CollUtil
工具类似,很多工具也有重复。
拆分
对集合按照指定长度分段,每一个段为单独的集合,返回这个集合的列表:
List<List<Object>> lists = ListUtil.split(Arrays.asList(1, 2, 3, 4), 1);
List<List<Object>> lists = ListUtil.split(null, 3);
也可以平均拆分,即平均分成N份,每份的数量差不超过1:
// [[1, 2, 3, 4]]
List<List<Object>> lists = ListUtil.splitAvg(Arrays.asList(1, 2, 3, 4), 1);
// [[1, 2], [3], [4]]
lists = ListUtil.splitAvg(Arrays.asList(1, 2, 3, 4), 3);
编辑元素
我们可以针对集合中所有元素按照给定的lambda定义规则修改元素:
List<String> a = ListUtil.toLinkedList("1", "2", "3");
final List<String> filter = (List<String>) CollUtil.edit(a, str -> "edit" + str);
// edit1
filter.get(0);
查找位置
List<String> a = ListUtil.toLinkedList("1", "2", "3", "4", "3", "2", "1");
// 查找所有2的位置
// [1,5]
final int[] indexArray = ListUtil.indexOfAll(a, "2"::equals);
列表截取
final List<Integer> of = ListUtil.of(1, 2, 3, 4);
// [3, 4]
final List<Integer> sub = ListUtil.sub(of, 2, 4);
// 对子列表操作不影响原列表
sub.remove(0);
排序
如我们想按照bean对象的order字段值排序:
@Data
@AllArgsConstructor
class TestBean{
private int order;
private String name;
}
final List<TestBean> beanList = ListUtil.toList(
new TestBean(2, "test2"),
new TestBean(1, "test1"),
new TestBean(5, "test5"),
new TestBean(4, "test4"),
new TestBean(3, "test3")
);
final List<TestBean> order = ListUtil.sortByProperty(beanList, "order");
元素交换
将指定元素和指定下标位置的元素交换位置
List<Integer> list = Arrays.asList(7, 2, 8, 9);
// 将元素8和下标为1的元素交换
ListUtil.swapTo(list, 8, 1);
分页
根据传入页码和每页条目数,返回该页拥有的所有数据(第一页的页码取决于PageUtil.getFirstPageNo()
,默认为0
)
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
List<String> page = ListUtil.page(1, 2, list);
分组
通过传入分区长度,将指定列表为不同的块,每块区域的长度相同(最后一块可能小于长度)
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
List<List<String>> partition = ListUtil.partition(list, 2);
Iterator工具-IterUtil
来源
最早此工具类中的方法是在CollUtil中的,由于经过抽象,因此单独拿出来以适应更广的场景。
方法介绍
isEmpty
是否为null或者无元素isNotEmpty
是否为非null或者至少有一个元素hasNull
是否有null元素isAllNull
是否全部为null元素countMap
根据集合返回一个元素计数的Map,所谓元素计数就是假如这个集合中某个元素出现了n次,那将这个元素做为key,n做为valuejoin
使用分隔符将集合转换为字符串toMap
将Entry列表转为Map,或者将key列表和value列表合并为MapasIterator
Enumeration转IteratorasIterable
Iterator转IterablegetFirst
获取列表的第一个元素getElementType
获取元素类型
有界优先队列-BoundedPriorityQueue
简介
举个例子。我有一个用户表,这个表根据用户名被Hash到不同的数据库实例上,我要找出这些用户中最热门的5个,怎么做?我是这么做的:
在每个数据库实例上找出最热门的5个
将每个数据库实例上的这5条数据按照热门程度排序,最后取出前5条
这个过程看似简单,但是你应用服务器上的代码要写不少。首先需要Query N个列表,加入到一个新列表中,排序,再取前5。这个过程不但代码繁琐,而且牵涉到多个列表,非常浪费空间。
于是,BoundedPriorityQueue
应运而生。
先看Demo:
/**
* 有界优先队列Demo
* @author Looly
*
*/
public class BoundedPriorityQueueDemo {
public static void main(String[] args) {
//初始化队列,设置队列的容量为5(只能容纳5个元素),元素类型为integer使用默认比较器,在队列内部将按照从小到大排序
BoundedPriorityQueue<Integer> queue = new BoundedPriorityQueue<Integer>(5);
//初始化队列,使用自定义的比较器
queue = new BoundedPriorityQueue<>(5, new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
//定义了6个元素,当元素加入到队列中,会按照从小到大排序,当加入第6个元素的时候,队列末尾(最大的元素)将会被抛弃
int[] array = new int[]{5,7,9,2,3,8};
for (int i : array) {
queue.offer(i);
}
//队列可以转换为List哦~~
ArrayList<Integer> list = queue.toList();
System.out.println(queue);
}
}
原理非常简单。设定好队列的容量,然后把所有的数据add或者offer进去(两个方法相同),就会得到前5条数据了。
线程安全的HashSet-ConcurrentHashSet
简介
我们知道,JDK提供了线程安全的HashMap:ConcurrentHashMap,但是没有提供对应的ConcurrentHashSet,Hutool借助ConcurrentHashMap封装了线程安全的ConcurrentHashSet。
使用
与普通的HashSet使用一致:
Set<String> set = new ConcurrentHashSet<>();
set.add("a");
set.add("b");
集合串行流工具-CollStreamUtil
介绍
Java8中的新特性之一就是Stream,Hutool针对常用操作做了一些封装
使用
集合转Map
@Data
@AllArgsConstructor
@ToString
public static class Student {
private long termId;//学期id
private long classId;//班级id
private long studentId;//班级id
private String name;//学生名称
}
我们可以建立一个学生id和学生对象之间的map:
List<Student> list = new ArrayList<>();
list.add(new Student(1, 1, 1, "张三"));
list.add(new Student(1, 1, 2, "李四"));
list.add(new Student(1, 1, 3, "王五"));
Map<Long, Student> map = CollStreamUtil.toIdentityMap(list, Student::getStudentId);
// 张三
map.get(1L).getName();
我们也可以自定义Map的key和value放的内容,如我们可以将学生信息的id和姓名生成map:
Map<Long, String> map = map = CollStreamUtil.toMap(list, Student::getStudentId, Student::getName);
// 张三
map.get(1L);
分组
我们将学生按照班级分组:
List<Student> list = new ArrayList<>();
list.add(new Student(1, 1, 1, "张三"));
list.add(new Student(1, 2, 2, "李四"));
list.add(new Student(2, 1, 1, "擎天柱"));
list.add(new Student(2, 2, 2, "威震天"));
list.add(new Student(2, 3, 2, "霸天虎"));
Map<Long, List<Student>> map = CollStreamUtil.groupByKey(list, Student::getClassId);
转换提取
我们可以将学生信息列表转换提取为姓名的列表:
List<String> nameList = CollStreamUtil.toList(list, Student::getName);
合并
合并两个相同key类型的map,可自定义合并的lambda,将 value1和value2合并成最终的类型,需要自行处理value为空的情况。
Map<Long, Student> map1 = new HashMap<>();
map1.put(1L, new Student(1, 1, 1, "张三"));
Map<Long, Student> map2 = new HashMap<>();
map2.put(1L, new Student(2, 1, 1, "李四"));
定义merge规则:
private String merge(Student student1, Student student2) {
if (student1 == null && student2 == null) {
return null;
} else if (student1 == null) {
return student2.getName();
} else if (student2 == null) {
return student1.getName();
} else {
return student1.getName() + student2.getName();
}
Map<Long, String> map = CollStreamUtil.merge(map1, map2, this::merge);
行遍历器-LineIter
介绍
此工具分别参考和Apache Commons io
和Guava
项目。
将Reader包装为一个按照行读取的Iterator。
使用
final LineIter lineIter = new LineIter(ResourceUtil.getUtf8Reader("test_lines.csv"));
for (String line : lineIter) {
Console.log(line);
}
Map
概述
由来
最早Map的相关工具是被包含在CollUtil中的,但是考虑到Map和集合非同一类数据结构,因此独立出来,且Hutool封装了许多自定义的Map类,因此单独成包。
使用
特殊Map
CaseInsensitiveMap
忽略大小写的Map,对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖CaseInsensitiveLinkedMap
忽略大小写的LinkedHashMap,对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖MapBuilder
Map创建器,可以链式创建MapMapProxy
Map代理类,通过代理包装Map,提供一系列的getXXX方法
工具
MapUtil
提供对Map常用操作的封装
Map工具-MapUtil
介绍
MapUtil是针对Map的一系列工具方法的封装,包括getXXX的快捷值转换方法。
方法
isEmpty
、isNotEmpty
判断Map为空和非空的方法,空的定义为null或没有值newHashMap
快速创建多种类型的HashMap实例createMap
创建自定义的Map类型的Mapof
此方法将一个或多个键值对加入到一个新建的Map中,下面是栗子:
Map<Object, Object> colorMap = MapUtil.of(new String[][] {
{"RED", "#FF0000"},
{"GREEN", "#00FF00"},
{"BLUE", "#0000FF"}
});
toListMap
行转列,合并相同的键,值合并为列表,将Map列表中相同key的值组成列表做为Map的value,例如传入数据是:
[
{a: 1, b: 1, c: 1},
{a: 2, b: 2},
{a: 3, b: 3},
{a: 4}
]
结果为:
{
a: [1,2,3,4],
b: [1,2,3,],
c: [1]
}
toMapList
列转行。将Map中值列表分别按照其位置与key组成新的map,例如传入数据:
{
a: [1,2,3,4],
b: [1,2,3,],
c: [1]
}
结果为:
[
{a: 1, b: 1, c: 1},
{a: 2, b: 2},
{a: 3, b: 3},
{a: 4}
]
join
、joinIgnoreNull
、sortJoin
将Map按照给定的分隔符转换为字符串,此方法一般用于签名。
Map<String, String> build = MapUtil.builder(new HashMap<String, String>())
.put("key1", "value1")
.put("key3", "value3")
.put("key2", "value2").build();
// key1value1key2value2key3value3
String join1 = MapUtil.sortJoin(build, StrUtil.EMPTY, StrUtil.EMPTY, false);
// key1value1key2value2key3value3123
String join2 = MapUtil.sortJoin(build, StrUtil.EMPTY, StrUtil.EMPTY, false, "123");
filter
通过传入的Filter实现来筛选需要的元素内容,返回true则保留这个元素,false则过滤元素。
Map<String, String> map = MapUtil.newHashMap();
map.put("a", "1");
map.put("b", "2");
map.put("c", "3");
map.put("d", "4");
Map<String, String> map2 = MapUtil.filter(map,(Filter<Entry<String, String>>) t -> Convert.toInt(t.getValue()) % 2 == 0);
结果为
{
b: "2",
d: "4"
}
map
通过传入的BiFunction实现来修改value,返回一个新的map
定义个小枚举
enum PeopleEnum {GIRL, BOY, CHILD}
开始操作
Map<Integer, String> adjectivesMap = MapUtil.<Integer, String>builder()
.put(0, "lovely")
.put(1, "friendly")
.put(2, "happily")
.build();
Map<Integer, String> resultMap = MapUtil.map(adjectivesMap, (k, v) -> v + " " + PeopleEnum.values()[k].name().toLowerCase());
结果为
{
0: "lovely girl",
1: "friendly boy",
2: "happily child"
}
reverse
Map的键和值互换
Map<String, String> map = MapUtil.newHashMap();
map.put("a", "1");
map.put("b", "2");
map.put("c", "3");
map.put("d", "4");
Map<String, String> map2 = MapUtil.reverse(map);
结果为:
{
"1": "a",
"2": "b",
"3": "c",
"4": "d",
}
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,则有序,是后加入的覆盖先加入的。
使用
BiMap<String, Integer> biMap = new BiMap<>(new HashMap<>());
biMap.put("aaa", 111);
biMap.put("bbb", 222);
// 111
biMap.get("aaa");
// 222
biMap.get("bbb");
// aaa
biMap.getKey(111);
// bbb
biMap.getKey(222);
可重复键值Map-TableMap
介绍
有时候我们需要键值对一一对应,但是又有可能有重复的键,也可能有重复的值,就像一个2列的表格一样:
因此,Hutool创建了TableMap
这类数据结构,通过键值单独建立List方式,使键值对一一对应,实现正向和反向两种查找。
当然,这种Map无论是正向还是反向,都是遍历列表查找过程,相比标准的HashMap要慢,数据越多越慢。
使用
TableMap<String, Integer> tableMap = new TableMap<>(new HashMap<>());
tableMap.put("aaa", 111);
tableMap.put("bbb", 222);
// 111
tableMap.get("aaa");
// 222
tableMap.get("bbb");
// aaa
tableMap.getKey(111);
// bbb
tableMap.getKey(222);
// [111]
tableMap.getValues("aaa");
//[aaa]
tableMap.getKeys(111);
Map流式构建器-MapBuilder
介绍
MapBuilder提供了一种流式的Map创建方法。
使用
Map<String, Object> srcMap = MapBuilder
.create(new HashMap<String, Object>())
.put("name", "AAA")
.put("age", 45).map();
Codec编码
Base62编码解码-Base62
介绍
Base62编码是由10个数字、26个大写英文字母和26个小写英文字母组成,多用于安全领域和短URL生成。
使用
String a = "伦家是一个非常长的字符串66";
// 17vKU8W4JMG8dQF8lk9VNnkdMOeWn4rJMva6F0XsLrrT53iKBnqo
String encode = Base62.encode(a);
// 还原为a
String decodeStr = Base62.decodeStr(encode);
Base64编码解码-Base64
介绍
Base64编码是用64(2的6次方)个ASCII字符来表示256(2的8次方)个ASCII字符,也就是三位二进制数组经过编码后变为四位的ASCII字符显示,长度比原来增加1/3。
使用
String a = "伦家是一个非常长的字符串";
//5Lym5a625piv5LiA5Liq6Z2e5bi46ZW/55qE5a2X56ym5Liy
String encode = Base64.encode(a);
// 还原为a
String decodeStr = Base64.decodeStr(encode);
Base32编码解码-Base32
介绍
Base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码。所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足。
使用
String a = "伦家是一个非常长的字符串";
String encode = Base32.encode(a);
Assert.assertEquals("4S6KNZNOW3TJRL7EXCAOJOFK5GOZ5ZNYXDUZLP7HTKCOLLMX46WKNZFYWI", encode);
String decodeStr = Base32.decodeStr(encode);
Assert.assertEquals(a, decodeStr);
莫尔斯电码-Morse
介绍
摩尔斯电码也被称作摩斯密码,是一种时通时断的信号代码,通过不同的排列顺序来表达不同的英文字母、数字和标点符号。
摩尔斯电码是由点dot(.)划dash(-)这两种符号所组成的。
实现
编码
final Morse morseCoder = new Morse();
String text = "Hello World!";
// ...././.-../.-../---/-...../.--/---/.-./.-../-../-.-.--/
morseCoder.encode(text);
解码
String text = "你好,世界!";
// -..----.--...../-.--..-.-----.-/--------....--../-..---....-.--./---.-.-.-..--../--------.......-/
String morse = morseCoder.encode(text);
morseCoder.decode(morse);
BCD码-BCD
介绍
BCD码(Binary-Coded Decimal)亦称二进码十进数或二-十进制代码。
BCD码这种编码形式利用了四个位元来储存一个十进制的数码,使二进制和十进制之间的转换得以快捷的进行。
使用
String strForTest = "123456ABCDEF";
// 转BCD
byte[] bcd = BCD.strToBcd(strForTest);
// 解码BCD
String str = BCD.bcdToStr(bcd);
回转N位密码-Rot
介绍
RotN(rotate by N places),回转N位密码,是一种简易的替换式密码,也是过去在古罗马开发的凯撒加密的一种变体。
使用
以Rot-13为例:
String str = "1f2e9df6131b480b9fdddc633cf24996";
// 4s5r2qs9464o713o2sqqqp966ps57229
String encode13 = Rot.encode13(str);
// 解码
String decode13 = Rot.decode13(encode13);
Punycode实现-PunyCode
介绍
Punycode是一个根据RFC 3492标准而制定的编码系统,主要用于把域名从地方语言所采用的Unicode编码转换成为可用于DNS系统的编码。
具体见:RFC 3492
使用
String text = "Hutool编码器";
// Hutool-ux9js33tgln
String strPunyCode = PunyCode.encode(text);
// Hutool编码器
String decode = PunyCode.decode("Hutool-ux9js33tgln");
// Hutool编码器
decode = PunyCode.decode("xn--Hutool-ux9js33tgln");