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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章