前言
这里我使用的是IDEA编译器进行演示
数组的创建与初始化
创建格式:
T[] 数组名 = new T[N]
T表示数组存放的数据类型,N表示数组的大小。
T[] 表示数组的类型。
这里要注意和C语言不同的是C语言使用类似int arr[10]这样的结构进行创建数组,而Java使用int[] 直接表明这就是一个数组,体现了Java语言具有简单性,通俗易懂。
数组有三种初始化方式:
int[] arr1 = new int[10];
int[] arr2 = new int[]{1,2,3,4,5,6};
int[] arr3 = {1,2,3,4,5,6};
我们来拆分一下上面三种方式:
首先类似:
T[] array = new T[N];
这种创建形式时,我们叫作动态初始化,这种的特点就是创建的数组所有的元素都是0.
然后就是类似下面的:
int[] arr2 = new int[]{1,2,3,4,5,6};
这种我们称为静态初始化,这种就直接对数组每个元素进行了赋值。
int[] arr3 = {1,2,3,4,5,6};
这种初始化和上面的是一样的,只是简写而已(少写了new int[]),这种就更方便书写。
静态初始化是不能写类似int[] arr = new int[3]{1,2,3}的,也就是第二个中括号是不可以写大小的,数组的大小编译器会更具{ }里面的数据进行确定~~
如果使用的是动态初始化,不同的类型对应不同的默认值~~
这里特别记忆一下存放boolean类型的数组每一个元素默认值是false
如果数组中存储元素类型为引用类型,默认值为null
特殊情况
如果你想分步创建的话,你可以使用下面两种形式:
int[] arr;arr = new int[3];
int[] arr;arr = new int[]{1,2,3};
下面的创建方式是不允许的:
数组的访问与遍历
访问一个元素
这里可以直接通过访问数组的单个元素,可以借助下标~~
public static void main(String[] args) {int[] arr = new int[]{1,2,5,6,8,9,0};System.out.println(arr[2]);}
遍历数组所有元素
1.可以使用循环来直接遍历数组,和C语言是一样的,当时Java里面我们可以直接使用.length来直接得到数组的大小~~
int[] arr = new int[]{1,2,5,6,8,9,0};for (int i = 0; i < arr.length; i++) {System.out.print(arr[i]+" ");}
IDEA编译器可以直接输入fori然后按回车,for循环语句就直接输出来了。
2.我们还可以使用for(数据类型+变量名 :数组名){} 来遍历整个数组元素。
int[] arr = new int[]{1,2,5,6,8,9,0};for (int x: arr) {System.out.print(x+" ");}
3.数组直接转字符串,然后直接输出。
int[] arr = new int[]{1,2,5,6,8,9,0};System.out.println(Arrays.toString(arr));
我们使用的是Arrays里面封装好的方法,它可以直接将数组转变为字符串[数组每一个元素],每一个元素后面会加一个逗号和空格。
引用类型讲解
基本类型变量与引用类型变量的区别
这里的基本数据类型就是我之前文章里写过的八大基本数据类型,该变量空间中直接存放的是其所对应的值;而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。
数组是实际上就是一个引用类型变量,数组名存放的是数组的地址,数组名的地址实际上就是数组所引用的对象的地址,这个对象就是数组包含的数组内容。
我们通过打印数组名,发现这是一堆奇怪的数字组合,这一堆数字中4eec7777就是数组所引用的对象的地址。
JVM内存的分布
JVM内存一共可以划分为五大空间:
程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址
虚拟机栈(JVM Stack): 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的
堆(Heap): JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2, 3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。
方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. 方法编译出的的字节码就是保存在这个区域
这里我们只关心Java虚拟机栈和堆~~
Java虚拟机栈会存放我们定义的变量和方法,数组名也是会存放在这里,而对象则会存放在堆里
我们从上面了解到数组名包含的就是数组所指向的对象的地址,然后我们可以画一下内存图,这里为了方便,只画Java虚拟机栈和堆这两个内存空间。
练习
我们来画一下内存分布图:
public static void func1() {int a = 10;int b = 20;int[] arr = new int[]{1,2,3};}
首先a是基本类型变量,所以a包含的数值就是10,直接入栈,然后是b也是一样,压栈,接着是数组arr压栈,由于数组是应用类型变量,所以是存放的是对象的地址,然后数组压栈~~
public static void func2() {int[] array1 = new int[3];array1[0] = 10;array1[1] = 20;array1[2] = 30;int[] array2 = new int[]{1,2,3,4,5};array2[0] = 100;array2[1] = 200;array1 = array2;array1[2] = 300;array1[3] = 400;array2[4] = 500;for (int i = 0; i < array2.length; i++) {System.out.println(array2[i]);}}
这里我用视频来演示一下压栈过程:
JVM内存画图
这里我来简单描述一下,首先array1入栈,array1引用的对象放在堆里,由于是动态初始化,所以开始时每一个元素都是0,接着就是修改数组元素。
然后是array2入栈,array2引用的对象放在堆里,由于是静态初始化,所以开始时array2所引用的对象的内容就是1,2,3,4,5;接着是修改了两个数组元素。
array1=array2 相当于把array2的值赋给了array1,也就是array1现在装的是array2所引用的对象的地址,所以array1的数组元素修改也影响到了array2,毕竟这两个变量现在所引用的对象是一样的~~
这里要注意的是如果原本array1所引用的对象是不是现在没有变量去引用了,不用当心内存泄漏,Java是有垃圾回收机制的,所以这块内存如果没有变量去引用就会被回收掉~~
数组传参和返回问题
时刻保持清醒,Java是没有指针的概念,也就意味着你无法进行取地址解引用等操作,这体现了Java的安全性,学过C语言的铁汁们都知道指针是很危险的,一不小心就内存泄漏,野指针非法访问等问题~~
我们来铺垫一下C语言的知识点:
形参的改变是不会影响实参
在Java中的基本类型变量也是一样的:
public static void number(int a){a += 10;}public static void main(String[] args) {int a = 10;System.out.println(a);}
但是,在Java中数组名可以像普通变量一样相互赋值,也就是地址可以交换或者改变,从而改变所引用的对象。所以数组名可以直接传参,形参接收实参的内容,意味着形参引用的对象就是实参引用的对象,所以形参修改数组是会影响到对象的,也就意味着实参也跟着修改~~
public static void increase(int[] array){for (int i = 0; i < array.length; i++) {array[i] *= 2;}}public static void main(String[] args) {int[] arr = new int[]{1,2,3,4};System.out.println(Arrays.toString(arr));System.out.println("扩大两倍后:");increase(arr);System.out.println(Arrays.toString(arr));}
同样的,如果你在方法里创建好了数组,想要返回数组也是可以的,直接返回数组名即可~~
public static int[] increase(int[] array){int[] arr = new int[array.length];for (int i = 0; i < array.length; i++) {arr[i] = array[i] * 2;}return arr;}public static void main(String[] args) {int[] arr = new int[]{1,2,3,4};System.out.println(Arrays.toString(arr));System.out.println("扩大两倍后:");int[] arr2 = increase(arr);System.out.println("arr数组:"+Arrays.toString(arr));System.out.println("arr2数组:"+Arrays.toString(arr2));}
null的介绍
当你创建的数组没有对象时,你可以赋值一个null
int[] arr = null;
如果你对null的数组进行访问的话,就会报出空指针异常!!!
注意Java确实没有指针的概念,这个空指针异常只是名字就叫作空指针异常而已~~
Arrays
这个Arrays里面包含很多方法供我们使用,里面也有很多重载,上面我们就知道数组转字符串,这里还有二分查找的方法~~
这里不一一介绍,想要了解的可以查阅官方帮助文档手册~~
后面的文章我也会去讲解~~
二维数组
二维数组的创建与初始化
先看代码:
public static void main(String[] args) {int[][] array = new int[4][];int[][] array2 = new int[][]{{1, 2, 3}, {4, 5, 6}};int[][] array3 = {{1,2},{3,4},{5,6}};}
你要分步也是可以的~~
值得注意的是Java的二维数组如果进行动态初始化是一定要给出行数的~~
我来解释一下:在C语言中我们知道二维数组其实就是有一个又一个一维数组构成的,在Java中我们会明显感受到这个内容。
为什么Java一定要知道二维数组的行数才给创建呢?
二维数组也是引用类型变量,在Java虚拟机栈也是存储的是地址,那这是谁的地址呢?除了自己的地址,那肯定存的是自己的成员(一维数组)的地址了
所以如果不知道行数也就不知道要开辟多少个空间来保存多少个地址~~
对比C语言,C语言在传参二维数组的时候为什么要知道二维数组的列数,行数却可以不需要知道呢?
站在C语言的角度,C语言需要开辟一整块空间来保存数组内容,所以C语言是一定要知道这个二维数组的每个成员(也就是一维数组)有多大的,给出列数意味着给出了每一个一维数组的具体大小,这样C语言就可以一次一个一维数组进行开辟空间了,最后二维数组也就随之构建好了,根本就用不到行数。
但是只给行数不给列数就不知道每一个一维数组有多大,就无法进行空间的开辟~~
看到这里大家是不是还有疑惑,就是Java不需要知道一维数组的大小吗?
当然不需要了,可以这样理解,Java中我们需要知道被引用的对象的地址,至于对象是多大不需要关心,我们只需要知道对象的地址~~
所以你会发现其实二维数组的成员(一维数组其实是可以不一样大的)~~
我拿 int[][] array2 = new int[][]{{1, 2, 3}, {4, 5, 6}};来说明一下:
我们来看一下Java这个二维数组内存分布图:
在这里我们明显看到二维数组实质就是由一维数组构建的~~
我们来验证一下吧:
public static void main(String[] args) {int[][] array = new int[4][];int[][] array2 = new int[][]{{1,2,3}, {4, 5, 6}};int[][] array3 = {{1,2},{3,4},{5,6}};System.out.println("array:");System.out.println(Arrays.toString(array));System.out.println("array2:");System.out.println(Arrays.toString(array2));System.out.println("array3:");System.out.println(Arrays.toString(array2));}
注意array是动态初始化,由于存储元素类型为引用类型,所以默认值为null
这里要注意了不要去遍历这种null数组哦~~
否则会报空指针异常的~~
二维数组的访问与遍历
遍历方式一
通过上面的解析,我们如果需要遍历二维数组的话,可以使用两个循环,通过.length来得知数组大小~~
public static void main(String[] args) {int[][] array = new int[4][];int[][] array2 = new int[][]{{1,2,3}, {4, 5, 6}};int[][] array3 = {{1,2},{3,4},{5,6}};System.out.println("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.println();}System.out.println("array3:");for (int i = 0; i < array3.length; i++) {for (int j = 0; j < array3[i].length; j++) {System.out.print(array3[i][j]+" ");}System.out.println();}}
遍历方式二
public static void print(int[][] array){for (int i = 0; i < array.length; i++) {System.out.println(Arrays.toString(array[i]));}}public static void main(String[] args) {int[][] array = new int[4][];int[][] array2 = new int[][]{{1,2,3}, {4, 5, 6}};int[][] array3 = {{1,2},{3,4},{5,6}};System.out.println("array2:");print(array2);System.out.println("array3:");print(array3);}
上面提到Java中的二维数组的成员可以不一样大的,我们来看一下吧:
public static void main(String[] args) {int[][] array = {{1},{2,3},{4,8,9}};for (int i = 0; i < array.length; i++) {for (int j = 0; j < array[i].length; j++) {System.out.print(array[i][j]+" ");}System.out.println();}}
public static void main(String[] args) {int[][] array = {{1},{2,3},{4,8,9}};for (int i = 0; i < array.length; i++) {System.out.println(Arrays.toString(array[i]));}}
这里我们知道当我们要使用Arrays.toString的时候传参需要传的是数组名(可以理解成地址)~~
之后它就顺着这个地址将数组的内容转换成字符串了~~
二维数组的传参与返回
不同于C语言,二维数组可以直接传参过去,不需要列数的说明,而C语言是一定要把列数传递过去的~~
public static void print(int[][] array){for (int i = 0; i < array.length; i++) {for (int j = 0; j < array[i].length; j++) {System.out.print(array[i][j]+" ");}System.out.println();}}public static void main(String[] args) {int[][] array = new int[4][];int[][] array2 = new int[][]{{1,2,3}, {4, 5, 6}};int[][] array3 = {{1,2},{3,4},{5,6}};System.out.println("array2:");print(array2);System.out.println("array3:");print(array3);}
返回二维数组就不多说了,也是可以直接返回的~~
public static int[][] creatArray(){int[][] arr = {{1},{2,3},{4,8,9}};return arr;}public static void main(String[] args) {int[][] array = creatArray();for (int i = 0; i < array.length; i++) {for (int j = 0; j < array[i].length; j++) {System.out.print(array[i][j]+" ");}System.out.println();}}