一、深入浅出
demo1:首先来观察数组的父类与类名
public class Test {
public static void main(String[] args) {
int[] array = new int[10];
System.out.println("array的父类是:" + array.getClass().getSuperclass());
System.out.println("array的类名是:" + array.getClass().getName());
}
}
-------Output:
array的父类是:class java.lang.Object
array的类名是:[I
由此可见:
a、数组的是Object的直接子类,但是它又与普通的java对象存在很大的不同
b、在JDK中没有[I类,而且"[I”也不是一个合法标识符,可见这里进行了特殊处理
demo2:通过对比多维数组来分析"[I”是什么鬼
public class Test {
public static void main(String[] args) {
int[] array_00 = new int[10];
System.out.println("一维数组:" + array_00.getClass().getName());
int[][] array_01 = new int[10][10];
System.out.println("二维数组:" + array_01.getClass().getName());
int[][][] array_02 = new int[10][10][10];
System.out.println("三维数组:" + array_02.getClass().getName());
}
}
-----------------Output:
一维数组:[I
二维数组:[[I
三维数组:[[[I
由此可见:
a、[代表了数组的维度,一个[表示一维,两个[表示二维。
b、数组的类名由若干个'['和数组元素类型的内部名称组成。
demo3:通过反射机制来获得全限定名
public class Test {
public static void main(String[] args) {
System.out.println("Object[]:" + Object[].class);
System.out.println("Object[][]:" + Object[][].class);
System.err.println("Object[][][]:" + Object[][][].class);
System.out.println("Object:" + Object.class);
}
}
---------Output:
Object[]:class [Ljava.lang.Object;
Object[][]:class [[Ljava.lang.Object;
Object[][][]:class [[[Ljava.lang.Object;
Object:class java.lang.Object
由此可见:
a、普通的java类是以全限定路径名+类名来作为自己的唯一标示的
b、而数组则是以若干个[+L+数组元素类全限定路径+类来最为唯一标示的。
demo3:通过反射机制来得到数组的内部结构
public class Test {
public static void main(String[] args) {
int[] array = new int[10];
Class clazz = array.getClass();
System.out.println(clazz.getDeclaredFields().length);
System.out.println(clazz.getDeclaredMethods().length);
System.out.println(clazz.getDeclaredConstructors().length);
System.out.println(clazz.getDeclaredAnnotations().length);
System.out.println(clazz.getDeclaredClasses().length);
}
}
----------------Output:
0
0
0
0
0
由此可见:
a、[I没有何成员变量、成员方法、构造函数、Annotation甚至连length成员变量这个都没有,它就是一个彻彻底底的空类。
b、既无length,那何来的array.length?
demo4:编译一个主程序,得到他的字节码
public class Main {
public static void main(String[] args) {
int a[] = new int[2];
int i = a.length;
}
}
0 iconst_2 //将int型常量2压入操作数栈
1 newarray 10 (int) //将2弹出操作数栈,作为长度,创建一个元素类型为int, 维度为1的数组,并将数组的引用压入操作数栈
3 astore_1 //将数组的引用从操作数栈中弹出,保存在索引为1的局部变量(即a)中
4 aload_1 //将索引为1的局部变量(即a)压入操作数栈
5 arraylength //从操作数栈弹出数组引用(即a),并获取其长度(JVM负责实现如何获取),并将长度压入操作数栈
6 istore_2 //将数组长度从操作数栈弹出,保存在索引为2的局部变量(即i)中
7 return //main方法返回
观察推演:
a、通过:arraylength这条指令是用来获取数组的长度
b、所以说JVM对数组的长度做了特殊的处理,它是通过arraylength这条指令来实现的。
二、数组的使用方法
数组的使用分为四个步骤:声明数组、分配空间、赋值、处理。
- 声明数组:
即声明类型,有两种形式:int[] array、int array[]。
- 分配空间:
分配连续的空间,array = new int[10];
- 赋值:
在已经分配的空间里面放入数据。array[0] = 1 、array[1] = 2……
- 处理
对数组元素进行操作。通过数组名+有效的下标来确认数据。
- 初始化
但其实分配空间和赋值是一起进行的,也就是完成数组的初始化。有如下三种形式:
int a[] = new int[2]; //默认为0,如果是引用数据类型就为null
int b[] = new int[] {1,2,3,4,5};
int c[] = {1,2,3,4,5};
三、性能?请优先考虑数组
a、在java中有很多方式来存储一系列数据,但数组是一种效率最高的存储和随机访问对象引用序列的方式。
b、集合类的底层也都是通过数组来实现的。
--------这是ArrayList的add()------
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
demo
Long time1 = System.currentTimeMillis();
for(int i = 0 ; i < 100000000 ;i++){
sum += arrays[i%10];
}
Long time2 = System.currentTimeMillis();
System.out.println("数组求和所花费时间:" + (time2 - time1) + "毫秒");
Long time3 = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
sum += list.get(i%10);
}
Long time4 = System.currentTimeMillis();
System.out.println("List求和所花费时间:" + (time4 - time3) + "毫秒");
--------------Output:
数组求和所花费时间:696毫秒
List求和所花费时间:3498毫秒
分析:
list.get(i)这个动作用于进行拆箱,Integer对象通过intValue方法自动转换成一个int基本类型,这里的消耗是巨大的
四、变长数组?
利用List集合add方法里面的扩容思路来模拟实现
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
/**
* 若当前需要的长度超过数组长度时进行扩容处理
*/
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3) / 2 + 1; //扩容
if (newCapacity < minCapacity)
newCapacity = minCapacity;
//拷贝数组,生成新的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
五、copyOf()浅拷贝
通过Arrays.copyOf()方法产生的数组是一个浅拷贝。同时数组的clone()方法也是,集合的clone()方法也是,所以我们在使用拷贝方法的同时一定要注意浅拷贝这问题。
public class Test {
public static void main(String[] args) {
Person person_01 = new Person("chenssy_01");
Person[] persons1 = new Person[]{person_01};
Person[] persons2 = Arrays.copyOf(persons1,persons1.length);
System.out.println("数组persons1:");
display(persons1);
System.out.println("---------------------");
System.out.println("数组persons2:");
display(persons2);
//改变其值
persons2[0].setName("chessy_02");
System.out.println("------------改变其值后------------");
System.out.println("数组persons1:");
display(persons1);
System.out.println("---------------------");
System.out.println("数组persons2:");
display(persons2);
}
public static void display(Person[] persons){
for(Person person : persons){
System.out.println(person.toString());
}
}
}
-------------Output:
数组persons1:
姓名是:chenssy_01
---------------------
数组persons2:
姓名是:chenssy_01
------------改变其值后------------
数组persons1:
姓名是:chessy_02
---------------------
数组persons2:
姓名是:chessy_02
六、asList的缺陷
asList返回的是一个长度不可变的列表。数组是多长,转换成的列表是多长,我们是无法通过add、remove来增加或者减少其长度的。
demo
public static void main(String[] args) { int[] datas = new int[]{1,2,3,4,5}; List list = Arrays.asList(datas); System.out.println(list.size()); } ------------Output: 1
疑问:
不应该是5吗?
asList()的源码
public static <T> List<T> asList(T... a) { return new ArrayList<T>(a); }
分析:
由于动态数组的存在导致,什么是动态数组,篇幅有限不便赘述了
demo
enum Week{Sum,Mon,Tue,Web,Thu,Fri,Sat} public static void main(String[] args) { Week[] weeks = {Week.Sum,Week.Mon,Week.Tue,Week.Web,Week.Thu,Week.Fri}; List<Week> list = Arrays.asList(weeks); list.add(Week.Sat); }
运行结果:
Exception in thread "main" java.lang.UnsupportedOperationException at java.util.AbstractList.add(AbstractList.java:131) at java.util.AbstractList.add(AbstractList.java:91) at com.array.Test.main(Test.java:18)
asList源码
public static <T> List<T> asList(T... a) { return new ArrayList<T>(a); }
分析:
a、这里是直接返回一个ArrayList对象返回,但这个ArrayList是Arrays工具类的一个内部类
b、所以这里的ArrayList并不是java.util.ArrayList
ArrayList内部类源码:
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) { if (array==null) throw new NullPointerException(); a = array; } /** 省略方法 **/ }
分析:
但是这个内部类并没有提供add()方法,那么add()方法哪里来呢?
父类源码:
public boolean add(E e) { add(size(), e); return true; } public void add(int index, E element) { throw new UnsupportedOperationException(); }
分析:
a、很遗憾父类仅仅只是提供了方法,并没有实现
b、这个内部类ArrayList也没有实现add()
c、在ArrayList中,它主要提供了如下几个方法:
1、size:元素数量
2、toArray:转换为数组,实现了数组的浅拷贝。
3、get:获得指定元素。
4、contains:是否包含某元素。