集合操作,CollectionUtils工具类
这篇讲的是在apache下的CollectionUtils, 而不是springframework下的CollectionUtils。
首先需要引入相关jar包:
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.3</version>
</dependency>
一、API常用方法
/*** 1、除非元素为null,否则向集合添加元素*/CollectionUtils.addIgnoreNull(personList,null);/*** 2、将两个已排序的集合a和b合并为一个已排序的列表,以便保留元素的自然顺序*/CollectionUtils.collate(Iterable<? extends O> a, Iterable<? extends O> b)/*** 3、将两个已排序的集合a和b合并到一个已排序的列表中,以便保留根据Comparator c的元素顺序。*/CollectionUtils.collate(Iterable<? extends O> a, Iterable<? extends O> b, Comparator<? super O> c)/*** 4、返回该个集合中是否含有至少有一个元素*/CollectionUtils.containsAny(Collection<?> coll1, T... coll2)/*** 5、如果参数是null,则返回不可变的空集合,否则返回参数本身。(很实用 ,最终返回List EMPTY_LIST = new EmptyList<>())*/CollectionUtils.emptyIfNull(Collection<T> collection)/*** 6、空安全检查指定的集合是否为空*/CollectionUtils.isEmpty(Collection<?> coll)/*** 7、 空安全检查指定的集合是否为空。*/CollectionUtils.isNotEmpty(Collection<?> coll)/*** 8、反转给定数组的顺序。*/CollectionUtils.reverseArray(Object[] array);/*** 9、差集*/CollectionUtils.subtract(Iterable<? extends O> a, Iterable<? extends O> b)/*** 10、并集*/CollectionUtils.union(Iterable<? extends O> a, Iterable<? extends O> b)/*** 11、交集*/CollectionUtils.intersection(Collection a, Collection b)/***12、 交集的补集(析取)*/CollectionUtils.disjunction(Collection a, Collection b)
二、非对象集合交、并、差处理
对于集合取交集、并集的处理其实有很多种方式,这里就介绍3种
第一种 是CollectionUtils工具类
第二种 是List自带方法
第三种 是JDK1.8 stream 新特性
1、CollectionUtils工具类
下面对于基本数据(包扩String)类型中的集合进行demo示例。
public static void main(String[] args) {String[] arrayA = new String[] { "1", "2", "3", "4"};String[] arrayB = new String[] { "3", "4", "5", "6" };List<String> listA = Arrays.asList(arrayA);List<String> listB = Arrays.asList(arrayB);//1、并集 unionSystem.out.println(CollectionUtils.union(listA, listB));//输出: [1, 2, 3, 4, 5, 6]//2、交集 intersectionSystem.out.println(CollectionUtils.intersection(listA, listB));//输出:[3, 4]//3、交集的补集(析取)disjunctionSystem.out.println(CollectionUtils.disjunction(listA, listB));//输出:[1, 2, 5, 6]//4、差集(扣除)System.out.println(CollectionUtils.subtract(listA, listB));//输出:[1, 2]}
2、List自带方法
public static void main(String[] args) {String[] arrayA = new String[] { "1", "2", "3", "4"};String[] arrayB = new String[] { "3", "4", "5", "6" };List<String> listA = Arrays.asList(arrayA);List<String> listB = Arrays.asList(arrayB);//1、交集List<String> jiaoList = new ArrayList<>(listA);jiaoList.retainAll(listB);System.out.println(jiaoList);//输出:[3, 4]//2、差集List<String> chaList = new ArrayList<>(listA);chaList.removeAll(listB);System.out.println(chaList);//输出:[1, 2]//3、并集 (先做差集再做添加所有)List<String> bingList = new ArrayList<>(listA);bingList.removeAll(listB); // bingList为 [1, 2]bingList.addAll(listB); //添加[3,4,5,6]System.out.println(bingList);//输出:[1, 2, 3, 4, 5, 6]}
注意 : intersection和retainAll的差别
它们的返回类型是不一样的,intersection返回的是一个新的List集合,而retainAll返回是Bollean类型。
那就说明retainAll方法是对原有集合进行处理再返回原有集合,会改变原有集合中的内容。
注意 : Arrays.asList将数组转集合不能进行add和remove操作。
原因:调用Arrays.asList()生产的List的add、remove方法时报异常,这是由Arrays.asList() 返回的市Arrays的内部类ArrayList, 而不是java.util.ArrayList。
所以正确做法如下
String[] array = {"1","2","3","4","5"};List<String> list = Arrays.asList(array);List arrList = new ArrayList(list);arrList.add("6");
3、JDK1.8 stream 新特性
public static void main(String[] args) {String[] arrayA = new String[] { "1", "2", "3", "4"};String[] arrayB = new String[] { "3", "4", "5", "6" };List<String> listA = Arrays.asList(arrayA);List<String> listB = Arrays.asList(arrayB);// 交集List<String> intersection = listA.stream().filter(item -> listB.contains(item)).collect(toList());System.out.println(intersection);//输出:[3, 4]// 差集 (list1 - list2)List<String> reduceList = listA.stream().filter(item -> !listB.contains(item)).collect(toList());System.out.println(reduceList);//输出:[1, 2]// 并集 (新建集合:1、是因为不影响原始集合。2、Arrays.asList不能add和remove操作。List<String> listAll = listA.parallelStream().collect(toList());List<String> listAll2 = listB.parallelStream().collect(toList());listAll.addAll(listAll2);System.out.println(listAll);//输出:[1, 2, 3, 4, 3, 4, 5, 6]// 去重并集 List<String> list =new ArrayList<>(listA);list.addAll(listB);List<String> listAllDistinct = list.stream().distinct().collect(toList());System.out.println(listAllDistinct);//输出:[1, 2, 3, 4, 5, 6]}
总结 :这三种我还是最喜欢第一种方式,因为第二种还需要确定该集合是否被多次调用。第三种可读性不高。
三、对象集合交、并、差处理
因为对象的equels比较是比较两个对象的内存地址,所以除非是同一对象,否则equel返回永远是false。
但我们实际开发中 在我们的业务系统中判断对象时有时候需要的不是一种严格意义上的相等,而是一种业务上的对象相等。
在这种情况下,原生的equals方法就不能满足我们的需求了,所以这个时候我们需要重写equals方法。
说明 :String为什么可以使用equels方法为什么只要字符串相等就为true,那是因为String类重写了equal和hashCode方法,比较的是值。
1、Person对象
public class Person {
private String name;
private Integer age;public Person(String name, Integer age) {this.name = name;this.age = age;}/*** 为什么重写equals方法一定要重写hashCode方法下面也会讲*/@Overridepublic int hashCode() {String result = name + age;return result.hashCode();}/*** 重写 equals 方法 根据name和age都相同那么对象就默认相同*/@Overridepublic boolean equals(Object obj) {Person u = (Person) obj;return this.getName().equals(u.getName()) && (this.age.equals(u.getAge()));}/*** 重写 toString 方法*/@Overridepublic String toString() {return "Person{" + "name='" + name + '\'' + ", age=" + age +'}';}}
2、测试
这里根据name和age都相同那么就默认相同对象。
public static void main(String[] args) {List<Person> personList = Lists.newArrayList();Person person1 = new Person("小小",3);Person person2 = new Person("中中",4);personList.add(person1);personList.add(person2);List<Person> person1List = Lists.newArrayList();Person person3 = new Person("中中",4);Person person4 = new Person("大大",5);person1List.add(person3);person1List.add(person4);/*** 1、差集*/System.out.println(CollectionUtils.subtract(personList, person1List));//输出:[Person{name='小小', age=3}]/*** 2、并集*/System.out.println(CollectionUtils.union(personList, person1List));//输出:[Person{name='小小', age=3}, Person{name='中中', age=4}, Person{name='大大', age=5}]/*** 3、交集*/System.out.println(CollectionUtils.intersection(personList, person1List));//输出:[Person{name='中中', age=4}]/*** 4、交集的补集(析取)*/System.out.println(CollectionUtils.disjunction(personList, person1List));//输出:[Person{name='小小', age=3}, Person{name='大大', age=5}]}
其它两种方式就不在测了,因为都一样。
四、为什么重写equels方法一定要重写hashCode方法
1、源码
其实上面的Person类我可以只重写equels方法而不写hashCode方法,一样能达到上面的效果。
但为什么还是建议写上呢?官方的说法是:
对象的equals方法被重写,那么对象的hashCode()也尽量重写
重写equals()方法就必须重写hashCode()方法的原因,从源头Object类讲起就更好理解了。
先来看Object关于hashCode()和equals()的源码:
public native int hashCode();public boolean equals(Object obj) {return (this == obj);}
光从代码中我们可以知道,hashCode()方法是一个本地native方法,返回的是对象引用中存储的对象的内存地址。
而equals方法是利用==来比较的也是对象的内存地址。
从上边我们可以看出,hashCode方法和equals方法是一致的。
如果equals方法返回false,hashcode可以不一样,但是这样不利于哈希表的性能,一般我们也不要这样做。
2、HashSet和Map集合类型
重写equals()方法就必须重写hashCode()方法主要是针对HashSet和Map集合类型,而对于List集合倒没什么影响。
原因:在向HashSet集合中存入一个元素时,HashSet会调用该对象(存入对象)的hashCode()方法来得到该对象的hashCode()值,然后根据该hashCode值决定该对象在HashSet中存储的位置。
简单的说:
HashSet集合判断两个元素相等的标准是:两个对象通过equals()方法比较相等,并且两个对象的HashCode()方法返回值也相等
如果两个元素通过equals()方法比较返回true,但是它们的hashCode()方法返回值不同,HashSet会把它们存储在不同的位置,依然可以添加成功。
3、代码示例
「1、People类」
重写equals方法,但并没有hashCode方法。public class People {private String name;private Integer age;public People(String name, Integer age) {this.name = name;this.age = age;}/*** 重写 equals 方法*/@Overridepublic boolean equals(Object obj) {People u = (People) obj;return this.getName().equals(u.getName()) && (this.age.equals(u.getAge()));}/*** 重写 toString 方法*/@Overridepublic String toString() {return "People{" + "name='" + name + '\'' +", age=" + age +'}';}
}
「2、实现类」
public static void main(String[] args) {HashSet<People> hashSet = Sets.newHashSet();People people1 = new People("小小",3);People people2 = new People("中中",4);People people3 = new People("中中",4);People people4 = new People("大大",5);hashSet.add(people1);hashSet.add(people2);hashSet.add(people3);hashSet.add(people4);System.out.println(hashSet);//输出:[People{name='小小', age=3}, People{name='中中', age=4}, People{name='大大', age=5}, People{name='中中', age=4}]
}
很明显,我重写了equals方法,那么people2和people3的equals应该相同,所以不能放入HashSet,但它们的hashCode()方法返回不同,所以导致同样能放入HashSet。
重点:对于Set集合必须要同时重写这两个方法,要不然Set的特性就被破坏了。