java代碼之美(12)---CollectionUtils工具類


CollectionUtils工具類

這篇講的CollectionUtils工具類是在apache下的, 而不是springframework下的CollectionUtils。

個人覺得CollectionUtils在真實項目中,可以使你的代碼更加簡潔和安全。

所以需要倒入相關jar包,目前從maven找到最新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、並集 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>  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方法是對原有集合進行處理再返回原有集合,會改變原有集合中的內容。

個人觀點: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的內部類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方法下面也會講
     */
    @Override
    public int hashCode() {
        String result = name + age;
        return result.hashCode();
    }
    /**
     * 重寫 equals 方法 根據name和age都相同那么對象就默認相同
     */
    @Override
    public boolean equals(Object obj) {
        Person u = (Person) obj;
        return this.getName().equals(u.getName()) && (this.age.equals(u.getAge()));
    }
    /**
     * 重寫 toString 方法
     */
    @Override
    public 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方法是一致的。還有最關鍵的一點,我們來看Object類中關於hashCode()方法的注釋:

  1.在 Java 應用程序執行期間,在對同一對象多次調用 hashCode 方法時,必須一致地返回相同的整數,前提是將對象進行 equals 比較時所用的信息沒有被修改。    
  2.如果根據 equals(Object) 方法,兩個對象是相等的,那么對這兩個對象中的每個對象調用 hashCode 方法都必須生成相同的整數結果。    
  3.如果根據 equals(java.lang.Object) 方法,兩個對象不相等,那么對這兩個對象中的任一對象上調用 hashCode 方法不 要求一定生成不同的整數結果。
     但是,程序員應該意識到,為不相等的對象生成不同整數結果可以提高哈希表的性能。      

整理 : hashCode()和equals()保持一致,如果equals方法返回true,那么兩個對象的hasCode()返回值必須一樣。如果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 方法
     */
    @Override
    public boolean equals(Object obj) {
        People u = (People) obj;
        return this.getName().equals(u.getName()) && (this.age.equals(u.getAge()));
    }
    /**
     * 重寫 toString 方法
     */
    @Override
    public 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的特性就被破壞了。



只要自己變優秀了,其他的事情才會跟着好起來(少將8)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM