我的JAVA笔记004----JAVA数组

我的JAVA笔记

----------------------------------第一章JAVA数组
----------------------------------2018.12.23

1.什么是数组?

  • 程序=算法+数据结构
    流程控制解决的问题即为算法问题。数据结构,就是把数据按照特定的某种结构来保存,数组就是一种基本的数据结构
  • 数组:相同数据类型的元素组成的集合。元素按照线性顺序排列,即除了第一个和最后一个元素外,每个元素都有前驱和后继。
  • Java语言是典型的静态语言,因此Java数组是静态的,即当数组被初始化之后,该数组所占的内存空间和数组长度都是不可改变的。

2.定义数组(声明数组变量)

根据数组元素的数据类型,可以将其分为基本类型数组和引用类型数组。
Java语言支持两种语法格式来定义数组:
type[ ] arrayName;
type arrayName[ ]; (不建议使用,为了和C语法兼容的写法)

  • 基本类型数组
  • 引用类型数组
public class TEST02 {

	public static void main(String[] args) {	
		Person[] students;
		System.out.println("动态初始化");
		students = new Person[2];
		Person zhang = new Person();
		zhang.age = 19;
		zhang.height = 180;
		zhang.info();
		Person lee = new Person();
		lee.age = 20;
		lee.height = 165;
		lee.info();
		System.out.println("数组变量指向Person对象");
		students[0] = zhang;
		students[1] = lee;
		students[0].info();
		students[1].info();		
		System.out.println("数组变量修改Person对象的属性");
		students[0].age = 20;
		students[0].info();
	}

}
class Person{
	public int age;
	public double height;
	public void info() {
		System.out.println("我的年龄是:"+age+",我的身高是:"+height);
	}
}

此时,完成数组定义,但暂时还没初始化。
数组是一种引用类型的变量,因此使用它定义一个变量时,仅仅表示定义了一个引用变量(也就是定义了一个指针),这个引用变量还未指向任何有效的内存,因此定义数组时不能指定数组的长度(初始化的工作任务)。

3.数组初始化

包括两种方式:静态初始化和动态初始化。

静态初始化

  • 完整写法:
    声明数组变量与数组初始化分开写
    type[] arrayName;
    arrayName= new type[]{data1,data2,data3};
    声明数组变量时初始化
    type[] arrayName = new type[]{data1,data2,data3};

其中,arrayName为数组变量;{data1,data2,data3}为数组对象;data1,data2,data3为数组元素。

  • 简化形式:
    type[] arrayName = {data1,data2,data3};(这种写法只能在声明数组变量时同时初始化)

动态初始化

type[] arrayName = new type[ length];

对于静态初始化方式,无须指定数组长度,指定了数组的数组元素,由系统来决定该数组的长度即可。
对于动态初始化,仅需要指定数组的长度(new关键字分配空间时需指定分配的间大小),即为每个数组元素指定所需的内存空间,系统将为这些元素分配初始值。

基本类型:

  • 整数类型(byte、short、int、long)则数组元素的初始值为0;
  • 浮点类型(float、double)则数组元素的初始值为0.0;
  • 布尔类型(boolean)则数组元素的初始值为false;
  • 字符类型(char)则数组元素的初始值为’\u0000’;

引用类型:

  • 引用类型(类、接口、数组)则数组元素的初始值为null。

4.使用数组

4.1数组元素的访问、遍历

  • 访问

数组对象的大小是固定的,长度为n,下标范围为0~n-1。
通常使用arrayName[下标]访问数组元素,当下标范围不在0~n-1时出现数组索引越界异常(运行时异常)

编译时异常和运行时异常,简单来说就是编译时异常就是在写代码时就会提示异常,运行时异常是在运行之后异常。

  • 遍历,输出数组

(1)普通for循环
.length属性,获取数组对象长度。

		for(int i=0;i<arrayName.length;i++) {
			System.out.println(arrayName[i]);
		}

(2)foreach循环(Java5)
无须获取数组长度,无须循环迭代条件,无须根据索引来访问数组元素,由系统完成。

		for(type variableName :arrayName) {
			System.out.println(variableName);
		}

variableName为一个临时变量,系统依次把数组元素赋给这个临时变量,这个临时变量不是数组元素(仅保存),所以不能对其进行赋值修改数组。
(3).toString()

		System.out.println(Arrays.toString(arrayName));

4.2复制

首先为什么要复制呢?
因为单纯的数组赋值无法使数组对象隔离,这样子容易使数组对象不小心被修改。

4.2.1赋值

	public static void main(String[] args) {
		
		int[] arr1 = new int[]{1,2,3,4};
		int[] arr2 = arr1;
		System.out.println("arr1:"+Arrays.toString(arr1));
		System.out.println("arr2:"+Arrays.toString(arr2));
		arr2[0]=0;
		System.out.println("arr1:"+Arrays.toString(arr1));
		System.out.println("arr2:"+Arrays.toString(arr2));
	}

运行结果为:
arr1:[1, 2, 3, 4]
arr2:[1, 2, 3, 4]
arr1:[0, 2, 3, 4]
arr2:[0, 2, 3, 4]
以上为数组赋值,两个引用变量指向同一个数组对象,两个引用变量均可以修改数组元素的值。
修改arr2的数组元素,会使该数组对象的元素值被修改,所以会发现arr1的打印结果和arr2一样。
如此,数组对象没有隔离性,不小心就会被修改。

4.2.2拷贝

拷贝:实现数组对象隔离性

	public static void main(String[] args) {
		
		int[] arr1 = new int[]{1,2,3,4};
		int[] arr3 = new int[arr1.length];
		System.out.println("初始化::");
		System.out.println("arr1:"+Arrays.toString(arr1));
		System.out.println("arr3:"+Arrays.toString(arr3));
		for(int i=0;i<arr1.length;i++) {
			arr3[i] = arr1[i];
		}
		System.out.println("拷贝后:");
		System.out.println("arr1:"+Arrays.toString(arr1));
		System.out.println("arr3:"+Arrays.toString(arr3));
		arr3[0]=0;
		System.out.println("修改arr3数组元素值:");
		System.out.println("arr1:"+Arrays.toString(arr1));
		System.out.println("arr3:"+Arrays.toString(arr3));

	}

结果为:
初始化::
arr1:[1, 2, 3, 4]
arr3:[0, 0, 0, 0]
拷贝后:
arr1:[1, 2, 3, 4]
arr3:[1, 2, 3, 4]
修改arr3数组元素值:
arr1:[1, 2, 3, 4]
arr3:[0, 2, 3, 4]
可以看到arr3的修改并不会影响到arr1,这就是实现了数组对象的隔离。
两个数组变量实际指向不同的数组对象。

4.2.3数组拷贝的其他方法

	public static void main(String[] args) {
		
		int[] src = new int[]{1,2,3,4};
		int[] dest1 = new int[src.length];
		System.arraycopy(src,0,dest1,0,src.length);
		System.out.println("src:"+Arrays.toString(src));
		System.out.println("dest1:"+Arrays.toString(dest1));
	
		int[] dest2 = Arrays.copyOf(src, src.length);
		System.out.println("dest2:"+Arrays.toString(dest2));
	}

结果为:
src:[1, 2, 3, 4]
dest1:[1, 2, 3, 4]
dest2:[1, 2, 3, 4]
需要import java.util.Arrays;
System.arraycopy(); Java API提供,底层使用C++写的,速度快,比for循环实现数组拷贝效率更高,推荐使用。
Arrays.copyOf(); JDK1.6版本提供,底层是arraycopy()方法,但需要注意JDK版本。

4.2.4利用拷贝进行数组扩容

数组的长度在初始化后是不可改变的,可以创建一个更大的新数组并将小数组复制到其中,实现“数组扩容”。

	public static void main(String[] args) {
		int[] a = new int[] {1,2,3,4,5,6};
		System.out.println("原始a:"+Arrays.toString(a));
		System.out.println(a);
		a = Arrays.copyOf(a, a.length+5);
		System.out.println("扩容后a:"+Arrays.toString(a));
		System.out.println(a);

	}
	

结果为:
原始a:[1, 2, 3, 4, 5, 6]
[I@7852e922
扩容后a:[1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0]
[I@4e25154f
实际上,只是将数组变量指向另一个新的数组对象,原始的数组长度并没有发生变化。

4.3数组的内存管理

4.3.1基本类型数组的初始化

程序先为数组分配内存空间,再将数组元素的值存入对应内存中。
int[] a;
在main方法栈区定义了一个数组变量,还未指向数组对象。
a= new int[] {1,2,3,4,5,6};
静态初始化之后,确定了数组长度,将数组元素填入堆区数组对象的内存中(若是动态初始化,默认值)。
注意:
指定类型的变量只能存储指定类型的值。
所有局部变量都存放在栈内存中,不管是基本类型的变量还是引用类型的变量,都是存储在各自的方法栈内存中的;但引用类型的变量所引用的对象(包括数组、普通的Java对象)则是存储在堆内存中。

4.3.2引用类型数组的初始化

引用类型数组的数组元素依然是引用类型的,因此数组元素里存储的还是引用,它指向另一块内存,这块内存里存储了该引用变量所引用的对象。

4.4易混淆的点

4.4.1数组对象和数组变量

数组变量只是一个引用变量,数组对象就是保存在堆内存中的连续内存空间。所以数组初始化并不是对数组变量执行初始化,而是在堆内存中创建数组对象(为该数组对象分配一块连续的内存空间)。
就像是一个数组对象可以看做一间房间,我们可以给它取不同的名字,但是我要是把这个名字挂到另一个房间去,那这个名字对应的就是另一间房间了,也就是指向了另一个数组对象(数组赋值)。当然,我一个房间可以取很多名字,但是它们中哪怕一个一旦修改了这个房间的属性,那么别的名字看到的房间属性也就变了,这就是数组对象的不隔离性。
从内存来看,数组变量存储在栈内存区,数组对象存储在堆内存区,两者是不一样的。

4.4.2数组长度

只要类型相互兼容,可以让一个数组变量指向另一个实际的数组,这种操作会产生数组的长度可变的错觉(数组对象改变了)。
由于数组变量整体赋值(上面讲的数组扩容)导致的数组的长度可以改变,只是一个假相(因为数组对象不是同一个对象)。

4.4.3多维数组

Java实际上没有多维数组,所谓多维数组其实就是数组元素依然是数组的一维数组,N维数组是数组元素为(N-1)维数组的数组。

猜你喜欢

转载自blog.csdn.net/guys_19/article/details/85223977