Arrays、Collections、Objects 常用方法源码解析

1 工具类通用的特征

工具类通用的特征写法:

  1. 构造器必须是私有的。这样的话,工具类就无法被 new 出来,因为工具类在使用的时候,无需初始化,直接使用即可,所以不会开放出构造器出来。
  2. 工具类的工具方法必须被 static、final 关键字修饰。这样的话就可以保证方法不可变,并且可以直接使用,非常方便。

注意: 尽量不在工具方法中,对共享变量有做修改的操作访问(如果必须要做的话,必须加锁),因为会有线程安全的问题。除此之外,工具类方法本身是没有线程安全问题的,可以放心使用。

2 Arrays

Arrays 主要对数组提供了一些高效的操作,比如说排序、查找、填充、拷贝、相等判断等等。

2.1 排序

Arrays.sort 方法主要用于排序,入参支持 int、long、double 等各种基本类型的数组,也支持自定义类的数组。

int型数组sort()排序源码:

public static void sort(int[] a) {
		//双轴快速排序算法
        DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
    }
    
    //对数组a 内下标为fromIndex到toIndex的元素排序
public static void sort(long[] a, int fromIndex, int toIndex) {
		//检测范围是否合理
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
    }

基本类型的数组de sort 方法排序的性能较高,主要原因是 sort 使用了双轴快速排序算法
自定义类的数组sort()源码

public static <T> void sort(T[] a, int fromIndex, int toIndex,
                               Comparator<? super T> c) {
            //外部的排序器为空使用下面的sort方法         
       if (c == null) {
           sort(a, fromIndex, toIndex);
       } else {
           rangeCheck(a.length, fromIndex, toIndex);
           if (LegacyMergeSort.userRequested)
           	//使用归并排序
               legacyMergeSort(a, fromIndex, toIndex, c);
           else
               TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
       }
   }

public static <T> void sort(T[] a, Comparator<? super T> c) {
       if (c == null) {
           sort(a);
       } else {
           if (LegacyMergeSort.userRequested)
               legacyMergeSort(a, c);
           else
               TimSort.sort(a, 0, a.length, c, null, 0, 0);
       }
   }

基本数据类型的sort排序结果只能小到大。如果我们要从大到小,就要使用这种方式
支持入参 外部的排序器Comparator。其内部排序的方法是使用归并排序

2.1 二分查找法

Arrays.binarySearch 方法主要用于快速从数组中查找出对应的值。其支持的入参类型非常多,如 byte、int、long 各种类型的数组。返回参数是查找到的对应数组下标的值,如果查询不到,则返回负数。
在这里插入图片描述

使用binarySearch注意两点:

  1. 如果被搜索的数组是无序的,一定要先排序,否则二分搜索很有可能搜索不到,我们 demo 里面也先对数组进行了排序;
  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.
}

二分的主要意思是每次查找之前,都找到中间值,然后拿要比较的值和中间值比较,根据结果修改比较的上限或者下限,通过循环最终找到相等的位置索引。

2.2 拷贝

数组拷贝经常遇到,有时需要拷贝整个数组,有时需要拷贝部分,比如 ArrayList 在 add(扩容) 或 remove(删除元素不是最后一个) 操作时,会进行一些拷贝。拷贝整个数组可以使用 copyOf 方法拷贝部分可以使用 copyOfRange 方法,看下底层源码的实现:

// 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;
}

public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

从源码中,我们发现,Arrays 的拷贝方法,实际上底层调用的是 System.arraycopy 这个 native 方法,如果你自己对底层拷贝方法比较熟悉的话,也可以直接使用。

3 Collections

Collections 是为了方便使用集合而产生的工具类,Arrays 方便数组使用,Collections 是方便集合使用。

Collections 也提供了 sort 和 binarySearch 方法,sort 底层使用的就是 Arrays.sort 方法,binarySearch 底层是自己重写了二分查找算法,实现的逻辑和 Arrays 的二分查找算法完全一致,这两个方法上 Collections 和 Arrays 的内部实现很类似。

3.1 求集合中最大、小值

提供了 max 方法来取得集合中的最大值,min 方法来取得集合中的最小值,max 和 min 方法很相似的,我们以 max 方法为例来说明一下,max 提供了两种类型的方法,一个需要传外部排序器,一个不需要传排序器,但需要集合中的元素强制实现 Comparable 接口,后者的泛型定义很有意思,我们来看下(从右往左看):
在这里插入图片描述

max 方法泛型 T 定义得非常巧妙,意思是泛型必须继承 Object 并且实现 Comparable 的接口。泛型直接定义了必须实现 Comparable 接口,在编译的时候就可告诉使用者,当前类没有实现 Comparable 接口,使用起来很友好。

3.2 多种类型的集合

Collections 对原始集合类进行了封装,提供了更好的集合类,一种是线程安全的集合,一种是不可变的集合,针对 List、Map、Set 都有提供:

3.2.1 线程安全的集合

线程安全的集合方法都是 synchronized 打头的,如下:在这里插入图片描述
从方法命名可以看出来,底层是通过 synchronized 轻量锁来实现的
synchronizedList 底层的实现:在这里插入图片描述
图片描述可以看到 List 的所有操作方法都被加上了 synchronized 锁,所以多线程对集合同时进行操作,是线程安全的。

3.2.1 不可变的集合

得到不可变集合的方法都是以 unmodifiable 开头的。这类方法的意思是,会从原集合中,得到一个不可变的新集合,新集合只能访问,无法修改;一旦修改,就会抛出异常。这主要是因为只开放了查询方法,其余任何修改操作都会抛出异常
unmodifiableList 底层实现机制:
在这里插入图片描述

4 Objects

Objec经常使用的就是两个场景,相等判断和判空。

4.1 相等判断

Objects 有提供 equals 和 deepEquals 两个方法来进行相等判断,前者是判断基本类型和自定义类的,后者是用来判断数组的
在这里插入图片描述
图片描述从源码中,可以看出 Objects 对基本类型和复杂类型的对象,都有着比较细粒度的判断,可以放心使用。

每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。
引用数据类型的比较不需要重写equals()方法,但引用类型的比较需要重写equals()方法
由上方源码看出 未重写的equals方法效果与 ==效果一致
重写了的equals课参考String类里的equals()方法

4.2 为空判断

在这里插入图片描述
图片描述Objects 提供了各种关于空的一些判断,isNull 和 nonNull 对于对象是否为空返回 Boolean 值,requireNonNull 方法更加严格,如果一旦为空,会直接抛出异常,需要根据生活的场景选择使用。

5 面试题

5.1 工作中有没有遇到特别好用的工具类,如何写好一个工具类

答:有的,像 Arrays 的排序、二分查找、Collections 的不可变、线程安全集合类、Objects 的判空相等判断等等工具类,好的工具类肯定很好用,比如说使用 static final 关键字对方法进行修饰,工具类构造器必须是私有等等手段来写好工具类。

5.2 写一个二分查找算法的实现

答:可以参考 Arrays 的 binarySearch 方法的源码实现。

5.3 如果我希望 ArrayList 初始化之后,不能被修改,该怎么办

答:可以使用 Collections 的 unmodifiableList 的方法,该方法会返回一个不能被修改的内部类集合,这些集合类只开放查询的方法,对于调用修改集合的方法会直接抛出异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Arrays类中提供了很多常用方法,下面列举一些比较常用的: 1. sort()方法:用于对数组进行排序,默认是升序排序,也可以传入一个Comparator对象以实现自定义排序。 2. binarySearch()方法:用于在已排序的数组中查找指定元素,如果找到了返回该元素的索引值,否则返回负数。 3. equals()方法:用于比较两个数组是否相等。 4. fill()方法:用于将数组中的所有元素都赋成同一个值。 5. copyOf()方法:用于复制一个数组,可以指定复制的长度,如果原数组长度不够,则用默认值补齐。 6. asList()方法:将数组转换为一个List对象。 7. toString()方法:将数组转换为一个字符串,方便输出和打印。 示例代码如下: ```java int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3}; //排序 Arrays.sort(arr); System.out.println(Arrays.toString(arr)); //[1, 1, 2, 3, 3, 4, 5, 5, 6, 9] //查找 int index = Arrays.binarySearch(arr, 6); System.out.println("6的索引值为:" + index); //输出:6的索引值为:8 //比较 int[] arr1 = {1, 2, 3}; int[] arr2 = {1, 2, 3}; boolean isEqual = Arrays.equals(arr1, arr2); System.out.println("arr1和arr2是否相等:" + isEqual); //输出:arr1和arr2是否相等:true //赋值 Arrays.fill(arr, 0); System.out.println(Arrays.toString(arr)); //[0, 0, 0, 0, 0, 0, 0, 0, 0, 0] //复制 int[] arr3 = Arrays.copyOf(arr, 5); System.out.println(Arrays.toString(arr3)); //[0, 0, 0, 0, 0] //转换为List List<int[]> list = Arrays.asList(arr); System.out.println(list.size()); //输出:1 //转换为字符串 String str = Arrays.toString(arr); System.out.println(str); //[0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值