📚博客主页:爱敲代码的小杨.
✨专栏:《Java SE语法》
❤️感谢大家点赞👍🏻收藏⭐评论✍🏻,您的三连就是我持续更新的动力❤️
文章目录
- 1.数组的基本概念
- 1.1 为什么使用数组?
- 1.2 什么是数组
- 1.3 数组的创建和初始化
- 1.3.1 数组的创建
- 1.3.2 数组的初始化
- 1.4 数组的使用
- 1.4.1 数组中元素访问
- 1.4.2 遍历数组
- 2.数组是引用类型
- 2.1 JVM 内存分布
- 2.2 基本类型的变量与引用类型变量的区别
- 2.3 引用变量
- 2.4 认识 null
- 3. 数组应用场景
- 3.1 保存数据
- 3.2 作为方法的参数
- 3.3 作为方法的返回值
- 4. 二维数组
- 5. 不规则数组
1.数组的基本概念
1.1 为什么使用数组?
假设现在要存储5个学生的年龄,按照之前掌握的知识点,我们会写出如下代码:声明5个变量存储学生变量
public class Test {public static void main(String[] args) {int age1;int age2;int age3;int age4;int age5;}
}
如果我们有10个学生呢?我们就要声明20个变量,似乎没有什么问题。那如果有100,1000个学生呢,我们就要声明100,1000个变量,这样就有点离谱了,使用数组我们就可以解决一个问题。
1.2 什么是数组
数组,是指一组类型相同的数据的集合,数组中每个数据称为元素。数组可以存放任意类型的元素,但同一个数组里存放的元素类型必须一致。数组分为一维数组和多维数组。
数组在内存中是一段连续的空间,比如现实中的车库:
在 Java中,包含6个整形类型元素的数组,就相当于上图中连在一起的6个车位,从上图中可以看到:
-
数组中存放的元素其类型相同
-
数组的空间是连在一起的
-
每个空间有自己的编号,起始位置的编号为0,即数组的下标。
1.3 数组的创建和初始化
1.3.1 数组的创建
基本语法格式:
T[] 数组名 = new T[N];
T
:表示数组中存放元素的类型T[]
:表示数组类型N
:表示数组的长度
代码示例:存储10个人的年龄
int[] ages = new int[10];
1.3.2 数组的初始化
Java 数组初始化主要分为静态初始化以及动态初始化
-
动态初始化:在创建数组时,直接指定数组中元素的个数
int[] ages = new int[10];
-
动态初始化:在创建数组是不直接指定数据元素个数,而直接讲具体的数据内容进行指定
语法格式:
T[] 数组名 = {data1,data2,....data};
int[] ages = new {1,2,3,4,5};
【注意事项】
-
静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。
-
静态初始化时, {}中数据类型必须与[]前数据类型一致。
-
静态初始化可以简写,省去后面的new T[]。
int[] arr = {1,3,2,5,4}; // 注意:虽然省去了new T[], 但是编译器编译代码时还是会还原
-
数组也可以按照如下C语言个数创建,不推荐
int arr[] = {1, 2, 3}; /* 该种定义方式不太友好,容易造成数组的类型就是int的误解 []如果在类型之后,就表示数组类型,因此int[]结合在一块写意思更清晰 */
-
静态和动态初始化也可以分为两步,但是省略格式不可以。
public class Main {public static void main(String[] args) {int[] array1;array1 = new int[10];int[] array2;array2 = new int[]{10, 20, 30};// 注意省略格式不可以拆分, 否则编译失败//int[] array3;//array3 = {1, 2, 3};} }
-
如果没有对数组进行初始化,数组中元素有其默认值
-
如果数组中存储元素类型为基类类型,默认值为基类类型对应的默认值,比如:
类型 默认值 byte 0 short 0 int 0 long 0 float 0.0f double 0.0 char /u0000 boolean false -
如果数组中存储元素类型为引用类型,默认值为
null
-
1.4 数组的使用
1.4.1 数组中元素访问
数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,该编号称为数组的下标,数组可以通过下标访问其任意位置的元素。比如:
public class Main {public static void main(String[] args) {int[] arr = new int[]{1,2,3,4,5};System.out.println(arr[0]);System.out.println(arr[1]);System.out.println(arr[2]);System.out.println(arr[3]);System.out.println(arr[4]);}
}
【注意事项】:
-
数组是一段连续的内存空间,因此支持随机访问,即通过下标快速访问数组中任意位置的元素
-
下标从0开始,介于[0,N) 之间不包含N,N为元素个数,不能越界,否则会报出下标越界异常。
抛出了
java.lang.ArrayIndexOutOfBoundsException
异常. 使用数组一定要下标谨防越界.
1.4.2 遍历数组
所谓 “遍历” 是指将数组中的所有元素都访问一遍, 访问是指对数组中的元素进行某种操作,比如:打印。
public class Main {public static void main(String[] args) {int[] arr = new int[]{1,2,3,4,5};System.out.println(arr[0]);System.out.println(arr[1]);System.out.println(arr[2]);System.out.println(arr[3]);System.out.println(arr[4]);}
}
上述代码可以起到对数组中元素遍历的目的,但问题是:
-
如果数组中增加了一个元素,就需要增加一条打印语句
-
如果输入中有100个元素,就需要写100个打印语句
-
如果现在要把打印修改为给数组中每个元素加1,修改起来非常麻烦。
通过观察代码可以发现,对数组中每个元素的操作都是相同的,则可以使用循环来进行打印。
1. 循环遍历数组
public class Main {public static void main(String[] args) {int[] arr = new int[]{1,2,3,4,5};for (int i = 0; i < 5; i++) {System.out.println(arr[i]);}}
}
改成循环之后,上述三个缺陷可以全部2和3问题可以全部解决,但是无法解决问题1。那能否获取到数组的长度呢?
【注意】:在数组中可以通过 数组对象.length
来获取数组的长度
public class Main {public static void main(String[] args) {int[] arr = new int[]{1,2,3,4,5};for (int i = 0; i < arr.length; i++) {System.out.println(arr[i]);}}
}
2. 使用 for-each
遍历数组
语法格式:
public class Main {public static void main(String[] args) {int[] arr = new int[]{1,2,3,4,5};for (int x : arr) {System.out.println(x);}}
}
for-each
是 for
循环的另外一种使用方式. 能够更方便的完成对数组的遍历. 可以避免循环条件和更新语句写错.
for-each
循环语句的循环变量将会遍历数组中的每个元素,而不是下标值。
3. 数组转字符串输出
import java.util.Arrays;public class Main {public static void main(String[] args) {int[] arr = new int[]{1,2,3,4,5};String ret = Arrays.toString(arr);System.out.println(ret);}
}
代码分析:
2.数组是引用类型
2.1 JVM 内存分布
内存是一段连续的存储空间,主要是用来存储程序运行时数据的。比如:
- 程序运行时代码需要加载到内存
- 程序运行产生的中间数据要存放在内存
- 程序中的常量也要保存
- 有些数据可能需要长时间存储,而有些数据当方法运行结束后就要被销毁。
如果对内存中存储的数据不加区分的随意存储,那对内存管理起来将会非常麻烦。比如:
因此 JVM 也对所使用的内存按照功能的不同进行了划分:
- 程序计数器:只是一个很小的空间,保存下一条执行的指令的地址
- 虚拟机栈:与方法调用相关的一些信息,每个方法在执行时,都会先创建栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后吧,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
- 本地方法栈:本地方法栈于虚拟机栈的作用类似,只不过保存的内容是方法的局部变量。在有些版本的 JVM 实现中,本地方法栈和虚拟机栈是一起的
- 堆:JVM 所管理的最大内存区域,使用**
new
创建的对象都是在堆上保存,堆是随着程序开始运行时而创建,随着程序的结束而销毁,堆中的数据只要还有在使用,就不会被销毁** - 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法编译出的字节码就是保存在这个区域。
2.2 基本类型的变量与引用类型变量的区别
基本数据类型的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;
而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址
public class Main {public static void main(String[] args) {int a = 10;int[] arr = new int[]{1,2,3};}
}
在上述代码中,a
、arr
,都是函数内部的变量,因此其空间都在main
方法对应的栈帧中分配。
a
是内置类型的变量,因此其空间中保存的就是给该变量初始化的值。
arr
是数组类型的引用变量,其内部保存的内容可以简单理解成是数组在堆空间中的首地址。
上图可以看出,引用变量并不直接存储对象本生,可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量便可以去操作对象。有点类似C语言中的指针,但是 Java 中引用要比指针的操作更简单。
2.3 引用变量
public class Main {public static void main(String[] args) {int[] arr1 = new int[3];arr1[0] = 1;arr1[1] = 2;arr1[2] = 3;int[] arr2 = new int[]{1,2,3,4,5};arr2[0] = 100;arr2[1] = 200;arr1 = arr2;arr1[2] = 300;arr1[3] = 400;arr2[4] = 500;for (int x : arr1) {System.out.println(x);}}
}
2.4 认识 null
null
在 Java 中表示“空引用”,也就是一个不指向对象的引用
public class Main {public static void main(String[] args) {int[] arr = null;System.out.println(arr[0]);}
}
null
的作用类似于C语言中的NULL
(空指针),都是表示一个无效的内存位置。因此不能对这个内存进行任何读写操作。一旦尝试读写,就会抛出NullPointerException
【注意】:Java 中并没有约定
null
和 0 下标地址的内存有任何关联。
3. 数组应用场景
3.1 保存数据
public class Main {public static void main(String[] args) {int[] arr = new int[]{1,2,3};for (int x : arr) {System.out.println(x);}}
}
3.2 作为方法的参数
-
参数传基本数据类型
public class Main {public static void main(String[] args) {int num = 0;func(num);System.out.println("num = " + num);// 0}private static void func(int x) {x = 10;System.out.println("x = " + x); // 10} }
上述代码我们可以发现
func
方法中修改了形参x
的值,不影响实参的num
值。 -
参数传引用数据类型
public class Main {public static void main(String[] args) {int[] arr = new int[]{1,2,3};fun1(arr);System.out.println(Arrays.toString(arr)); // [1,2,3]fun2(arr);System.out.println(Arrays.toString(arr)); // [99,2,3]}public static void fun1(int[] arr) {arr = new int[]{11,22,33,44,55}; // 修改了形参的指向}public static void fun2(int[] arr) {arr[0] = 99; // 形参改变了实惨的值} }
上述代码我们可以发现
fun1
方法中修改了形参的指向,不影响实参数组的值fun2
方法内部修改了数组的内容,方法外部的数组内容也发生了改变。因为数组是引用类型,按照引用类型进行传递,是可以修改其中存放的内容的。
【总结】:所谓的“引用”本质只是存了地址。Java 将数组设定为引用类型,这样的话后续进行数组参数传参,其实只是将数组的地址传入函数形参中,这样可以避免对整数数组的拷贝(数组可能比较长,那么拷贝开销就会很大)。
3.3 作为方法的返回值
public class Main {public static void main(String[] args) {int[] ret = fun();System.out.println(Arrays.toString(ret)); // [1, 2, 3, 4, 5]}public static int[] fun() {int[] arr = new int[]{1,2,3,4,5};return arr;}
}
4. 二维数组
二维数组本质上也就是一维数组,只不过每个元素又是一个一维数组
基本语法:
数据类型[][] 数组名称 = new 数据类型 [行数][列数] { 初始化数据 };
代码示例:
public class Main {public static void main(String[] args) {int[][] arr = {{1, 2, 3},{4,5,6}};for (int i = 0; i < arr.length; i++) {for (int j = 0; j < arr[i].length; j++) {System.out.print(arr[i][j] + " ");}System.out.println();}System.out.println("=======");for (int[] tempArr : arr) {for (int x : tempArr) {System.out.print(x + " ");}System.out.println();}System.out.println("=======");String ret = Arrays.deepToString(arr); // deepToString()深度打印System.out.println(ret);}
}
Java 二维数组在定义的时候是可以省略列的
int[][] arr = new int[2][];
二维数组的用法和一维数组并没有明显差别, 因此我们不再赘述.
同理, 还存在 “三维数组”, “四维数组” 等更复杂的数组, 只不过出现频率都很低.
5. 不规则数组
代码示例:
public class Main {public static void main(String[] args) {int[][] arr = new int[2][];// 每一个一维数组 进行初始化arr[0] = new int[3];arr[1] = new int[5];for (int i = 0; i < arr.length; i++) {for (int j = 0; j < arr[i].length; j++) {System.out.print(arr[i][j] + " ");}System.out.println();}}
}
// 运行结果
0 0 0
0 0 0 0 0