[java复习] 数组 + Arrays

这一章的复习主要涵盖 数组的基本性质数组和泛型java.util.Arrays 工具类。这里整理的都是个人觉得比较重要重新认真分析的东西,不一定全部为基础,如果有不准确的地方,还望大佬从评论区指出,感激不尽。

文章中的所有的自定义的测试都在自己的 java-review 仓库中,本文对应的链接:
dd

数组

性质

  1. java数组是边界安全的

    不同于 c++,当数组的下标越出边界的时候会抛出一个 ArrayIndexOutOfBoundsException 数组越界的异常,后来用了 c++ 才直到 java 的好,c++ 中越界的时候会得到一个为止的数,数组开小的时候就会出现这样的问题,不会报错可以正常编译运行还有结果,但是结果变得让人匪夷所思,根本不知道错在了哪

  2. 数组为一级对象

    数组的初始化中,出来第一维度以外的维度都彼此独立,相当于一个数组对象中有很多的数组引用的成员变量,引用的初始化可以延时完成。创建了的数组的数组成员都是默认的零值,这也就是为什么使用创建类的数组不会触发类的初始化的原因

数组为一级对象

前面有提到过,数组对象是虚拟机创建的,数组标识符只是一个引用

一个数组对象所持有的所有的数组元素其实就是相当于 一个对象持有的成员变量,他们初始化还非常类似

数组元素的初始化就像是创建一个对象实例的时候,类的所有的成员变量都会被初始化为他们的零值(参考 [java复习]基本类型 那篇文章中类型的零值),数组中的初始化都会被初始化为对应的零值,比如: int 会初始化为 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// java test

/*
测试数组为一维对象
*/
private static void arrayCreate() {
int[] arr; // 声明数组引用
arr = new int[5];

int[][] arr2D = new int[2][];
arr2D[0] = new int[4];
arr2D[1] = new int[2];
for (int i = 0; i < arr2D.length; i++) {
log("arr2D[" + i + "].length: " + arr2D[i].length);
}
arr2D[4] = null;

/* Output:
arr2D[0].length: 4
arr2D[1].length: 2
*///:~
}

从上面的例子中,可以看出 new 一个数组对象,只需要关注最外层的数组长度就好了,因为数组的内部就相当于一个数组引用,类似于 int[] arr
2D 数组 为例,其另一个维度不指定的时候,就相当于了声明了一些数组引用,所以它的另一个维度不需要在声明的时候就马上确定下来,使得 另一个维度的每个数组的长度可以不一样,可以像 arr 一样,在后面进行 arr = new int[5]; 这种初始化

泛型与泛型

java 中的泛型和数组不能够很好的结合

类似于泛型容器的创建方式,在前面的泛型中有介绍到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// java test

static class Inner<E> {
private Random random = new Random();

private E[] es = (E[])new Object[5];
private Object[] os = new Object[5];

/*
测试泛型数组
*/
private void genericArray() {
E[] es1 = (E[])new Object[5];
}

/*
E[] 泛型数组直接获取 泛型对象
*/
private E getE() {
return es[random.nextInt(es.length)];
}

/*
Object[] obj数组获取数组元素强制转换为 泛型对象
*/
private E getO() {
return (E)os[random.nextInt(os.length)];
}
}

/*
测试创建 泛型类的数组
*/
public static void main(String[] args) {
Inner[] ins1 = new Inner[5];

Inner<Integer>[] ins2 = new Inner[5];
Inner<Integer>[] ins3 = (Inner<Integer>[])new Inner[5];

Inner<Integer>[] ins4 = (Inner<Integer>[])ins1;

//! Inner<Integer>[] ins5 = new Inner<Integer>[5]; //! Error

log("ins1: " + ins1);
log("ins2: " + ins2);
log("ins3: " + ins3);
log("ins4: " + ins4);

/* Output:
ins1: [Lcom.example.review.array.Generic$Inner;@10f87f48
ins2: [Lcom.example.review.array.Generic$Inner;@2f4d3709
ins3: [Lcom.example.review.array.Generic$Inner;@4e50df2e
ins4: [Lcom.example.review.array.Generic$Inner;@10f87f48
*///:~
}

Arrays

asList()

Arrays # asList 是一个泛型方法,将一个数组转化为 ArrayList<E>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// java 12 source

public class Arrays {

/** Returns a fixed-size list backed by the specified array. */
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}

/**
* @serial include
*/
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;

ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}

/** ... */
}


private static class ArrayItr<E> implements Iterator<E> {
private int cursor;
private final E[] a; // 同样为 fianl 不可变引用

ArrayItr(E[] a) {
this.a = a;
}

@Override
public boolean hasNext() {
return cursor < a.length;
}

@Override
public E next() {
int i = cursor;
if (i >= a.length) {
throw new NoSuchElementException();
}
cursor = i + 1;
return a[i];
}
}

}

这里得到并不是 java.util.ArrayList,而是 Arrays 的内部类,不同于 java.util.ArrayListArrays 的内部类只实现了 AbstractList 的一部分方法(如图所示),对于类似于 add(E e)remove(E e) 方法并没有进行实现,调用这些方法的时候就会调用父类 AbstractList 中的方法抛出 UnsupportedOperationException 这个运行时异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// java 12 source

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {

public boolean add(E e) {
add(size(), e);
return true;
}

public void add(int index, E element) {
throw new UnsupportedOperationException();
}

}

我们习惯上称 Arrays # asList 得到的 List 不能进行修改容量的操作,上述就是原因之一,另一个原因就是 Arrays.ArrayList 中用来保存元素的数组为 final E[] a 注定了这个数组是不能够对其容量进行修改,这两点就是 Arrays # asList 得到的容器无法进行关于容量操作的原因。

  1. Arrays.ArrayList 没有实现 AbstractList 中的一部分方法,如: AbstarctList # addAbstarctList # remove

  2. Arrays.ArrayList 的成员变量 final E[] a 为不可变引用,无法改变其指向的数组

  3. Arrays.ArrayList # iterator 方法得到的迭代器

copyOf()

将数组复制到一个指定长度的新数组并返回,底层通过 System # arraycopy 来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// java 12 source

public final class System {

@HotSpotIntrinsicCandidate
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);

}


public class Arrays {

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

}
  1. Arrays # copyOf 会创建一个 newLength 大小的数组,并取 original.length 原数组长度和 newLength 中的较小值的那一个

  2. System # arraycopy 对相同数组进行拷贝的时候前面的已经复制的值不会出现覆盖情况,同样不能越界

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// test

private static void systemArrayCopy() {
Integer[] arrSc = {1, 2, 3, 4, 5, 6};
System.arraycopy(arrSc, 0, arrSc, 2, arrSc.length - 2);
printArr(arrSc);
Integer[] arr = {1, 2, 3};
arr = Arrays.copyOf(arr, 4);
printArr(arr);
/* Output:
[1, 2, 1, 2, 3, 4]
[1, 2, 3, null]
*///:~
}