前言:本篇我们学习的是数组的定义与使用,在前面我们已经学习了Java的一些基本内容,然后今天我们来学习Java中的数组。
每文一图:
让我们走进数组的学习ba!
数组定义与使用:- 一.数组基本用法
- 1.创建数组
- 2.数组的使用
- 二.数组作为方法的参数
- 1.基本用法
- 2.理解引用类型(重点/难点)
- 3.认识null
- 4.初识 JVM 内存区域划分
- 三.数组作为方法的返回值
- 四.二维数组
要了解数组的基本用法,先了解一下:什么是数组?其实数组本质上就是让我们能 “批量” 创建相同类型的变量,然后把他们作为一个组合组合起来。
例如:
当我们需要表示两个数据, 可以创建两个变量 int a; int b当我们需要表示五个数据, 可以创建五个变量 int a1;int a2; int a3; int a4; int a5;
但是如果需要表示一万个数据, 那么就不能创建一万个变量了(当然你要是够肝也可以)。这时候就需要使用数组, 帮我们批量创建数据。
所以数组就是让我们能在创建大量相同数据的时候可以更加方便,并且可以首先一些连续性的功能。在这里还有一个注意事项: 在 Java 中, 数组中包含的变量必须是相同类型!
接下来让我们走进数组的基本用法吧>
1.创建数组
首先我们先来学习创建数组的基本语法:
基本语法:
动态初始化
数据类型[ ] 数组名称 = new 数据类型 [ ] { 初始化数据 };
静态初始化
数据类型[ ] 数组名称 = { 初始化数据 };
在这里的new实际上是一个关键字,是实例化一个对象的关键字,意味着Java当中的数组也是一个对象。静态初始化中虽然没有new,但也是一个对象。
代码例子:
public static void main(String[] args) {
int [] array1 = {1,2,3,4,5};//静态初始化
int [] array2 = new int[5];//未初始化的数组
int [] array3 = new int[]{1,2,3,4,5};//动态初始化
}
注意事项:
1.定义数组的时候,不能够写具体的数组,如int [10] array ={};
2.静态初始化的时候, 数组元素个数和初始化数据的格式是一致的,如我们定义的是int [] array1,初始化中数据的类型就应该是整形,不能出现浮点型等类型
对于数组的初始化,其实是这样子的:
当我们创建一个数组并初始化,就会得到这样子的数组,数组中存储了5个元素,每个元素都有一个下标对应,下标是从0开始的,比如array[0]在这里就是1,而对于没有初始化的数组定义,会将其中的元素都是默认值为0。
这就是数组的创建。
2.数组的使用
1.获取长度 & 访问元素
像我们上面说的,对于创建出来的元素,每一个元素我们有其对应的下标,这样可以让我们更好的访问这个元素。同时,我们在创建数组之后,可以通过数组 + .length去获取数组长度,也就是有多少个元素。
我们直接上代码:
public static void main(String[] args) {
int [] array1 = {1,2,3,4,5};
int [] array2 = new int[5];
// 获取数组长度
System.out.println("length: " + array1.length); // 执行结果: 5
// 访问数组中的元素
System.out.println(array1[0]); // 执行结果: 1
System.out.println(array1[1]); // 执行结果: 2
array2[0] = 100;
System.out.println(array2[0]); // 执行结果: 100
}
}
正如上面代码一样,我们可以用array1.length去获取数组array1的长度。然后当我们想知道哪一个元素的值的时候我们就可以通过其下标访问这个元素,如array1[0]就是访问array1中的第一个元素。
然后就是我们创建好没有初始化的数组array2,我们可以给其赋值,如array2[0] = 100;然后就把该值存储到了array2的0下标所对应的第一个元素处。同样的如果我们在后面再加上array2[0] = 10;,array2的0下标所对应的就是10了,因为100已经被覆盖了。
最后就是我们的数组下标访问时,如果我们定义的是5个元素,其下标就应该是0-4,所以我们只可以访问0-4下标的元素,不能访问下标为5的,比如array1[5]=0 //err。就相当于只买了这套房子的四层楼,我们没有第五层楼的钥匙一样。
所以这里我们总结了几点注意事项:
1.使用 arr.length 能够获取到数组的长度, 这个操作为成员访问操作符,后面在面向对象中会经常用到。
2.使用 [ ] 按下标取数组元素,需要注意,下标从 0 开始计数!
3.使用 [ ] 操作既能读取数据,也能修改数据。
4.下标访问操作不能超出有效范围 [0, length - 1] , 如果超出有效范围, 会出现下标越界异常。
接下来演示几个例子:
1.下标越界(没钥匙还闯别人家)
public static void main(String[] args) {
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
// 执行结果
//Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
//at Test.main(Test.java:4)
}
好家伙,就买了三层楼想去一百楼?那肯定是不行的,所以我们的运行结果是一个错误。我们在写代码的时候会经常碰到错误,我们要读懂错误,才能更好的学习,这里其实就是ArrayIndexOut,也就是数组越界。
2.遍历数组(把我家房子都看一遍)
其实,所谓 “遍历” 是指将数组中的所有元素都访问一遍,不重不漏。通常需要搭配循环语句,我们这里就搭配一波for循环。
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
//arr.length表示数组长度,一次打印一个,多长就打印多少次
}
3.使用 for-each 遍历数组
对于for循环还有一种是for-each,在这里才写是因为其和数组关系密切,for-each 是 for 循环的另外一种使用方式.,能够更方便的完成对数组的遍历。可以避免循环条件和更新语句写错。
public static void main(String[] args) {
int[] arr = {1, 2, 3};
for (int x : arr) {
System.out.println(x);
}
//遍历数组,全部打印
//优点:不用写循环更新语句 缺点:全打印,拿不到下标
//
}
这就是数组的基本语法。
二.数组作为方法的参数
在我们上一篇文章中我们学习了方法,知道方法的好处,那方法和数组在一起,又会产生什么样的火花呢?我们来看看:
1.基本用法基本语法还是遍历打印的,只不过是以方法的形式,对他进行了一个包装,让我们想打印的时候直接调用方法就好了,不需要再写一次。
public static void printArray(int[] a) {
for (int x : a) {
System.out.println(x);//遍历打印
}
}
public static void main(String[] args) {
int[] arr = {1, 2, 3};
printArray(arr);//调用打印数组的方法
}
在这个代码中,我们需要注意的是:
int[] a 是函数的形参,int[] arr 是函数实参。传参的arr表示数组类型的引用。如果需要获取到数组长度, 同样可以使用 a.length。
什么是引用?我们接下来就会看见。
2.理解引用类型(重点/难点)
对于引用类型,我们先从代码引起:
先看下面的代码:
public static void main(String[] args) {
int num = 0;
func(num);
System.out.println("num = " + num);
}
public static void func(int x) {
x = 10;
System.out.println("x = " + x);
}
// 执行结果x = 10 num = 0
我们发现,修改形参 x 的值, 不影响实参的 num 值。
我们再看数组的版本:
public static void main(String[] args) {
int[] arr = {1, 2, 3};
func(arr);
System.out.println("arr[0] = " + arr[0]);
}
public static void func(int[] a) {
a[0] = 10;
System.out.println("a[0] = " + a[0]);
}
// 执行结果
// a[0] = 10
// arr[0] = 10
我们发现, 在函数内部修改数组内容如a[0], 函数外部的arr[0]也发生改变。这是因为此时数组名 arr 是一个 “引用” ,当传参的时候,是按照引用传参!
对于引用:
这里我们要先从内存开始说起,我们知道电脑手机等设别都是有内存的,这里说的内存就是指我们熟悉的 “内存”。
那么如何理解内存?
内存可以直观的理解成一个宿舍楼,有一个长长的大走廊,上面有很多房间。每个房间的大小是 1 Byte (如果计算机有 8G 内存, 则相当于有 80亿 个这样的房间)。每个房间上面又有一个门牌号,这个门牌号就称为 地址。
那么啥又是引用?
引用相当于一个 “别名”,也可以理解成一个指针(一个指向这个地址的指针)。创建一个引用只是相当于创建了一个很小的变量,这个变量保存了一个整数,这个整数表示内存中的一个地址。
我们用具体图示演示一下:
其实还是房子的例子,我们的引用就是能找到能进去我们房子的钥匙,这样子才可以改变我们的房子,有一天我们懒得改变,就叫别人去弄,给别人配了把备用钥匙,那别人去改变了我们房子的东西,我们的房子也肯定真的变了。
我们再来写一个代码:
public static void print(int [] a){
for (int i = 0; i < a.length; i++) {
System.out.print(a[i]);
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = new int[]{1, 2, 3};
print(arr);
}
其实对于Java的底层中大致有这些玩意:
我们这里以栈堆来说一下关于引用:
在创建数组的时候,我们就会在栈中开辟空间创建数组名和它的引用,然后在堆是创建数组对象,也就是数组存储的数据,引用则指向了这些数据的位置。然后传参到一个方法处相当于把这个引用拷贝了一份给他。
总结:
所谓的 “引用” 本质上只是存了一个地址。
Java将数组设定成引用类型,这样的话后续进行数组参数传参,其实只是将数组的地址传入到函数形参中。这样可以避免对整个数组的拷贝(数组可能比较长,那么拷贝开销就会很大)。
3.认识null
null 在 Java 中表示 “空引用” ,也就是一个无效的引用。
比如我们访问一个引用为null的数组:
public static void main(String[] args) {
int[] arr = null;
System.out.println(arr[0]);
// 执行结果 :空错误
// Exception in thread "main" java.lang.NullPointerException
// at Test.main(Test.java:6)
}
null 的作用类似于 C 语言中的 NULL (空指针), 都是表示一个无效的内存位置. 因此不能对这个内存进行任何读写操作。一旦尝试读写,就会抛出 NullPointerException。
4.初识 JVM 内存区域划分
JVM是Java VirtualMachine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
我们发现,在上面的图中,程序计数器,虚拟机栈,本地方法栈被很多个原谅色的,名叫 Thread(线程) 的方框圈起来了,并且存在很多份。而堆,方法区, 运行时常量池,只有一份。
在这里我们只需要了解一下大概的构造,然后用他去理解引用,对于整个JVM的学习,再说吧(bushi)。
1.局部变量和引用保存在栈上,new 出的对象保存在堆上。
2.堆的空间非常大,栈的空间比较小。
3.堆是整个 JVM 共享一个,而栈每个线程具有一份(一个 Java 程序中可能存在多个栈)。
三.数组作为方法的返回值
如果我想让数组的每个元素都变成原来的两倍,应该怎么写呢?
我们来看这个代码:
// 直接修改原数组
public static void main(String[] args) {
int[] arr = {1, 2, 3};
transform(arr);
printArray(arr);
}
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
public static void transform(int[] arr) {
for (int i = 0; i < arr.length; i++) {
arr[i] = arr[i] * 2;
}
}
在这里,我们的代码固然可以实现我们想要的效果,但是破坏了原有数组。有时候我们不希望破坏原数组,就需要在方法内部创建一个新的数组,并由方法返回出来。
所以我们就需要接收返回值的代码:
// 返回一个新的数组
public static void main(String[] args) {
int[] arr = {1, 2, 3};
int[] output = transform(arr);
printArray(output);
}
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
public static int[] transform(int[] arr) {
int[] ret = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
ret[i] = arr[i] * 2;
}
return ret;
}
这样子就不会破坏原来的数组,而且由于数组是引用类型,返回的时候只是将这个数组的首地址返回给函数调用者,没有拷贝数组内容,从而比较高
效。
四.二维数组
介绍了那么多,其实刚刚我们介绍的都是一维数组,还有二维三维等的数组,但都有异曲同工之妙,所以我们这里先介绍二维数组。
二维数组本质上也就是一维数组, 只不过每个元素又是一个一维数组。
基本语法:
数据类型[][] 数组名称 = new 数据类型 [行数][列数] { 初始化数据 };
代码演示:
//跟一维数组相似
public static void main(String[] args) {
int[][] arr1 = {{1,2,3},{4,5}};
int[][] arr2 = new int[][]{{1,2,3},{4,5}};
int[][] arr3 = new int[2][3];
}
那上面为什么说二维数组本质上也就是一维数组,其实我们可以这样理解,二维数组中,每一层当做一个元素,而每一层的那个元素里面又有一些元素。
所以说二维数组的第一层表示了有多少个行,然后每一个行作为一个一维数组去表示列,去展示里面的元素。
//打印二维数组
public static void main(String[] args) {
int[][] arr= new int[][]{{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.printf("%dt", arr[i][j]);
}
System.out.println("");
}
}
这就是二维数组。
二维数组的用法和一维数组并没有明显差别,因此我们不再赘述。同理,还存在 “三维数组”,“四维数组” 等更复杂的数组,只不过出现频率都很低。
这就是本篇Java中的数组定义与使用的全部内容啦,如果觉得还不错或者感觉对你有帮助,不妨点赞关注一键三连。稍后会写一篇关于数组的习题一起学习。一起学习,共同努力!也可以期待这个系列接下来的博客噢。
链接:都在这里! Java SE 带你从零到一系列
还有一件事:



