CollectionUtils工具類
CollectionUtils工具類是在apache下的,而不是springframework下的CollectionUtils
個人覺得在真實項目中CollectionUtils,可以使你的代碼更加簡潔和安全
下面我們就一起來探究一下
先從maven官方找到最新jar包如下:
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.4</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、並集 union System.out.println(CollectionUtils.union(listA,listB)); //輸出:[1, 2, 3, 4, 5, 6] //2、交集intersection System.out.println(CollectionUtils.intersection(listA,listB)); //輸出:[3, 4] //3、交集的補集(析取)disjunction System.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> jiaoJiList = new ArrayList<>(listA); jiaoJiList.retainAll(listB); System.out.println(jiaoJiList); //輸出:[3, 4] //2差集 List<String> chaJiList = new ArrayList<>(listA); chaJiList.removeAll(listB); System.out.println(chaJiList); //輸出:[1, 2] //3並集(先做差集在做添加所有) List<String> bingList = new ArrayList<>(listA); bingList.removeAll(listB);//[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方法是對原有集合進行處理再返回原有集合,會改變原有集合中的內容
1:從性能角度來考慮的話,List自帶會高點,因為它不用再創建新的集合,2:需要注意的是,因為retainAll因為會改變原有集合,所以該集合需要多次使用就不適合用retainAll
注意:Arrays.asList將數據轉集合不能進行add和remove操作
原因:調用Arrays.asList()生產的List的add、remove方法時報異常,這是由Arrays.asList()返回的是Arrays的內部類ArrayList,而不是java.util.ArrayList.Arrays的內部類ArrayList和java.util.ArrayList都是繼承AbstractList,remove,add
等方法AbstractList中是默認throw UnsupportedOperationException而且不做任何操作,java.util.ArrayList重新了這些方法而Arrays的內部類沒有重新,所以會拋出異常
所以正確做法如下:
String[] array = {"1","2","3"}; List<String> list = Arrays.asList(array); List arrList = new ArrayList(list); arrList.add("4");
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(Collectors.toList()); System.out.println(intersection); //輸出:[3, 4] //並集(新建集合:1、是因為不影響原始集合,2、Arrays.asList不能add和remove操作) List<String> listAll = listA.parallelStream().collect(Collectors.toList()); List<String> listAll2 = listB.parallelStream().collect(Collectors.toList()); listAll.addAll(listAll2); System.out.println(listAll); //輸出:[1, 2, 3, 4, 3, 4, 5, 6] //差集 List<String> reduceList = listA.stream().filter(item -> !listB.contains(item)).collect(Collectors.toList()); System.out.println(reduceList); //輸出:[1, 2] //去重並集 ArrayList<String> list = new ArrayList<>(listA); list.addAll(listB); List<String> listAllDistinct = list.stream().distinct().collect(Collectors.toList()); System.out.println(listAllDistinct); //輸出:[1, 2, 3, 4, 5, 6] }
總結:三種方式,第一種稍微好用一些,第二種還需要確定該集合是否被多次調用,第三種可讀性不高
三、對象集合交、並、差處理 |
因為對象的equals比較是比較兩個對象的內存地址,所以除非是同一對象,否則equals返回的永遠是false
但實際開發中,業務系統中判斷對象有時候需要的不是一種嚴格意義上的相等,而是業務上的對象相等,在這種情況下,原生的equals方法就不能滿足我們的需求了,所以這個時候我們需要重寫equals方法
說明:String為什么可以使用equals方法,只要字符串相等就為true,那是因為String類重寫了equals和hashCode方法,比較的是值
1、Person對象
public static void main(String[] args) { List<Person> personList = Lists.newArrayList(); Person person1 = new Person("小小", 15); Person person2 = new Person("中中", 16); personList.add(person1); personList.add(person2); List<Person> person1List = Lists.newArrayList(); Person person3 = new Person("中中", 16); Person person4 = new Person("大大", 17); person1List.add(person3); person1List.add(person4); /** * 1、差集 */ System.out.println(CollectionUtils.subtract(personList,person1List)); //輸出:[Person{name='小小', age=15}] /** * 2、交集 */ System.out.println(CollectionUtils.intersection(personList,person1List)); //輸出:[Person{name='中中', age=16}] /** * 3、並集 */ System.out.println(CollectionUtils.union(personList,person1List)); //輸出:[Person{name='小小', age=15}, Person{name='大大', age=17}, Person{name='中中', age=16}] /** * 4、交集的補集(析取) */ System.out.println(CollectionUtils.disjunction(personList,person1List)); //輸出:[Person{name='小小', age=15}, Person{name='大大', age=17}] }
其他兩種方式就不測了,因為都一樣
四、為什么要重寫equals方法一定要重寫hashCode方法 |
1、源碼
其實上面的Person類我可以只重寫equals方法而不寫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方法是一致的,還有最關鍵的一點
我們來Object類中關於hashCode方法的注釋
1.在java應用程序運行期間,在對同一對象多次調用hashCode方法時,必須一致地返回相同的整數,前提是將對象進行equals比較時所用的信息沒有被修改。 2.如果根據equals(Object)方法,兩個對象是相等的,那么對這兩個對象調用hashCode方法都必須生成相同的整數結果。 3.如果根據equals(java.lang.Object)方法,兩個對象不相等,那么對這兩個對象中的任一對象上調用hashCode方法不 要求一定生成不同的整數結果,但是,我們應該意識到,為不相等的對象生成不同整數結果可以提高哈希表的性能
整理:hashCode和equals保持一致,如果equals方法返回true,那么兩個對象的hashCode返回值必須一樣,如果equals方法返回false,hashCode可以不一樣,但是這樣不利於哈希表的性能,一般我們也不要這樣做
假設兩個對象,重寫了其equals方法,其相等條件是某屬性相等,就返回true,如果不重寫hashCode方法,其返回的依然是兩個對象的內存地址值,必然不相等,這就出現了equals方法相等,但是hashCode不相等的情況,這不符合hashCode的規則
2、HashSet和Map集合類型
重寫equals方法就必須重寫hashCode方法主要是針對HashSet和Map集合類型,而對於List集合到沒有什么影響
原因:在向HashSet集合中存入一個元素時,HashSet會調用該對象(存入對象)的hashCode方法來得到該對象的hashCode值,然后根據該hashCode值決定該對象在HashSet中存儲的位置,簡單的說:
HashSet集合判斷兩個元素相等的標准是:兩個對象通過equals方法比較相等,並且兩個對象hashCode方法返回值也相等,如果兩個元素通過equals方法比較返回true,但是它們的hashCode方法返回值不同,HashSet會把它們存儲在不同的位置,依然可以
添加成功
這就是問題所在:就是如果你只重寫equals方法,而不重寫hashCode,如果equals為true,而它們的hashCode方法返回值肯定不一樣,因為它們都不是同一對象所以內存地址肯定不一樣,所以它還是添加成功了,那么其實你寫的equals方法根本沒啥軟用
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方法 * @param o * @return */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; People people = (People) o; return Objects.equals(name, people.name) && Objects.equals(age, people.age); } /** * 重寫toString方法 * @return */ @Override public String toString() { return "People{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
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=5}, People{name='中中', age=4}, People{name='小小', age=3}, People{name='中中', age=4}] }
很明顯,我重寫equals方法,那么people2和people3的equals應該相同,所以不能放入HashSet,但它們的hashCode方法返回不同,所以導致同樣能放入HashSet
重點:對於Set集合必須要同時重寫這兩個方法,要不然Set的特性就被破壞了