【本节目标】
1. 理解数组基本概念
2. 掌握数组的基本用法
3. 数组与方法互操作
4. 熟练掌握数组相关的常见问题和代码
目录
1. 数组的基本概念
1.1什么是数组
1.2 数组的创建及初始化
1.3 数组的使用
2. 数组是引用类型
2.1基本类型变量与引用类型变量的区别
2.2再谈引用变量
2.3 认识 null
3. 数组的应用场景
3.1作为函数的参数
3.2 作为函数的返回值
4. 数组练习
4.1 数组转字符串
4.2 数组排序
4.3 数组拷贝
5. 二维数组
1. 数组的基本概念
1.1什么是数组
数组:可以看成是相同类型元素的一个集合。在内存中是一段连续的空间。
比如现实中的车库:
在java中,包含6个整形类型元素的数组,就相当于上图中连在一起的6个车位,从上图中可以看到:
1. 数组中存放的元素其类型相同
2. 数组的空间是连在一起的
3. 每个空间有自己的编号,其实位置的编号为0,即数组的下标。
1.2 数组的创建及初始化
T:表示数组中存放元素的类型
T[]:表示数组的类型
N:表示数组的长度
数组的创建:
int[] array1 = new int[10]; // 创建一个可以容纳10个int类型元素的数组
double[] array2 = new double[5]; // 创建一个可以容纳5个double类型元素的数组
String[] array3 = new String[3]; // 创建一个可以容纳3个字符串元素的数组
数组的初始化:
下面,我们来创建并初始化一个整形数组:
也可以采用C语言中创建数组的形式,但是一般不建议使用这种方式:
还可以使用下面这种方式来创建数组,这种方法和第一种方式没有任何区别,第一种方法只是这种方法的简写。
在上面,我们定义了一个数组,并对其进行了初始化。注意,如果创建了数组并且进行了初始化,则方括号[ ] 不能加数字,加了数字编译器会报错:
错误示范:
数组的初始化主要分为动态初始化以及静态初始化。
1. 动态初始化:在创建数组时,直接指定数组中元素的个数
2. 静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定
【注意事项】
静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。
静态初始化时, {}中数据类型必须与[]前数据类型一致。
静态初始化可以简写,省去后面的new T[]。(注意:虽然省去了new T[ ], 但是编译器编译代码时还是会还原)
静态和动态初始化也可以分为两步,但是省略格式不可以
int[] array1;
array1 = new int[10];
int[] array2;
array2 = new int[]{10, 20, 30};
// 注意省略格式不可以拆分, 否则编译失败
// int[] array3;
// array3 = {1, 2, 3};
因此,数组进行整体赋值的时候,只有一次机会,那就是定义的时候。
如果没有对数组进行初始化,数组中元素有其默认值
如果数组中存储元素类型为基类类型,默认值为基类类型对应的默认值,比如:
值得注意的是,boolean类型数组默认初始值是false
如果数组中存储元素类型为引用类型,默认值为null
1.3 数组的使用
数组中元素访问
数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,该编号称为数组的下标,数组可以通过 下标访问其任意位置的元素
int[] array = {1,2,3,4};
System.out.println(array[0]);
System.out.println(array[1]);
System.out.println(array[2]);
System.out.println(array[3]);
输出结果:
也可以通过[ ]对数组中的元素进行修改:
【注意事项】
1. 数组是一段连续的内存空间,因此支持随机访问,即通过下标访问快速访问数组中任意位置的元素
2. 下标从0开始,介于[0, N)之间不包含N,N为元素个数,不能越界,否则会报出下标越界异常。
在写代码的过程中,我们经常会出现类似下面的这种错误提示:
这是由于数组下标越界异常导致的,只要数组的下标不是一个合法的下标范围,都会出现这个错误提示。
在数组中可以通过 数组对象.length 来获取数组的长度
记住不要在length后面加括号
大家有没有想过,下面的代码的结果会是什么呢
程序输出结果:
其实,这输出的是数组的地址,在数组名这个变量中,存放的是地址,我们把数组名这个变量叫做引用变量,或者直接叫做引用。
遍历数组
所谓 "遍历" 是指将数组中的所有元素都访问一遍, 访问是指对数组中的元素进行某种操作,比如:打印。
方式一:普通for循环
方式二:增强for循环(for-each循环)
for-each 是 for 循环的另外一种使用方式. 能够更方便的完成对数组的遍历. 可以避免循环条件和更新语句写错
这个方法是在遍历这个数组的时候,把数组当中的元素进行赋值给 x
两种方法输出结果相同:
方式三:
在java中,有一个将整形数组转成字符串的方法:
在使用前,我们需要加上这句代码:
import java.util.Arrays;
也可以写成这种形式:
2. 数组是引用类型
2.1基本类型变量与引用类型变量的区别
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;
而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。
在上述代码中,a、b、arr,都是函数内部的变量,因此其空间都在main方法对应的栈帧中分配。
a、b是内置类型的变量,因此其空间中保存的就是给该变量初始化的值。
array是数组类型的引用变量,其内部保存的内容可以简单理解成是数组在堆空间中的首地址。
2.2再谈引用变量
public static void func() {
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]);
}
}
1.array1 = array2, 即让array1去引用array2引用的数组空间, 此时array1和array2实际是一个数组。
2.通过array1将数组2和3号位置元素修改为300,400,此时arrray2也能看到数组中修改的结果,因为array1和array2引用的是同一个数组。
3.通过array2将数组4号位置元素修改为500,此时array1也能看到数组中修改的结果,因为array1和array2引用的是同一个数组。
4.通过array2对数组中元素进行打印,输出100,200,300,400,500.
总结:
这句代码的意思就是array2 这个引用指向了array1这个引用所指向的对象。
问题:
一个引用能不能同时指向多个对象?
答案:不能。
2.3 认识 null
null 在 Java 中表示 "空引用" , 也就是一个不指向对象的引用.
null 的作用类似于 C 语言中的 NULL (空指针), 都是表示一个无效的内存位置. 因此不能对这个内存进行任何读写操 作. 一旦尝试读写, 就会抛出 NullPointerException.
注意: Java 中并没有约定 null 和 0 号地址的内存有任何关联.
3. 数组的应用场景
3.1作为函数的参数
在函数传参的时候,如果参数是基本数据类型,我们在函数中修改形参的值,并不会改变实参。因为形参是实参的一份临时拷贝。
但是,如果函数参数传递的是像数组这样的引用数据类型,方法外部的数组内容也发生改变
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]);
}
发现在func方法内部修改数组的内容, 方法外部的数组内容也发生改变. 因为数组是引用类型,按照引用类型来进行传递,是可以修改其中存放的内容的。
分析下面的代码:
public class Test {
public static void main(String[] args) {
int[] arr = {1,2,3,4};
//分别调用func1和func2之后,arr数组里面的值分别是多少?
System.out.println(Arrays.toString(arr));
}
public static void func1(int[] array){
array[0] = 99;
}
public static void func2(int[] array){
array = new int[]{11,22,33,44,55};
}
}
当我们只调用func1,程序运行的结果:
当我们只调用func2,程序运行的结果:
分析:
调用func1:
调用func2:
总结: 所谓的 "引用" 本质上只是存了一个地址. Java 将数组设定成引用类型, 这样的话后续进行数组参数传参, 其实 只是将数组的地址传入到函数形参中. 这样可以避免对整个数组的拷贝(数组可能比较长, 那么拷贝开销就会很大).
3.2 作为函数的返回值
public class Test{
public static int[] func(){
int[] arr = new int[]{1,2,3,4,5};
return arr;
}
public static void main(String[] args){
int[] ret = func();
System.out.println(Arrays.toString(ret));
}
}
这个程序定义了一个Test
类,其中包含了一个func
方法和一个main
方法。
func
方法是一个静态方法,返回类型为int[]
。在方法体内,它创建了一个包含整数元素的数组arr
,并将其初始化为{1,2,3,4,5}
。然后,它返回这个数组。
main
方法是程序的入口点。在方法体内,它调用func
方法,并将返回的数组存储在名为ret
的变量中。然后,它使用Arrays.toString
方法将ret
数组转换为字符串,并通过System.out.println
打印输出。
输出结果:
4. 数组练习
4.1 数组转字符串
在之前,我们已经讲到了将数组转成字符串进行输出的方法:
使用这个方法后续打印数组就更方便一些.
Java 中提供了 java.util.Arrays 包, 其中包含了一些操作数组的常用方法.
对于toString()这个方法,我们也可以自己来实现:
public static String myToString(int[] array) {
if(array == null){
return "null";
}
if(array.length == 0){
return "[]";
}
String ret = "[";
for (int i = 0; i < array.length; i++) {
ret += array[i];
if (i < array.length - 1) {
ret += ", ";
}
}
ret += "]";
return ret;
}
4.2 数组排序
还有可以对数组进行排序的方法:
也可以指定要排序的区间:
要注意的是,在java中的区间一般都是左闭右开 [ ) .
冒泡排序算法思路
假设排升序:
1. 将数组中相邻元素从前往后依次进行比较,如果前一个元素比后一个元素大,则交换,一趟下来后最大元素 就在数组的末尾
2. 依次从上上述过程,直到数组中所有的元素都排列好
public static void bubbleSort(int[] array) {
//i 代表排序的趟数
for (int i = 0; i < array.length - 1; i++) {
boolean flag = true;//优化
for (int j = 0; j < array.length - i - 1; j++) {
if (array[j] > array[j + 1]) {
int tmp = array[j];
array[j] = array[j + 1];
array[j+1]=tmp;
flag = false;
}
}
if(flag){
break;
}
}
}
冒泡排序性能较低。
4.3 数组拷贝
在Java中,有几种方式可以进行数组的拷贝。
1.使用循环逐个拷贝元素:可以使用循环将源数组的元素一个个复制到新数组中。例如:
public class Test {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int[] copy = new int[array.length];
for (int i = 0; i < array.length; i++) {
copy[i] = array[i];
}
System.out.println(Arrays.toString(copy));
}
}
2.使用Arrays.copyOf
方法:Arrays
类提供了一个名为copyOf
的方法,它可以用于将源数组的元素复制到新数组中。例如:
而且,Arrays.copyOf
方法还可以用来给数组扩容
3.使用System.arraycopy
方法:System
类提供了一个名为arraycopy
的静态方法,它可以用于将源数组的一部分元素复制到目标数组的指定位置。例如:
注意:数组当中存储的是基本类型数据时,不论怎么拷贝基本都不会出现什么问题,但如果存储的是引用数据类 型,拷贝时需要考虑深浅拷贝的问题,关于深浅拷贝在后续详细给大家介绍。
5. 二维数组
二维数组的创建方法:
注意:[ ] 内不能加数字,同时,数组的每一行元素都必须使用大括号括起来,否则会报错:
除此之外,还可以这样定义二维数组:
和C语言不同的是,C语言二维数组可以省略行,但不能省略列,java恰恰相反,java在定义二维数组的时候,可以省略列,但不能省略行。
下面是错误示例:
对于一个二维数组:
通过下面的代码即可验证:
程序输出的是两串地址:
二维数组本质上也就是一维数组, 只不过每个元素又是一个一维数组.
因此,对于下面的代码:array.length求出的是数组的行数,array[0].length求出的是列数。
输出结果:
对于二维数组,我们还可以这样玩:
同时这是一个不规则的二维数组,它的列不相同 -
注意:new int[ ] 不能声省略:
二维数组的遍历:
int[][] array = new int[][]{{1,2,3},{4,5,6}};
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();
}
使用for-each遍历二维数组:
将二维数组转成字符串:
Arrays.deepToString
是一个用于将多维数组转换为字符串的方法,它可以打印多维数组的内容。
使用Arrays.deepToString
方法可以方便地将二维数组转换为字符串表示形式,而不需要自己编写遍历和拼接字符串的代码。
下面是使用Arrays.deepToString
方法的示例: