重学java-5.数组的基本概念

重学java-5.数组

  • 数组的基本概念
    • 一维数组
      • 三种定义方式
      • 两种初始化
    • 二维数组
    • 对象数组
    • 数组的引用传递
    • 方法参数的传递
      • 基本数据类型作为方法的参数
      • 引用数据类型作为方法的参数
      • 变量传参后是否影响外界的值
    • 数组的3个复制方法
      • System.arraycopy()
      • arrays.copyOf()方法
      • clone 方法
      • 效率比较
    • 数组的排序方法sort()

数组的基本概念

在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在内存的申请上有所不同。

  • 1和2在申请内存时,把array1,array2存放在栈中的同时,在堆中申请相应的内存,并让array1和array2指向堆中信息。
  • 3分为两步,第一步是把array3存放在栈中,在第二步执行以前,array3都指向null。直到第二部执行,array3才指向堆。

两种初始化

  • 静态初始化:数组在申请空间的同时就设置好了相应的数据内容。
  • 动态初始化:数组在申请空间后添加数组内容。

下面举个小例子:

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++中指针的工作机制。

数组的3个复制方法

你知道茴字的四种写法吗?

System.arraycopy()

    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));
	}

输出结果:
在这里插入图片描述

arrays.copyOf()方法

    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()慢,而且不能选择截取位置,一般用于数组的扩容。

clone 方法

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:这里可以引入深拷贝和浅拷贝的概念,请戳这里。

数组的排序方法sort()

使用方法如下:

public static void main(String[] args) {
		int a[] = {1,3,2,4,5};
		Arrays.sort(a);
		System.out.println(Arrays.toString(a));
	}

这个sort方法很强大,源码里面针对各种情况分别进行插入、快速、归并排序。

你可能感兴趣的:(java初学,java初学)