在文章开头,我们先看看工具类通用的特征:
构造器必须是私有的。这样的话,工具类就无法被 new 出来,因为工具类在使用的时候,无需初始化,直接使用即可,所以不会开放出构造器出来。工具类的工具方法必须被 static、final 关键字修饰。这样的话就可以保证方法不可变,并且可以直接使用,非常方便。我们需要注意的是,尽量不在工具方法中,对共享变量有做修改的操作访问(如果必须要做的话,必须加锁),因为会有线程安全的问题。除此之外,工具类方法本身是没有线程安全问题的,可以放心使用。
Arrays 主要对数组提供了一些高效的操作,比如说排序、查找、填充、拷贝、相等判断等等
将数组转化为字符串,也可以看做是输出当前数组所有元素。下面是 toString 方法的源码:
// 这里除了int外,Arrays还提供了 double、long、char等... public static String toString(int[] a) { if (a == null) return "null"; int iMax = a.length - 1; if (iMax == -1) return "[]"; // 通过一个StringBuilder来拼接数组中的所有元素 StringBuilder b = new StringBuilder(); // 左边 [ b.append('['); for (int i = 0; ; i++) { b.append(a[i]); // 如果数组遍历完了,加上右边],然后toString转为String if (i == iMax) return b.append(']').toString(); b.append(", "); } }数组排序, 使用了双轴快速排序算法,效率高
排序算法分为以下几种情况:
如果数组长度大于等于286且连续性好(有序程度高),就用归并排序如果大于等于286且连续性不好(有序程度低),就用双轴快速排序如果长度小于286且大于等于47,就用双轴快速排序如果长度小于47,就用插入排序 入参支持 int、long、double 等各种基本类型的数组同样支持自定义类的数组,但必须要有比较器 实现Comparable接口,重写compareTo方法传入Comparator比较器,重写comparing方法下面是一个自定义类型的代码示例:
// 自定义类 class SortDTO { private String sortTarget; public SortDTO(String sortTarget) { this.sortTarget = sortTarget; } public String getSortTarget() { return sortTarget; } @Override public String toString() { return "SortDTO{" + "sortTarget='" + sortTarget + '\'' + '}'; } } public void testSort(){ List<SortDTO> list = new ArrayList<SortDTO>(){ { add(new SortDTO("test01")); add(new SortDTO("test03")); add(new SortDTO("test02")); } }; // 先把数组的大小初始化成 list 的大小,保证能够正确执行 toArray SortDTO[] array = new SortDTO[list.size()]; list.toArray(array); System.out.println("排序之前:" + Arrays.toString(array)); // sort 需要传入自定义比较器 // Arrays.sort(array, (a, b) -> { // return a.getSortTarget().compareTo(b.getSortTarget()); // }); Arrays.sort(array, Comparator.comparing(SortDTO::getSortTarget)); System.out.println("排序之后:" + Arrays.toString(array)); }执行结果
排序之前:[SortDTO{sortTarget='test01'}, SortDTO{sortTarget='test03'}, SortDTO{sortTarget='test02'}] 排序之后:[SortDTO{sortTarget='test01'}, SortDTO{sortTarget='test02'}, SortDTO{sortTarget='test03'}]二分查找。
支持的入参类型非常多,如 byte、int、long 各种类型的数组
若被搜索的数组是无序的,一定要先排序,否则二分搜索很有可能搜索不到
返回参数是查找到的对应数组下标的值,如果查询不到,则返回负数
下面是一个自定义类的代码示例:
public void testBinarySearch(){ List<SortDTO> list = new ArrayList<SortDTO>(){ { add(new SortDTO("100")); add(new SortDTO("300")); add(new SortDTO("500")); add(new SortDTO("200")); } }; SortDTO[] array = new SortDTO[list.size()]; list.toArray(array); System.out.println("搜索之前:" + Arrays.toString(array)); // 必须要先排序 Arrays.sort(array, Comparator.comparing(SortDTO::getSortTarget)); System.out.println("先排序,结果为:" + Arrays.toString(array)); // 将排序好的数组传入,若是自定义类型还要传入比较器 int index = Arrays.binarySearch(array, new SortDTO("200"), Comparator.comparing(SortDTO::getSortTarget)); // 返回的index小于0表示没找到 if(index<0){ throw new RuntimeException("没有找到200"); } System.out.println("搜索结果:" + array[index]); }执行结果
搜索之前:[SortDTO{sortTarget='100'}, SortDTO{sortTarget='300'}, SortDTO{sortTarget='500'}, SortDTO{sortTarget='200'}] 先排序,结果为:[SortDTO{sortTarget='100'}, SortDTO{sortTarget='200'}, SortDTO{sortTarget='300'}, SortDTO{sortTarget='500'}] 搜索结果:SortDTO{sortTarget='200'}接下来,我们来看下二分法底层代码的实现:
二分的主要意思是每次查找之前,都找到中间值,然后拿我们要比较的值和中间值比较,根据结果修改比较的上限或者下限,通过循环最终找到相等的位置索引
// 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. }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; }asList 的作用是将数组转换为 List
List list = Arrays.asList("a","b","c")这里注意, Arrays.asList 在使用时有两个坑:
坑 1:数组被修改后,会直接影响到新 List 的值。坑 2:不能对新 List 进行 add、remove 等操作,否则运行时会报 UnsupportedOperationException 错误。 public void testArrayToList(){ Integer[] array = new Integer[]{1,2,3,4,5,6}; List<Integer> list = Arrays.asList(array); // 坑1:修改数组的值,会直接影响原 list log.info("数组被修改之前,集合第一个元素为:{}",list.get(0)); array[0] = 10; log.info("数组被修改之前,集合第一个元素为:{}",list.get(0)); // 坑2:使用 add、remove 等操作 list 的方法时, // 会报 UnsupportedOperationException 异常 list.add(7); }我们就从源码中看看这两个坑从何来:
从上图中,我们可以发现Arrays.asList 方法返回的 List 并不是 java.util.ArrayList,而是自己内部的一个静态类
该静态类没有自己创建一个数组,而是直接持有数组的引用,所以修改数组,该list就会改变并且没有实现 add、remove 等方法,所以调用add,remove时会抛出异常若是要变成 java.util.ArrayList 可以 new ArrayList(Arrays.asList(arr)),即 array–> Collection --> ArrayList
