大家好,我是指北君。
在本文中,我们来看看 java.util.Arrays ,我们可以使用 Arrays 创建,比较,排序,搜索,stream 和转化数组。
创建 我们来看看,使用Arrays 怎么创建一个新的数组,一般来说,我们可以使用Arrays 的 copyOf , copyOfRange 和 fill 方法。
copyOf 和 copyOfRange 要使用copyOfRange,我们需要一个原始数组和我们想要复制的开始索引(包括)和结束索引(不包括)。 我们先定一个数组 intro。
复制 String[ ] intro = new String[ ] { "once" , "upon" , "a" , "time" } ;
String[ ] abridgement = Arrays.copyOfRange ( storyIntro, 0 , 3 ) ;
assertArrayEquals( new String[ ] { "once" , "upon" , "a" } , abridgement) ;
assertFalse( Arrays.equals ( intro, abridgement) ) ;
要使用 copyOf ,,我们需要使用intro和一个目标数组大小,然后我们会得到一个该长度的新数组。
复制 String[ ] revised = Arrays.copyOf ( intro, 3 ) ;
String[ ] expanded = Arrays.copyOf ( intro, 5 ) ;
assertArrayEquals( Arrays.copyOfRange ( intro, 0 , 3 ) , revised) ;
assertNull( expanded[ 4 ] ) ;
注意,如果我们的目标尺寸大于原始尺寸,copyOf会用 null 填充数组。
fill 另一种方法,我们可以创建一个固定长度的数组,就是填充,当我们想要一个所有元素都相同的数组时,这个方法很有用。
复制 String[ ] stutter = new String[ 3 ] ;
Arrays.fill ( stutter, "once" ) ;
assertTrue( Stream.of ( stutter) .allMatch ( el -> "once" .equals ( el) ) ;
注意,我们需要事先将数组实例化,而不是像String[] filled = Arrays.fill("once", 3);,因为这个特性是在语言中出现泛型之前引入的。
比较 我们先走来看看 Arrays 的比较方法
equals 和 deepEquals 我们可以使用 equals 进行简单的数组大小和内容比较。 如果我们添加一个null作为其中一个元素,内容检查就会失败。
复制 assertTrue( Arrays.equals ( new String[ ] { "once" , "upon" , "a" , "time" } , intro) ) ;
assertFalse( Arrays.equals ( new String[ ] { "once" , "upon" , "a" , null } , intro) ) ;
当我们有嵌套或多维数组时,我们可以使用deepEquals不仅检查顶层元素,还可以递归地执行检查。
复制 Object[ ] story = new Object[ ] { intro, new String[ ] { "chapter one" , "chapter two" } , end } ;
Object[ ] copy = new Object[ ] { intro, new String[ ] { "chapter one" , "chapter two" } , end } ;
assertTrue( Arrays.deepEquals ( story, copy) ) ;
assertFalse( Arrays.equals ( story, copy) ) ;
注意,这里 deepEquals 是通过的,但equals却失败了。这是因为deepEquals在每次遇到数组时都会调用自己,而equals只是比较子数组的引用。
hashCode 和 deepHashCode 我们使用hashCode来计算一个基于数组内容的整数
复制 Object[ ] looping = new Object[ ] { intro, intro } ;
int hashBefore = Arrays.hashCode ( looping) ;
int deepHashBefore = Arrays.deepHashCode ( looping) ;
现在,我们将原数组的一个元素设置为空,并重新计算哈希值。
复制 intro[ 3 ] = null ;
int hashAfter = Arrays.hashCode ( looping) ;
deepHashCode检查嵌套数组的元素数量和内容是否匹配。 如果我们用deepHashCode重新计算。
复制 int deepHashAfter = Arrays.deepHashCode ( looping) ;
现在,我们能够看到这两个方法的不同。
复制 assertEquals( hashAfter, hashBefore) ;
assertNotEquals( deepHashAfter, deepHashBefore) ;
deepHashCode是我们在数组上使用HashMap和HashSet等数据结构时使用的基础计算。
排序和搜索 排序 如果我们的元素是原始类型,或者它们实现了 Comparable 接口,我们可以使用sort来执行排序。
复制 String[ ] sorted = Arrays.copyOf ( intro, 4 ) ;
Arrays.sort ( sorted) ;
assertArrayEquals( new String[ ] { "a" , "once" , "time" , "upon" } , sorted) ;
请注意,排序会使原始引用发生变化,这就是为什么我们在这里进行复制。排序将对不同的数组元素类型使用不同的算法。原始类型使用quicksort,对象类型使用Timsort。对于一个随机排序的数组,两者的平均情况都是O(n log(n))。从Java 8开始,parallelSort可用于并行排序, 它提供了一种使用几个Arrays.sort任务的并发排序方法。
搜索 如果我们有一个排序的数组,那么我们可以在 O(log n) 中完成,我们可以用 binarySearch 来完成这样的任务。
复制 int exact = Arrays.binarySearch ( sorted, "time" ) ;
int caseInsensitive = Arrays.binarySearch ( sorted, "TiMe" , String:: compareToIgnoreCase) ;
assertEquals( "time" , sorted[ exact] ) ;
assertEquals( 2 , exact) ;
assertEquals( exact, caseInsensitive) ;
如果我们没有提供一个比较器作为第三个参数,那么 binarySearch 就默认我们的元素类型是可比较的。如果我们的数组没有被首先排序,那么 binarySearch 将不会像我们所期望的那样工作。
流 我们都知道Arrays在Java 8中进行了更新,包含了Stream API的方法,如parallelSort、stream和setAll等。
stream 使我们能够完全访问我们的数组的Stream API。
复制 Assert.assertEquals ( Arrays.stream ( intro) .count ( ) , 4 ) ;
exception.expect ( ArrayIndexOutOfBoundsException.class ) ;
Arrays.stream ( intro, 2 , 1 ) .count ( ) ;
我们可以为流提供包容性和排他性指数,但是如果指数失序、为负数或超出范围,我们应该判断 ArrayIndexOutOfBoundsException。
转化 toString、asList和setAll给了我们几种不同的方法来转换数组。
toString和deepToString 我们可以通过toString获得原始数组的可读版本的一个好方法。
复制 assertEquals( "[once, upon, a, time]" , Arrays.toString ( storyIntro) ) ;
当数组有嵌套的时候,我们必须再次使用deepToString 来打印嵌套数组的内容。
复制 assertEquals(
"[[once, upon, a, time], [chapter one, chapter two], [the, end]]" ,
Arrays.deepToString ( story) ) ;
asList 在所有的数组方法中,最方便我们使用的是asList。我们有一个简单的方法把数组变成一个列表。
复制 List< String> rets = Arrays.asList ( storyIntro) ;
assertTrue( rets.contains ( "upon" ) ) ;
assertTrue( rets.contains ( "time" ) ) ;
assertEquals( rets.size ( ) , 4 ) ;
返回的列表将是一个固定的长度,而且无法添加或删除元素,还要注意的是,asList会返回这个ArrayList的类型,和我们平常在使用的ArrayList 并不一样。在调试的时候,就可能是非常具有欺骗性的,我们在写的过程中特别要注意。
setAll 通过setAll,我们可以用一个 functional interface 来设置一个数组的所有元素。下面的代码现将位置索引作为一个参数传入到getWord 方法中。
复制 String[ ] longAgo = new String[ 4 ] ;
Arrays.setAll ( longAgo, i -> this.getWord ( i) ) ;
assertArrayEquals( longAgo, new String[ ] { "a" , "long" , "time" , "ago" } ) ;
当然,异常处理是使用lambda的一个比较棘手的部分。所以请记住,如果lambda抛出一个异常,那么Java就不会定义数组的最终状态。