在java中,数组属于引用型数据,指的是一组相关便令的集合。
public static void main(String[] args) {
//1
int [] array1 = new int[100];
//2
int array2[] = new int[100];
//3
int array3[] = null;
array3 = new int[100];
}
这三种方法中,1和2只是写法上的不同。但3与1和2在内存的申请上有所不同。
下面举个小例子:
public static void main(String[] args) {
//动态初始化
int [] array1 = new int[3];
array1[0] = 1;
array1[1] = 2;
array1[2] = 3;
System.out.print("array1: ");
for(int i = 0; i < array1.length; ++i) {
System.out.print(array1[i] + " ");
}
System.out.println();
//静态初始化
int array2[] = new int[] {4,5,6};
System.out.print("array2: ");
for(int i = 0; i < array2.length; ++i) {
System.out.print(array2[i] + " ");
}
System.out.println();
}
二维数组的定义与初始化与一维的大同小异,在这里举个简单的例子:
public static void main(String[] args) {
//1
int array1[][] = new int[2][2];
//2 静态初始化
int [] array2[] = new int[][] {{1,2},{3,4}};
//3 动态初始化
int array3[][] = null;
array3 = new int[2][2];
int cnt = 0;
for(int i = 0; i < array3.length; ++i) {
for(int j = 0; j < array3[i].length; ++j) {
array3[i][j] = ++cnt;
}
}
//输出
System.out.print("array2: ");
for(int i = 0; i < array2.length; ++i) {
for(int j = 0; j < array2[i].length; ++j) {
System.out.print(array2[i][j]+ " ");
}
}
System.out.print('\n'+"array3: ");
for(int i = 0; i < array3.length; ++i) {
for(int j = 0; j < array3[i].length; ++j) {
System.out.print(array3[i][j]+" ");
}
}
}
对象数组也是一个样,在这里举个简单例子。
public class test1 {
public static void main(String[] args) {
Book book[] = new Book[] {new Book("a"), new Book("b"), new Book("c")};
System.out.println(Arrays.toString(book));
}
}
class Book {
private String name;
public Book(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name;
}
}
根据 重学java-4.面对对象基本概念 可知,引用数据类型都可以进行引用传递操作,因此数组也可以进行引用传递操作。
举个例子:
public static void main(String[] args) {
int [] array1 = new int[] {1,2,3};
int array2[] = array1;//引用传递,使array2指向array1所指向的堆
array2[0] = 4;//通过array2改变堆内的值
System.out.println("array1: ");
for(int i = 0; i < array1.length; ++i) {
System.out.print(array1[i] + " ");
}
}
标题很长,其实就是想讨论一下数组、类等引用数据类型作为一个方法的参数是如何存在的,便于我们加深对栈内存与堆内存的理解。
public class test1 {
public static void main(String[] args) {
int a = 1;
change(a);
System.out.println(a);
}
public static void change(int b) {
b = 2;
}
}
运行结果:
我们发现a的值在传入change方法后明明更改为2,但最终输出时却仍是1。暂且不提为什么,我们继续往下看。
public class test1 {
public static void main(String[] args) {
int arraya[] = new int[] {1,2};
change(arraya);
System.out.println(arraya[0]);
}
public static void change(int[] arrayb) {
arrayb[0] = 3;
}
}
public class test1 {
public static void main(String[] args) {
Book booka = new Book("野草");
fun(booka);
System.out.println(booka.getName());
}
public static void fun(Book bookb) {
bookb.setName("狂人");
}
}
class Book {
private String name;
public Book(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
运行结果:
更改成功。
在上面的代码中,我们实例化了一个Book类型的对象booka,将booka作为参数传入了fun方法中,调用该方法。而在fun方法中,把Book类型的bookb的name属性更改为"狂人"。fun方法结束后,输出booka的name属性,发现其已然改为"狂人"。
以上两段代码,如果把方法这个载体省去,main函数的过程与Book bookb = booka,int[] arrayb = arraya的引用传递过程并无区别。事实上也正是如此,当我们把booka传入fun方法时,就是实现了bookb = booka的过程。
在基本数据类型作为参数的时候,我们发现在方法中更改的数据又回归原值。这是数据类型的原因,还是方法的特性?
还是之前的例子,我加入了一个change方法,负责改变类的指向。
public class test1 {
public static void main(String[] args) {
Book a = new Book("野草");
Book b = new Book("坟");
fun(a);
change(a, b);
System.out.println(a.getName());
System.out.println(b.getName());
}
public static void fun(Book bookb) {
bookb.setName("狂人");
}
public static void change(Book a, Book b) {
b = a;
}
public static void fun2(int b) {
b = 2;
}
}
class Book {
private String name;
public Book(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
运行结果:
可以发现,经过引用传递的值成功改变,而直接改变参数的值则无效。这与方法本身的特性有关,当我们调用一个方法,系统就会在栈中申请一段空间用来存储方法中的值,而这段空间的寿命会伴随着方法的结束而结束。
之所以使用引用传递能改变原来的值,是因为它改变的是参数所指向的值。这样说有点绕,我们可以举个例子:
定义一个方法,传进去的参数是一个门牌号。此时,系统会在栈中申请一段空间,并在这段空间中制作门牌号的副本。显然,无论怎么改变副本,都与原来的门牌号无关。但如果我们要改变门牌号对应的房子,那就一改一个准了,因为无论做了几个副本,只要牌号正确,我们都能按照门牌号找到房子。其实这就是c++中指针的工作机制。
你知道茴字的四种写法吗?
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
上图是源码,使用native关键字,说明这是个原生函数,也就是说这个方法是用c/c++实现的,被编译成DLL(Dynamic Link Library)文件,需要用的时候直接调用。
注意:源码中被native关键字修饰的方法虽然都是空的方法,但它是有实现体的,只不过实现体在外部需要调用。这一点要与abstract关键字区分开,被abstract关键字修饰过的方法不能有实现体,因此二者不会同时修饰同一个方法。(参考:该博客
使用方法如下:
public static void main(String[] args) {
int a[] = {1,2,3,4,5};
int b[] = new int[5];
//System.arraycopy(src原数组, srcPos复制的起始位, dest目标数组, destPos目标数组起始位, length复制的长度);
System.arraycopy(a, 0, b, 1, 3);
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
上面的源码,可以看出来,这个方法在内部新建了一个数组调用System.arraycopy()方法。
使用方法如下:
public static void main(String[] args) {
int a[] = {1,2,3,4,5};
int b[] = new int[10];
//b新数组 = Arrays.copyOf(original原数组, newLength从下标0开始的复制长度)
b = Arrays.copyOf(a, 10);
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));
}
因为其在内部调用System.arraycopy()方法,所以会比System.arraycopy()慢,而且不能选择截取位置,一般用于数组的扩容。
protected native Object clone() throws CloneNotSupportedException;
这个是clone方法的源码,也是native关键字修饰。
使用方法如下:
public static void main(String[] args) {
int a[] = {1,2,3,4,5};
int b[] = new int[10];
b = a.clone();
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));
}
效率:System.arraycopy > Arrays.copyOf > clone > for循环
效率相关测试:戳这里
flag:这里可以引入深拷贝和浅拷贝的概念,请戳这里。
使用方法如下:
public static void main(String[] args) {
int a[] = {1,3,2,4,5};
Arrays.sort(a);
System.out.println(Arrays.toString(a));
}
这个sort方法很强大,源码里面针对各种情况分别进行插入、快速、归并排序。