Arrays、Collections、Objects方法源碼

Arrays、Collections、Objects,都是對應對象的工具類。好的工具類通用的寫法特徵:

  1. 構造器必須是私有的。這樣的話,工具類就無法被 new 出來,因爲工具類在使用的時候,無需初始化,直接使用即可,所以不會開放出構造器出來。
  2. 工具類的工具方法必須被 static、final 關鍵字修飾。這樣的話就可以保證方法不可變、不能被重寫,並且可以直接使用,非常方便。

我們需要注意的是,儘量不在工具方法中,對共享變量有做修改的操作訪問(如果必須要做的話,必須加鎖),因爲會有線程安全的問題。除上述情況外,工具類方法本身是沒有線程安全問題的,可以放心使用。

Arrays

Arrays主要對數組Array提供了一些高效的操作,比如說排序、查找、填充、拷貝、相等判斷等等。

排序

Arrays.sort 主要用於排序,從下圖可以看出,sort可接收所有類型數組。
image.png
sort 的性能高,是因爲其使用了DualPivotQuickSort 雙軸快速排序。以sort(byte[] a)爲例,看到其實是直接調用了DualPivotQuicksort.sort方法。
對於雙軸快速排序,可參考雙軸快速排序-CSDN

public static void sort(byte[] a) {
    DualPivotQuicksort.sort(a, 0, a.length - 1);
}

二分查找

使用Arrays.bin的注意事項:

  1. 如果被搜索的數組是無序的,一定要先排序,否則二分搜索很有可能搜索不到。
  2. 搜索方法返回的是數組的下標值。如果搜索不到,返回的下標值就會是負數,這時我們需要判斷一下正負。如果是負數,還從數組中獲取數據的話,會報數組越界的錯誤。

二分查找的實現

// a:我們要搜索的數組,fromIndex:從那裏開始搜索,默認是0; 
// toIndex:搜索到何處停止,默認是數組大小
// key:我們需要搜索的元素
// c:比較器
private static <T> int binarySearch0(T[] a, int fromIndex, int toIndex,
                                     T key, Comparator<? super T> c) {
    // 如果比較器 c 是空的,直接使用 key 的 Comparable.compareTo 方法進行排序
    // 假設 key 類型是 String 類型,String 默認實現了 Comparable 接口,就可以直接使用 compareTo 方法進行排序
    if (c == null) {
        // 這是另外一個方法,使用內部排序器進行比較的方法
        return binarySearch0(a, fromIndex, toIndex, key);
    }
    int low = fromIndex;
    int high = toIndex - 1;
    // 開始位置小於結束位置,就會一直循環搜索
    while (low <= high) {
        // 假設 low =0,high =10,那麼 mid 就是 5,所以說二分的意思主要在這裏,每次都是取索引的中間值
        // 這裏涉及到二進制計算
        int mid = (low + high) >>> 1;
        T midVal = a[mid];
        // 比較數組中間值和給定的值的大小關係
        int cmp = c.compare(midVal, key);
        // 如果數組中間值小於給定的值,說明我們要找的值在中間值的右邊。我們將左邊界移到中點往右
        if (cmp < 0)
            low = mid + 1;
        // 我們要找的值在中間值的左邊。我們將左邊界移到中點往右
        else if (cmp > 0)
            high = mid - 1;
        else
        // 找到了
            return mid; // key found
    }
    // 返回的值是負數,表示沒有找到
    return -(low + 1);  // key not found.
}

拷貝

數組拷貝我們經常遇到,有時需要拷貝整個數組,有時需要拷貝部分。
比如 ArrayList 在 add(擴容) 或 remove(刪除元素不是最後一個) 操作時,會進行一些拷貝。
拷貝整個數組我們可以使用 copyOf 方法,拷貝部分我們可以使用 copyOfRange 方法,以 copyOfRange 爲例,看下底層源碼的實現:Arrays 的拷貝方法,實際上底層調用的是 System.arraycopy 這個 native 方法。

// original 原始數組數據
// from 拷貝起點
// to 拷貝終點
public static char[] copyOfRange(char[] original, int from, int to) {
    // 需要拷貝的長度
    int newLength = to - from;
    if (newLength < 0)
        throw new IllegalArgumentException(from + " > " + to);
    // 初始化新數組
    char[] copy = new char[newLength];
    // 調用 native 方法進行拷貝,參數的意思分別是:
    // 被拷貝的數組、從數組那裏開始、目標數組、從目的數組那裏開始拷貝、拷貝的長度
    System.arraycopy(original, from, copy, 0,
                     Math.min(original.length - from, newLength));
    return copy;
}

// System.arraycopy方法簽名
public static native void arraycopy(Object src, 
                                    int  srcPos,
                                    Object dest, 
                                    int destPos,
                                    int length);

Collections

Collections也提供了二分查找、排序方法,sort 底層使用的就是 Arrays.sort 方法,binarySearch 底層是自己重寫了二分查找算法,實現的邏輯和 Arrays 的二分查找算法完全一致。

求集合中最大、小值(max、min)

max 和 min 方法很相似,以下以max方法爲例。max提供瞭如下兩種重載。

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {}

public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) {}

image.png
image.png
兩個方法的區別在於一個需要傳外部排序器;一個雖然不需要傳排序器,但是需要集合中的元素強制實現 Comparable 接口。從上述源碼中,可以體會到:

  1. max 方法泛型 T 定義得非常巧妙,意思是泛型必須繼承 Object 並且實現 Comparable 的接口。一般讓我們來定義的話,我們可能會在方法裏面去判斷有無實現 Comparable 的接口,這種是在運行時才能知道結果。而這裏泛型直接定義了必須實現 Comparable 接口,在編譯的時候就可通過錯誤告訴使用者,當前類沒有實現 Comparable 接口
  2. 以上源碼給我們提供了實現兩種排序機制的優秀示例:自定義類實現 Comparable 接口、傳入外部排序器。兩種排序實現原理類似,但實現有所差別,我們在工作中如果需要些排序的工具類時,可以效仿。

不可變集合

得到不可變集合的方法,都以unmodifiable爲前綴。這類方法的意思是,我們會從原集合中,得到一個不可變的新集合。得到的新集合只能訪問,無法修改,一旦修改,就會拋出異常。
新集合都是Collections中定義的私有類,如UnmodifiableCollection。爲什麼在調用不可變集合的修改方法時會拋出錯誤呢?原因如下圖,這些不可變的集合類,只開放了查詢方法,在定義時即將修改性質的方法,拋出UnsupportedOperationException
image.png

同步/線程安全集合

得到不可變集合的方法,都以synchronized爲前綴。以 synchronizedCollection 爲例,源碼如下,所有的方法簽名中,都使用了synchronized來修飾,所以多線程對集合同時進行操作,是線程安全的。

static class SynchronizedCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 3053995032091335093L;

        final Collection<E> c;  // Backing Collection
        final Object mutex;     // Object on which to synchronize

        SynchronizedCollection(Collection<E> c) {
            this.c = Objects.requireNonNull(c);
            mutex = this;
        }

        SynchronizedCollection(Collection<E> c, Object mutex) {
            this.c = Objects.requireNonNull(c);
            this.mutex = Objects.requireNonNull(mutex);
        }

        public int size() {
            synchronized (mutex) {return c.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return c.isEmpty();}
        }
        public boolean contains(Object o) {
            synchronized (mutex) {return c.contains(o);}
        }
        public Object[] toArray() {
            synchronized (mutex) {return c.toArray();}
        }
        public <T> T[] toArray(T[] a) {
            synchronized (mutex) {return c.toArray(a);}
        }

        public Iterator<E> iterator() {
            return c.iterator(); // Must be manually synched by user!
        }

        public boolean add(E e) {
            synchronized (mutex) {return c.add(e);}
        }
        public boolean remove(Object o) {
            synchronized (mutex) {return c.remove(o);}
        }

        public boolean containsAll(Collection<?> coll) {
            synchronized (mutex) {return c.containsAll(coll);}
        }
        public boolean addAll(Collection<? extends E> coll) {
            synchronized (mutex) {return c.addAll(coll);}
        }
        public boolean removeAll(Collection<?> coll) {
            synchronized (mutex) {return c.removeAll(coll);}
        }
        public boolean retainAll(Collection<?> coll) {
            synchronized (mutex) {return c.retainAll(coll);}
        }
        public void clear() {
            synchronized (mutex) {c.clear();}
        }
        public String toString() {
            synchronized (mutex) {return c.toString();}
        }
        // Override default methods in Collection
        @Override
        public void forEach(Consumer<? super E> consumer) {
            synchronized (mutex) {c.forEach(consumer);}
        }
        @Override
        public boolean removeIf(Predicate<? super E> filter) {
            synchronized (mutex) {return c.removeIf(filter);}
        }
        @Override
        public Spliterator<E> spliterator() {
            return c.spliterator(); // Must be manually synched by user!
        }
        @Override
        public Stream<E> stream() {
            return c.stream(); // Must be manually synched by user!
        }
        @Override
        public Stream<E> parallelStream() {
            return c.parallelStream(); // Must be manually synched by user!
        }
        private void writeObject(ObjectOutputStream s) throws IOException {
            synchronized (mutex) {s.defaultWriteObject();}
        }
    }

Objects

對於 Objects,我們經常使用的就是兩個場景,相等判斷和判空。

相等判斷

Objects 提供 equals 和 deepEquals 兩個方法來進行相等判斷。
equals:判斷基本類型和自定義類
deepEquals :判斷數組
從以下源碼中,可以看出 Objects 對基本類型和複雜類型的對象,都有着比較細粒度的判斷,可以放心使用。

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}
public static boolean deepEquals(Object a, Object b) {
    if (a == b)
        return true;
    else if (a == null || b == null)
        return false;
    else
        // 實際是調用了 deepEquals0 方法
        return Arrays.deepEquals0(a, b);
}

static boolean deepEquals0(Object e1, Object e2) {
        assert e1 != null;
        boolean eq;
        if (e1 instanceof Object[] && e2 instanceof Object[])
            eq = deepEquals ((Object[]) e1, (Object[]) e2);
        else if (e1 instanceof byte[] && e2 instanceof byte[])
            eq = equals((byte[]) e1, (byte[]) e2);
        else if (e1 instanceof short[] && e2 instanceof short[])
            eq = equals((short[]) e1, (short[]) e2);
        else if (e1 instanceof int[] && e2 instanceof int[])
            eq = equals((int[]) e1, (int[]) e2);
        else if (e1 instanceof long[] && e2 instanceof long[])
            eq = equals((long[]) e1, (long[]) e2);
        else if (e1 instanceof char[] && e2 instanceof char[])
            eq = equals((char[]) e1, (char[]) e2);
        else if (e1 instanceof float[] && e2 instanceof float[])
            eq = equals((float[]) e1, (float[]) e2);
        else if (e1 instanceof double[] && e2 instanceof double[])
            eq = equals((double[]) e1, (double[]) e2);
        else if (e1 instanceof boolean[] && e2 instanceof boolean[])
            eq = equals((boolean[]) e1, (boolean[]) e2);
        else
            eq = e1.equals(e2);
        return eq;
    }

爲空判斷

Objects 提供 isNullrequireNonNull 兩個方法來進行爲空判斷。
requireNonNull 方法,傳入的參數一旦爲空,會直接拋出異常,我們需要在實際場景中進行選擇。

public static boolean isNull(Object obj) {
    return obj == null;
}

public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
    if (obj == null)
        throw new NullPointerException(messageSupplier.get());
    return obj;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章